home *** CD-ROM | disk | FTP | other *** search
/ The Datafile PD-CD 5 / DATAFILE_PDCD5.iso / utilities / p / python / !Python / Lib / NetLib / py / sgmllib < prev    next >
Text File  |  1996-03-28  |  12KB  |  447 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.  
  11. import regex
  12. import string
  13.  
  14.  
  15. # Regular expressions used for parsing
  16.  
  17. interesting = regex.compile('[&<]')
  18. incomplete = regex.compile('&\([a-zA-Z][a-zA-Z0-9]*\|#[0-9]*\)?\|'
  19.                '<\([a-zA-Z][^<>]*\|'
  20.                   '/\([a-zA-Z][^<>]*\)?\|'
  21.                   '![^<>]*\)?')
  22.  
  23. entityref = regex.compile('&\([a-zA-Z][a-zA-Z0-9]*\)[^a-zA-Z0-9]')
  24. charref = regex.compile('&#\([0-9]+\)[^0-9]')
  25.  
  26. starttagopen = regex.compile('<[>a-zA-Z]')
  27. shorttagopen = regex.compile('<[a-zA-Z][a-zA-Z0-9]*/')
  28. shorttag = regex.compile('<\([a-zA-Z][a-zA-Z0-9]*\)/\([^/]*\)/')
  29. endtagopen = regex.compile('</[<>a-zA-Z]')
  30. endbracket = regex.compile('[<>]')
  31. special = regex.compile('<![^<>]*>')
  32. commentopen = regex.compile('<!--')
  33. commentclose = regex.compile('--[ \t\n]*>')
  34. tagfind = regex.compile('[a-zA-Z][a-zA-Z0-9]*')
  35. attrfind = regex.compile(
  36.     '[ \t\n]+\([a-zA-Z_][a-zA-Z_0-9]*\)'
  37.     '\([ \t\n]*=[ \t\n]*'
  38.     '\(\'[^\']*\'\|"[^"]*"\|[-a-zA-Z0-9./:+*%?!()_#=~]*\)\)?')
  39.  
  40.  
  41. # SGML parser base class -- find tags and call handler functions.
  42. # Usage: p = SGMLParser(); p.feed(data); ...; p.close().
  43. # The dtd is defined by deriving a class which defines methods
  44. # with special names to handle tags: start_foo and end_foo to handle
  45. # <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
  46. # (Tags are converted to lower case for this purpose.)  The data
  47. # between tags is passed to the parser by calling self.handle_data()
  48. # with some data as argument (the data may be split up in arbutrary
  49. # chunks).  Entity references are passed by calling
  50. # self.handle_entityref() with the entity reference as argument.
  51.  
  52. class SGMLParser:
  53.  
  54.     # Interface -- initialize and reset this instance
  55.     def __init__(self, verbose=0):
  56.     self.verbose = verbose
  57.     self.reset()
  58.  
  59.     # Interface -- reset this instance.  Loses all unprocessed data
  60.     def reset(self):
  61.     self.rawdata = ''
  62.     self.stack = []
  63.     self.lasttag = '???'
  64.     self.nomoretags = 0
  65.     self.literal = 0
  66.  
  67.     # For derived classes only -- enter literal mode (CDATA) till EOF
  68.     def setnomoretags(self):
  69.     self.nomoretags = self.literal = 1
  70.  
  71.     # For derived classes only -- enter literal mode (CDATA)
  72.     def setliteral(self, *args):
  73.     self.literal = 1
  74.  
  75.     # Interface -- feed some data to the parser.  Call this as
  76.     # often as you want, with as little or as much text as you
  77.     # want (may include '\n').  (This just saves the text, all the
  78.     # processing is done by goahead().)
  79.     def feed(self, data):
  80.     self.rawdata = self.rawdata + data
  81.     self.goahead(0)
  82.  
  83.     # Interface -- handle the remaining data
  84.     def close(self):
  85.     self.goahead(1)
  86.  
  87.     # Internal -- handle data as far as reasonable.  May leave state
  88.     # and data to be processed by a subsequent call.  If 'end' is
  89.     # true, force handling all data as if followed by EOF marker.
  90.     def goahead(self, end):
  91.     rawdata = self.rawdata
  92.     i = 0
  93.     n = len(rawdata)
  94.     while i < n:
  95.         if self.nomoretags:
  96.         self.handle_data(rawdata[i:n])
  97.         i = n
  98.         break
  99.         j = interesting.search(rawdata, i)
  100.         if j < 0: j = n
  101.         if i < j: self.handle_data(rawdata[i:j])
  102.         i = j
  103.         if i == n: break
  104.         if rawdata[i] == '<':
  105.         if starttagopen.match(rawdata, i) >= 0:
  106.             if self.literal:
  107.             self.handle_data(rawdata[i])
  108.             i = i+1
  109.             continue
  110.             k = self.parse_starttag(i)
  111.             if k < 0: break
  112.             i = k
  113.             continue
  114.         if endtagopen.match(rawdata, i) >= 0:
  115.             k = self.parse_endtag(i)
  116.             if k < 0: break
  117.             i =  k
  118.             self.literal = 0
  119.             continue
  120.         if commentopen.match(rawdata, i) >= 0:
  121.             if self.literal:
  122.             self.handle_data(rawdata[i])
  123.             i = i+1
  124.             continue
  125.             k = self.parse_comment(i)
  126.             if k < 0: break
  127.             i = i+k
  128.             continue
  129.         k = special.match(rawdata, i)
  130.         if k >= 0:
  131.             if self.literal:
  132.             self.handle_data(rawdata[i])
  133.             i = i+1
  134.             continue
  135.             i = i+k
  136.             continue
  137.         elif rawdata[i] == '&':
  138.         k = charref.match(rawdata, i)
  139.         if k >= 0:
  140.             k = i+k
  141.             if rawdata[k-1] != ';': k = k-1
  142.             name = charref.group(1)
  143.             self.handle_charref(name)
  144.             i = k
  145.             continue
  146.         k = entityref.match(rawdata, i)
  147.         if k >= 0:
  148.             k = i+k
  149.             if rawdata[k-1] != ';': k = k-1
  150.             name = entityref.group(1)
  151.             self.handle_entityref(name)
  152.             i = k
  153.             continue
  154.         else:
  155.         raise RuntimeError, 'neither < nor & ??'
  156.         # We get here only if incomplete matches but
  157.         # nothing else
  158.         k = incomplete.match(rawdata, i)
  159.         if k < 0:
  160.         self.handle_data(rawdata[i])
  161.         i = i+1
  162.         continue
  163.         j = i+k
  164.         if j == n:
  165.         break # Really incomplete
  166.         self.handle_data(rawdata[i:j])
  167.         i = j
  168.     # end while
  169.     if end and i < n:
  170.         self.handle_data(rawdata[i:n])
  171.         i = n
  172.     self.rawdata = rawdata[i:]
  173.     # XXX if end: check for empty stack
  174.  
  175.     # Internal -- parse comment, return length or -1 if not terminated
  176.     def parse_comment(self, i):
  177.     rawdata = self.rawdata
  178.     if rawdata[i:i+4] <> '<!--':
  179.         raise RuntimeError, 'unexpected call to handle_comment'
  180.     j = commentclose.search(rawdata, i+4)
  181.     if j < 0:
  182.         return -1
  183.     self.handle_comment(rawdata[i+4: j])
  184.     j = j+commentclose.match(rawdata, j)
  185.     return j-i
  186.  
  187.     # Internal -- handle starttag, return length or -1 if not terminated
  188.     def parse_starttag(self, i):
  189.     rawdata = self.rawdata
  190.     if shorttagopen.match(rawdata, i) >= 0:
  191.         # SGML shorthand: <tag/data/ == <tag>data</tag>
  192.         # XXX Can data contain &... (entity or char refs)?
  193.         # XXX Can data contain < or > (tag characters)?
  194.         # XXX Can there be whitespace before the first /?
  195.         j = shorttag.match(rawdata, i)
  196.         if j < 0:
  197.         return -1
  198.         tag, data = shorttag.group(1, 2)
  199.         tag = string.lower(tag)
  200.         self.finish_shorttag(tag, data)
  201.         k = i+j
  202.         if rawdata[k-1] == '<':
  203.         k = k-1
  204.         return k
  205.     # XXX The following should skip matching quotes (' or ")
  206.     j = endbracket.search(rawdata, i+1)
  207.     if j < 0:
  208.         return -1
  209.     # Now parse the data between i+1 and j into a tag and attrs
  210.     attrs = []
  211.     if rawdata[i:i+2] == '<>':
  212.         # SGML shorthand: <> == <last open tag seen>
  213.         k = j
  214.         tag = self.lasttag
  215.     else:
  216.         k = tagfind.match(rawdata, i+1)
  217.         if k < 0:
  218.         raise RuntimeError, 'unexpected call to parse_starttag'
  219.         k = i+1+k
  220.         tag = string.lower(rawdata[i+1:k])
  221.         self.lasttag = tag
  222.     while k < j:
  223.         l = attrfind.match(rawdata, k)
  224.         if l < 0: break
  225.         attrname, rest, attrvalue = attrfind.group(1, 2, 3)
  226.         if not rest:
  227.         attrvalue = attrname
  228.         elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
  229.          attrvalue[:1] == '"' == attrvalue[-1:]:
  230.         attrvalue = attrvalue[1:-1]
  231.         attrs.append((string.lower(attrname), attrvalue))
  232.         k = k + l
  233.     if rawdata[j] == '>':
  234.         j = j+1
  235.     self.finish_starttag(tag, attrs)
  236.     return j
  237.  
  238.     # Internal -- parse endtag
  239.     def parse_endtag(self, i):
  240.     rawdata = self.rawdata
  241.     j = endbracket.search(rawdata, i+1)
  242.     if j < 0:
  243.         return -1
  244.     tag = string.lower(string.strip(rawdata[i+2:j]))
  245.     if rawdata[j] == '>':
  246.         j = j+1
  247.     self.finish_endtag(tag)
  248.     return j
  249.  
  250.     # Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>)
  251.     def finish_shorttag(self, tag, data):
  252.     self.finish_starttag(tag, [])
  253.     self.handle_data(data)
  254.     self.finish_endtag(tag)
  255.  
  256.     # Internal -- finish processing of start tag
  257.     # Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag
  258.     def finish_starttag(self, tag, attrs):
  259.     try:
  260.         method = getattr(self, 'start_' + tag)
  261.     except AttributeError:
  262.         try:
  263.         method = getattr(self, 'do_' + tag)
  264.         except AttributeError:
  265.         self.unknown_starttag(tag, attrs)
  266.         return -1
  267.         else:
  268.         self.handle_starttag(tag, method, attrs)
  269.         return 0
  270.     else:
  271.         self.stack.append(tag)
  272.         self.handle_starttag(tag, method, attrs)
  273.         return 1
  274.  
  275.     # Internal -- finish processing of end tag
  276.     def finish_endtag(self, tag):
  277.     if not tag:
  278.         found = len(self.stack) - 1
  279.         if found < 0:
  280.         self.unknown_endtag(tag)
  281.         return
  282.     else:
  283.