home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 April / PCWorld_2001-04_cd.bin / Software / TemaCD / webclean / webparser / sgmllib.py < prev    next >
Text File  |  2000-12-16  |  11KB  |  322 lines

  1. # A parser for SGML, using the derived class as static DTD.
  2.  
  3. # XXX This only supports those SGML features used by HTML.
  4.  
  5. # XXX There should be a way to distinguish between PCDATA (parsed
  6. # character data -- the normal case), RCDATA (replaceable character
  7. # data -- only char and entity references and end tags are special)
  8. # and CDATA (character data -- only end tags are special).
  9.  
  10. # sgmlop support added by fredrik@pythonware.com (April 6, 1998)
  11.  
  12. import re
  13. import string
  14.  
  15. try:
  16.     import sgmlop
  17. except ImportError:
  18.     sgmlop = None
  19.  
  20. # SGML parser base class -- find tags and call handler functions.
  21. # Usage: p = SGMLParser(); p.feed(data); ...; p.close().
  22. # The dtd is defined by deriving a class which defines methods
  23. # with special names to handle tags: start_foo and end_foo to handle
  24. # <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
  25. # (Tags are converted to lower case for this purpose.)  The data
  26. # between tags is passed to the parser by calling self.handle_data()
  27. # with some data as argument (the data may be split up in arbutrary
  28. # chunks).  Entity references are passed by calling
  29. # self.handle_entityref() with the entity reference as argument.
  30.  
  31. # --------------------------------------------------------------------
  32. # original re-based SGML parser
  33.  
  34. interesting = re.compile('[&<]')
  35. incomplete = re.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|'
  36.                         '<([a-zA-Z][^<>]*|'
  37.                         '/([a-zA-Z][^<>]*)?|'
  38.                         '![^<>]*)?')
  39. entityref = re.compile('&([a-zA-Z][a-zA-Z0-9]*)[^a-zA-Z0-9]')
  40. charref = re.compile('&#([0-9]+)[^0-9]')
  41. starttagopen = re.compile('<[>a-zA-Z]')
  42. shorttagopen = re.compile('<[a-zA-Z][a-zA-Z0-9]*/')
  43. shorttag = re.compile('<([a-zA-Z][a-zA-Z0-9]*)/([^/]*)/')
  44. endtagopen = re.compile('</[<>a-zA-Z]')
  45. endbracket = re.compile('[>]')
  46. special = re.compile('<![^<>]*>')
  47. commentopen = re.compile('<!--')
  48. commentclose = re.compile(r'--\s*>')
  49. tagfind = re.compile('[a-zA-Z][a-zA-Z0-9]*')
  50. attrfind = re.compile(
  51.     r'\s*([a-zA-Z_][-.a-zA-Z_0-9]*)'
  52.     r'(\s*=\s*'
  53.     r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:+*%?!\(\)_#=~]*))?')
  54.  
  55.  
  56. class SlowSGMLParser:
  57.  
  58.     # Interface -- initialize and reset this instance
  59.     def __init__(self, verbose=0):
  60.         self.verbose = verbose
  61.         self.reset()
  62.  
  63.     # Interface -- reset this instance.  Loses all unprocessed data
  64.     def reset(self):
  65.         self.rawdata = ''
  66.         self.lasttag = '???'
  67.         self.nomoretags = 0
  68.         self.literal = 0
  69.  
  70.     # For derived classes only -- enter literal mode (CDATA) till EOF
  71.     def setnomoretags(self):
  72.         self.nomoretags = self.literal = 1
  73.  
  74.     # For derived classes only -- enter literal mode (CDATA)
  75.     def setliteral(self, *args):
  76.         self.literal = 1
  77.  
  78.     # Interface -- feed some data to the parser.  Call this as
  79.     # often as you want, with as little or as much text as you
  80.     # want (may include '\n').  (This just saves the text, all the
  81.     # processing is done by goahead().)
  82.     def feed(self, data):
  83.         self.rawdata = self.rawdata + data
  84.         self.goahead(0)
  85.  
  86.     # Interface -- handle the remaining data
  87.     def close(self):
  88.         self.goahead(1)
  89.  
  90.     # Internal -- handle data as far as reasonable.  May leave state
  91.     # and data to be processed by a subsequent call.  If 'end' is
  92.     # true, force handling all data as if followed by EOF marker.
  93.     def goahead(self, end):
  94.         rawdata = self.rawdata
  95.         i = 0
  96.         n = len(rawdata)
  97.         while i < n:
  98.             if self.nomoretags:
  99.                 self.handle_data(rawdata[i:n])
  100.                 i = n
  101.                 break
  102.             match = interesting.search(rawdata, i)
  103.             if match: j = match.start(0)
  104.             else: j = n
  105.             if i < j: self.handle_data(rawdata[i:j])
  106.             i = j
  107.             if i == n: break
  108.             if rawdata[i] == '<':
  109.                 if starttagopen.match(rawdata, i):
  110.                     if self.literal:
  111.                         self.handle_data(rawdata[i])
  112.                         i = i+1
  113.                         continue
  114.                     k = self.parse_starttag(i)
  115.                     if k < 0: break
  116.                     i = k
  117.                     continue
  118.                 if endtagopen.match(rawdata, i):
  119.                     k = self.parse_endtag(i)
  120.                     if k < 0: break
  121.                     i =  k
  122.                     self.literal = 0
  123.                     continue
  124.                 if commentopen.match(rawdata, i):
  125.                     if self.literal:
  126.                         self.handle_data(rawdata[i])
  127.                         i = i+1
  128.                         continue
  129.                     k = self.parse_comment(i)
  130.                     if k < 0: break
  131.                     i = i+k
  132.                     continue
  133.                 match = special.match(rawdata, i)
  134.                 if match:
  135.                     if self.literal:
  136.                         self.handle_data(rawdata[i])
  137.                         i = i+1
  138.                         continue
  139.                     i = match.end(0)
  140.                     continue
  141.             elif rawdata[i] == '&':
  142.                 match = charref.match(rawdata, i)
  143.                 if match:
  144.                     name = match.group(1)
  145.                     self.handle_charref(name)
  146.                     i = match.end(0)
  147.                     if rawdata[i-1] != ';': i = i-1
  148.                     continue
  149.                 match = entityref.match(rawdata, i)
  150.                 if match:
  151.                     name = match.group(1)
  152.                     self.handle_entityref(name)
  153.                     i = match.end(0)
  154.                     if rawdata[i-1] != ';': i = i-1
  155.                     continue
  156.             else:
  157.                 raise RuntimeError, 'neither < nor & ??'
  158.             # We get here only if incomplete matches but
  159.             # nothing else
  160.             match = incomplete.match(rawdata, i)
  161.             if not match:
  162.                 self.handle_data(rawdata[i])
  163.                 i = i+1
  164.                 continue
  165.             j = match.end(0)
  166.             if j == n:
  167.                 break # Really incomplete
  168.             self.handle_data(rawdata[i:j])
  169.             i = j
  170.         # end while
  171.         if end and i < n:
  172.             self.handle_data(rawdata[i:n])
  173.             i = n
  174.         self.rawdata = rawdata[i:]
  175.  
  176.     # Internal -- parse comment, return length or -1 if not terminated
  177.     def parse_comment(self, i):
  178.         rawdata = self.rawdata
  179.         if rawdata[i:i+4] <> '<!--':
  180.             raise RuntimeError, 'unexpected call to handle_comment'
  181.         match = commentclose.search(rawdata, i+4)
  182.         if not match:
  183.             return -1
  184.         j = match.start(0)
  185.         self.handle_comment(rawdata[i+4: j])
  186.         j = match.end(0)
  187.         return j-i
  188.  
  189.     # Internal -- handle starttag, return length or -1 if not terminated
  190.     def parse_starttag(self, i):
  191.         rawdata = self.rawdata
  192.         if shorttagopen.match(rawdata, i):
  193.             # SGML shorthand: <tag/data/ == <tag>data</tag>
  194.             # XXX Can data contain &... (entity or char refs)?
  195.             # XXX Can data contain < or > (tag characters)?
  196.             # XXX Can there be whitespace before the first /?
  197.             match = shorttag.match(rawdata, i)
  198.             if not match:
  199.                 return -1
  200.             tag, data = match.group(1, 2)
  201.             tag = string.lower(tag)
  202.             self.unknown_starttag(tag, [])
  203.             self.handle_data(data)
  204.             self.unknown_endtag(tag)
  205.             k = match.end(0)
  206.             return k
  207.         # XXX The following should skip matching quotes (' or ")
  208.         quote = 0
  209.         for j in range(i+1, len(rawdata)):
  210.             if rawdata[j]=='"' or rawdata[j]=="'":
  211.                 quote = not quote
  212.             elif rawdata[j]==">" and not quote:
  213.                 break
  214.         if rawdata[j]!='>': return -1
  215.         #match = endbracket.search(rawdata, i+1)
  216.         #if not match:
  217.         #    return -1
  218.         #j = match.start(0)
  219.         # Now parse the data between i+1 and j into a tag and attrs
  220.         attrs = []
  221.         if rawdata[i:i+2] == '<>':
  222.             # SGML shorthand: <> == <last open tag seen>
  223.             k = j
  224.             tag = self.lasttag
  225.         else:
  226.             match = tagfind.match(rawdata, i+1)
  227.             if not match:
  228.                 raise RuntimeError, 'unexpected call to parse_starttag'
  229.             k = match.end(0)
  230.             tag = string.lower(rawdata[i+1:k])
  231.             self.lasttag = tag
  232.         while k < j:
  233.             match = attrfind.match(rawdata, k)
  234.             if not match: break
  235.             attrname, rest, attrvalue = match.group(1, 2, 3)
  236.             if not rest:
  237.                 attrvalue = attrname
  238.             elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
  239.                  attrvalue[:1] == '"' == attrvalue[-1:]:
  240.                 attrvalue = attrvalue[1:-1]
  241.             attrs.append((string.lower(attrname), attrvalue))
  242.             k = match.end(0)
  243.         if rawdata[j] == '>':
  244.             j = j+1
  245.         self.unknown_starttag(tag, attrs)
  246.         return j
  247.  
  248.     # Internal -- parse endtag
  249.     def parse_endtag(self, i):
  250.         rawdata = self.rawdata
  251.         match = endbracket.search(rawdata, i+1)
  252.         if not match:
  253.             return -1
  254.         j = match.start(0)
  255.         tag = string.lower(string.strip(rawdata[i+2:j]))
  256.         if rawdata[j] == '>':
  257.             j = j+1
  258.         self.unknown_endtag(tag)
  259.         return j
  260.  
  261.     # handlers, can be overridden
  262.     def handle_entityref(self, name): pass
  263.     def handle_charref(self, name):   pass
  264.     def handle_proc(self, name):      pass
  265.     def handle_special(self, name):   pass
  266.     def handle_data(self, data):      pass
  267.     def handle_cdata(self, data):     pass
  268.     def handle_comment(self, data):   pass
  269.     def unknown_starttag(self, tag, attrs): pass
  270.     def unknown_endtag(self, tag):    pass
  271.     def unknown_charref(self, ref):   pass
  272.     def unknown_entityref(self, ref): pass
  273.  
  274.  
  275. # --------------------------------------------------------------------
  276. # accelerated SGML parser
  277.  
  278. class FastSGMLParser:
  279.     # Interface -- initialize and reset this instance
  280.     def __init__(self):
  281.         self.parser = sgmlop.SGMLParser(self)
  282.         self.feed = self.parser.feed
  283.         self.reset = self.parser.reset
  284.  
  285.     # XXX For derived classes only -- enter literal mode (CDATA) till EOF
  286.     def setnomoretags(self):
  287.         pass
  288.  
  289.     # XXX For derived classes only -- enter literal mode (CDATA)
  290.     def setliteral(self, *args):
  291.         pass
  292.  
  293.     # Interface -- handle the remaining data
  294.     def close(self):
  295.         try:
  296.             self.parser.close()
  297.         finally:
  298.             self.parser = None
  299.  
  300.     # handlers, can be overridden
  301.     def handle_entityref(self, name): pass
  302.     def handle_charref(self, name):   pass
  303.     def handle_proc(self, name):      pass
  304.     def handle_special(self, name):   pass
  305.     def handle_data(self, data):      pass
  306.     def handle_cdata(self, data):     pass
  307.     def handle_comment(self, data):   pass
  308.     def unknown_starttag(self, tag, attrs): pass
  309.     def unknown_endtag(self, tag):    pass
  310.     def unknown_charref(self, ref):   pass
  311.     def unknown_entityref(self, ref): pass
  312.  
  313.  
  314. # pick a suitable parser
  315. if sgmlop:
  316.     SGMLParser = FastSGMLParser
  317. else:
  318.     SGMLParser = SlowSGMLParser
  319.  
  320. if __name__ == '__main__':
  321.     print "Ok"
  322.