我正在尝试解析一个文件,例如: http://www.sec.gov/Archives/edgar/data/1409896/000118143112051484/0001181431-12-051484.hdr.sgml
我正在使用 Python 3,并且无法找到现有库的解决方案来解析带有开放标签的 SGML 文件。 SGML 允许隐式闭合标签。当尝试使用 LXML、XML 或 beautiful soup 解析示例文件时,我最终得到的结果是隐式关闭标签在文件末尾而不是在行末尾关闭。
例如:
<COMPANY>Awesome Corp
<FORM> 24-7
<ADDRESS>
<STREET>101 PARSNIP LN
<ZIP>31337
</ADDRESS>
这最终被解释为:
<COMPANY>Awesome Corp
<FORM> 24-7
<ADDRESS>
<STREET>101 PARSNIP LN
<ZIP>31337
</ADDRESS>
</ZIP>
</STREET>
</FORM>
</COMPANY>
但是,我需要将其解释为:
<COMPANY>Awesome Corp</COMPANY>
<FORM> 24-7</FORM>
<ADDRESS>
<STREET>101 PARSNIP LN</STREET>
<ZIP>31337</ZIP>
</ADDRESS>
如果有一个非默认解析器传递给 LXML/BS4 可以处理这个问题,我就错过了。
如果您可以找到所使用文档的 SGML DTD,解决方案可能是使用 OpenSP SGML 工具包 中的 osx SGML 到 XML 转换器将文档转换为 XML。
这是一个简单的例子。假设我们有以下 SGML 文档(company.sgml;带有根元素):
<!DOCTYPE ROOT SYSTEM "company.dtd">
<ROOT>
<COMPANY>Awesome Corp
<FORM> 24-7
<ADDRESS>
<STREET>101 PARSNIP LN
<ZIP>31337
</ADDRESS>
DTD (company.dtd) 如下所示:
<!ELEMENT ROOT - o (COMPANY, FORM, ADDRESS) >
<!ELEMENT COMPANY - o (#PCDATA) >
<!ELEMENT FORM - o (#PCDATA) >
<!ELEMENT ADDRESS - - (STREET, ZIP) >
<!ELEMENT STREET - o (#PCDATA) >
<!ELEMENT ZIP - o (#PCDATA) >
- o
位表示结束标记可以省略。
SGML文档可以使用osx进行解析,输出可以使用xmllint进行格式化,如下:
osx company.sgml | xmllint --format -
上述命令的输出:
<?xml version="1.0"?>
<ROOT>
<COMPANY>Awesome Corp</COMPANY>
<FORM> 24-7</FORM>
<ADDRESS>
<STREET>101 PARSNIP LN</STREET>
<ZIP>31337</ZIP>
</ADDRESS>
</ROOT>
现在我们有了格式良好的 XML,可以使用 lxml 或其他 XML 工具进行处理。
我不知道您链接到的文档是否有完整的 DTD。以下 PDF 文件包含有关 EDGAR 的相关信息,包括可能有用的 DTD:http://www.sec.gov/info/edgar/pdsdissemspec910.pdf(我通过这个答案找到它)。但链接的 SGML 文档包含 PDF 文件中未提及的元素(例如,
SEC-HEADER
)。
我使用 OpenSP 的 osx 进行 Edgar 解析工作,但我还构建了一个仅 Python 的 hack,它似乎也有效。
from html.parser import HTMLParser
class EndTagFinder(HTMLParser):
def __init__(self,):
super().__init__()
self.end_tags = set()
def handle_endtag(self, tag):
self.end_tags.add(tag)
def end_tags(sgml: str) -> set[str]:
f = EndTagFinder()
f.feed(sgml)
return f.end_tags
class SGMLFixer(HTMLParser):
def __init__(self, end_tags: set[str]=None):
super().__init__()
if end_tags is None:
self.end_tags = set()
else:
self.end_tags = end_tags
self.pieces = []
self.open_starttag = None
def handle_starttag(self, tag, attrs):
if self.open_starttag is not None:
if self.open_starttag not in self.end_tags:
self.pieces.append(('end', self.open_starttag))
self.open_starttag = None
self.pieces.append(('start', (tag, attrs)))
self.open_starttag = tag
def handle_data(self, data):
cleaned_data = data.strip()
if cleaned_data:
self.pieces.append(('data', cleaned_data))
def handle_endtag(self, tag):
if self.open_starttag is not None:
if self.open_starttag not in self.end_tags:
self.pieces.append(('end', self.open_starttag))
self.open_starttag = None
self.pieces.append(('end', tag))
def pieces(sgml: str, endtags: set[str]) -> list[tuple]:
f = SGMLFixer(endtags)
f.feed(sgml)
return f.pieces
def piece_to_str(piece: tuple) -> str:
_type, value = piece
if _type == 'start':
tag, attrs = value
return f'<{tag}>'
elif _type == 'end':
return f'</{value}>'
elif _type == 'data':
return value
else:
raise
def sgml_to_xml(sgml: str) -> str:
return ''.join(piece_to_str(p) for p in pieces(sgml, end_tags(sgml)))
if __name__ == '__main__':
sgml = '''<COMPANY>Awesome Corp
<FORM> 24-7
<ADDRESS>
<STREET>101 PARSNIP LN
<ZIP>31337
</ADDRESS>
'''
print(sgml_to_xml(sgml))
<company>Awesome Corp</company><form>24-7</form><address><street>101 PARSNIP LN</street><zip>31337</zip></address>