"""A parser for SGML, using the derived class as a static DTD."""
# XXX This only supports those SGML features used by HTML.
# XXX There should be a way to distinguish between PCDATA (parsed
# character data -- the normal case), RCDATA (replaceable character
# data -- only char and entity references and end tags are special)
# and CDATA (character data -- only end tags are special). RCDATA is
__all__
= ["SGMLParser", "SGMLParseError"]
# Regular expressions used for parsing
interesting
= re
.compile('[&<]')
incomplete
= re
.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|'
entityref
= re
.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
charref
= re
.compile('&#([0-9]+)[^0-9]')
starttagopen
= re
.compile('<[>a-zA-Z]')
shorttagopen
= re
.compile('<[a-zA-Z][-.a-zA-Z0-9]*/')
shorttag
= re
.compile('<([a-zA-Z][-.a-zA-Z0-9]*)/([^/]*)/')
piclose
= re
.compile('>')
endbracket
= re
.compile('[<>]')
tagfind
= re
.compile('[a-zA-Z][-_.a-zA-Z0-9]*')
r
'\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)(\s*=\s*'
r
'(\'[^
\']*\'|
"[^"]*"|[-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?
')
class SGMLParseError(RuntimeError):
"""Exception raised for all parse errors."""
# SGML parser base class -- find tags and call handler functions.
# Usage: p = SGMLParser(); p.feed(data); ...; p.close().
# The dtd is defined by deriving a class which defines methods
# with special names to handle tags: start_foo and end_foo to handle
# <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
# (Tags are converted to lower case for this purpose.) The data
# between tags is passed to the parser by calling self.handle_data()
# with some data as argument (the data may be split up in arbitrary
# chunks). Entity references are passed by calling
# self.handle_entityref() with the entity reference as argument.
class SGMLParser(markupbase.ParserBase):
def __init__(self, verbose=0):
"""Initialize and reset this instance."""
"""Reset this instance. Loses all unprocessed data."""
self.__starttag_text = None
markupbase.ParserBase.reset(self)
"""Enter literal mode (CDATA) till EOF.
Intended for derived classes only.
self.nomoretags = self.literal = 1
def setliteral(self, *args):
"""Enter literal mode (CDATA).
Intended for derived classes only.
"""Feed some data to the parser.
Call this as often as you want, with as little or as much text
as you want (may include '\n'). (This just saves the text,
all the processing is done by goahead().)
self.rawdata = self.rawdata + data
"""Handle the remaining data."""
def error(self, message):
raise SGMLParseError(message)
# Internal -- handle data as far as reasonable. May leave state
# and data to be processed by a subsequent call. If 'end
' is
# true, force handling all data as if followed by EOF marker.
self.handle_data(rawdata[i:n])
match = interesting.search(rawdata, i)
if match: j = match.start()
self.handle_data(rawdata[i:j])
if starttagopen.match(rawdata, i):
self.handle_data(rawdata[i])
k = self.parse_starttag(i)
if rawdata.startswith("</", i):
if rawdata.startswith("<!--", i):
# Strictly speaking, a comment is --.*--
# within a declaration tag <!...>.
# This should be removed,
# and comments handled only in parse_declaration.
k = self.parse_comment(i)
if rawdata.startswith("<?", i):
if rawdata.startswith("<!", i):
# This is some sort of declaration; in "HTML as
# deployed," this should only be the document type
# declaration ("<!DOCTYPE html...>").
k = self.parse_declaration(i)
self.handle_data(rawdata[i])
match = charref.match(rawdata, i)
self.handle_charref(name)
if rawdata[i-1] != ';': i = i-1
match = entityref.match(rawdata, i)
self.handle_entityref(name)
if rawdata[i-1] != ';': i = i-1
self.error('neither
< nor
& ??
')
# We get here only if incomplete matches but
match = incomplete.match(rawdata, i)
self.handle_data(rawdata[i])
break # Really incomplete
self.handle_data(rawdata[i:j])
self.handle_data(rawdata[i:n])
self.rawdata = rawdata[i:]
# XXX if end: check for empty stack
# Extensions for the DOCTYPE scanner:
# Internal -- parse processing instr, return length or -1 if not terminated
if rawdata[i:i+2] != '<?
':
self.error('unexpected call to
parse_pi()')
match = piclose.search(rawdata, i+2)
self.handle_pi(rawdata[i+2: j])
def get_starttag_text(self):
return self.__starttag_text
# Internal -- handle starttag, return length or -1 if not terminated
def parse_starttag(self, i):
self.__starttag_text = None
if shorttagopen.match(rawdata, i):
# SGML shorthand: <tag/data/ == <tag>data</tag>
# XXX Can data contain &... (entity or char refs)?
# XXX Can data contain < or > (tag characters)?
# XXX Can there be whitespace before the first /?
match = shorttag.match(rawdata, i)
tag, data = match.group(1, 2)
self.__starttag_text = '<%s/' % tag
self.finish_shorttag(tag, data)
self.__starttag_text = rawdata[start_pos:match.end(1) + 1]
# XXX The following should skip matching quotes (' or ")
match = endbracket.search(rawdata, i+1)
# Now parse the data between i+1 and j into a tag and attrs
if rawdata[i:i+2] == '<>':
# SGML shorthand: <> == <last open tag seen>
match = tagfind.match(rawdata, i+1)
self.error('unexpected call to parse_starttag')
tag = rawdata[i+1:k].lower()
match = attrfind.match(rawdata, k)
attrname, rest, attrvalue = match.group(1, 2, 3)
elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
attrvalue[:1] == '"' == attrvalue[-1:]:
attrvalue = attrvalue[1:-1]
attrs.append((attrname.lower(), attrvalue))
self.__starttag_text = rawdata[start_pos:j]
self.finish_starttag(tag, attrs)
# Internal -- parse endtag
def parse_endtag(self, i):
match = endbracket.search(rawdata, i+1)
tag = rawdata[i+2:j].strip().lower()
# Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>)
def finish_shorttag(self, tag, data):
self.finish_starttag(tag, [])
# Internal -- finish processing of start tag
# Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag
def finish_starttag(self, tag, attrs):
method = getattr(self, 'start_
' + tag)
method = getattr(self, 'do_
' + tag)
self.unknown_starttag(tag, attrs)
self.handle_starttag(tag, method, attrs)
self.handle_starttag(tag, method, attrs)
# Internal -- finish processing of end tag
def finish_endtag(self, tag):
found = len(self.stack) - 1
if tag not in self.stack:
method = getattr(self, 'end_
' + tag)
self.report_unbalanced(tag)
if self.stack[i] == tag: found = i
while len(self.stack) > found:
method = getattr(self, 'end_
' + tag)
self.handle_endtag(tag, method)
# Overridable -- handle start tag
def handle_starttag(self, tag, method, attrs):
# Overridable -- handle end tag
def handle_endtag(self, tag, method):
# Example -- report an unbalanced </...> tag.
def report_unbalanced(self, tag):
print '*** Unbalanced
</' + tag + '>'
print '*** Stack
:', self.stack
def handle_charref(self, name):
"""Handle character reference, no need to override."""
self.unknown_charref(name)
self.unknown_charref(name)
# Definition of entities -- derived classes may override
{'lt
': '<', 'gt
': '>', 'amp
': '&', 'quot
': '"', 'apos': '\''}
def handle_entityref(self, name):
"""Handle entity references.
There should be no need to override this method; it can be
tailored by setting up the self.entitydefs mapping appropriately.
self.handle_data(table[name])
self.unknown_entityref(name)
# Example -- handle data, should be overridden
def handle_data(self, data):
# Example -- handle comment, could be overridden
def handle_comment(self, data):
# Example -- handle declaration, could be overridden
def handle_decl(self, decl):
# Example -- handle processing instruction, could be overridden
def handle_pi(self, data):
# To be overridden -- handlers for unknown objects
def unknown_starttag(self, tag, attrs): pass
def unknown_endtag(self, tag): pass
def unknown_charref(self, ref): pass
def unknown_entityref(self, ref): pass
class TestSGMLParser(SGMLParser):
def __init__(self, verbose=0):
SGMLParser.__init__(self, verbose)
def handle_data(self, data):
self.testdata = self.testdata + data
if len(repr(self.testdata)) >= 70:
print 'data:', repr(data)
def handle_comment(self, data):
r = r[:32] + '...' + r[-32:]
def unknown_starttag(self, tag, attrs):
print 'start tag: <' + tag + '>'
print 'start tag: <' + tag,
for name, value in attrs:
print name + '=' + '"' + value + '"',
def unknown_endtag(self, tag):
print 'end tag: </' + tag + '>'
def unknown_entityref(self, ref):
print '*** unknown entity ref: &' + ref + ';'
def unknown_charref(self, ref):
print '*** unknown char ref: &#' + ref + ';'
def unknown_decl(self, data):
print '*** unknown decl: [' + data + ']'
if args and args[0] == '-s':
if __name__ == '__main__':