home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pytho152.zip / emx / lib / python1.5 / xmllib.py < prev    next >
Text File  |  2000-08-10  |  33KB  |  887 lines

  1. # A parser for XML, using the derived class as static DTD.
  2. # Author: Sjoerd Mullender.
  3.  
  4. import re
  5. import string
  6.  
  7.  
  8. version = '0.2'
  9.  
  10. # Regular expressions used for parsing
  11.  
  12. _S = '[ \t\r\n]+'                       # white space
  13. _opS = '[ \t\r\n]*'                     # optional white space
  14. _Name = '[a-zA-Z_:][-a-zA-Z0-9._:]*'    # valid XML name
  15. _QStr = "(?:'[^']*'|\"[^\"]*\")"        # quoted XML string
  16. illegal = re.compile('[^\t\r\n -\176\240-\377]') # illegal chars in content
  17. interesting = re.compile('[]&<]')
  18.  
  19. amp = re.compile('&')
  20. ref = re.compile('&(' + _Name + '|#[0-9]+|#x[0-9a-fA-F]+)[^-a-zA-Z0-9._:]')
  21. entityref = re.compile('&(?P<name>' + _Name + ')[^-a-zA-Z0-9._:]')
  22. charref = re.compile('&#(?P<char>[0-9]+[^0-9]|x[0-9a-fA-F]+[^0-9a-fA-F])')
  23. space = re.compile(_S + '$')
  24. newline = re.compile('\n')
  25.  
  26. attrfind = re.compile(
  27.     _S + '(?P<name>' + _Name + ')'
  28.     '(' + _opS + '=' + _opS +
  29.     '(?P<value>'+_QStr+'|[-a-zA-Z0-9.:+*%?!()_#=~]+))?')
  30. starttagopen = re.compile('<' + _Name)
  31. starttagend = re.compile(_opS + '(?P<slash>/?)>')
  32. starttagmatch = re.compile('<(?P<tagname>'+_Name+')'
  33.                       '(?P<attrs>(?:'+attrfind.pattern+')*)'+
  34.                       starttagend.pattern)
  35. endtagopen = re.compile('</')
  36. endbracket = re.compile(_opS + '>')
  37. endbracketfind = re.compile('(?:[^>\'"]|'+_QStr+')*>')
  38. tagfind = re.compile(_Name)
  39. cdataopen = re.compile(r'<!\[CDATA\[')
  40. cdataclose = re.compile(r'\]\]>')
  41. # this matches one of the following:
  42. # SYSTEM SystemLiteral
  43. # PUBLIC PubidLiteral SystemLiteral
  44. _SystemLiteral = '(?P<%s>'+_QStr+')'
  45. _PublicLiteral = '(?P<%s>"[-\'()+,./:=?;!*#@$_%% \n\ra-zA-Z0-9]*"|' \
  46.                         "'[-()+,./:=?;!*#@$_%% \n\ra-zA-Z0-9]*')"
  47. _ExternalId = '(?:SYSTEM|' \
  48.                  'PUBLIC'+_S+_PublicLiteral%'pubid'+ \
  49.               ')'+_S+_SystemLiteral%'syslit'
  50. doctype = re.compile('<!DOCTYPE'+_S+'(?P<name>'+_Name+')'
  51.                      '(?:'+_S+_ExternalId+')?'+_opS)
  52. xmldecl = re.compile('<\?xml'+_S+
  53.                      'version'+_opS+'='+_opS+'(?P<version>'+_QStr+')'+
  54.                      '(?:'+_S+'encoding'+_opS+'='+_opS+
  55.                         "(?P<encoding>'[A-Za-z][-A-Za-z0-9._]*'|"
  56.                         '"[A-Za-z][-A-Za-z0-9._]*"))?'
  57.                      '(?:'+_S+'standalone'+_opS+'='+_opS+
  58.                         '(?P<standalone>\'(?:yes|no)\'|"(?:yes|no)"))?'+
  59.                      _opS+'\?>')
  60. procopen = re.compile(r'<\?(?P<proc>' + _Name + ')' + _opS)
  61. procclose = re.compile(_opS + r'\?>')
  62. commentopen = re.compile('<!--')
  63. commentclose = re.compile('-->')
  64. doubledash = re.compile('--')
  65. attrtrans = string.maketrans(' \r\n\t', '    ')
  66.  
  67. # definitions for XML namespaces
  68. _NCName = '[a-zA-Z_][-a-zA-Z0-9._]*'    # XML Name, minus the ":"
  69. ncname = re.compile(_NCName + '$')
  70. qname = re.compile('(?:(?P<prefix>' + _NCName + '):)?' # optional prefix
  71.                    '(?P<local>' + _NCName + ')$')
  72.  
  73. xmlns = re.compile('xmlns(?::(?P<ncname>'+_NCName+'))?$')
  74.  
  75. # XML parser base class -- find tags and call handler functions.
  76. # Usage: p = XMLParser(); p.feed(data); ...; p.close().
  77. # The dtd is defined by deriving a class which defines methods with
  78. # special names to handle tags: start_foo and end_foo to handle <foo>
  79. # and </foo>, respectively.  The data between tags is passed to the
  80. # parser by calling self.handle_data() with some data as argument (the
  81. # data may be split up in arbutrary chunks).  Entity references are
  82. # passed by calling self.handle_entityref() with the entity reference
  83. # as argument.
  84.  
  85. class XMLParser:
  86.     attributes = {}                     # default, to be overridden
  87.     elements = {}                       # default, to be overridden
  88.  
  89.     # Interface -- initialize and reset this instance
  90.     def __init__(self):
  91.         self.__fixed = 0
  92.         self.reset()
  93.  
  94.     def __fixelements(self):
  95.         self.__fixed = 1
  96.         self.elements = {}
  97.         self.__fixdict(self.__dict__)
  98.         self.__fixclass(self.__class__)
  99.  
  100.     def __fixclass(self, kl):
  101.         self.__fixdict(kl.__dict__)
  102.         for k in kl.__bases__:
  103.             self.__fixclass(k)
  104.  
  105.     def __fixdict(self, dict):
  106.         for key in dict.keys():
  107.             if key[:6] == 'start_':
  108.                 tag = key[6:]
  109.                 start, end = self.elements.get(tag, (None, None))
  110.                 if start is None:
  111.                     self.elements[tag] = getattr(self, key), end
  112.             elif key[:4] == 'end_':
  113.                 tag = key[4:]
  114.                 start, end = self.elements.get(tag, (None, None))
  115.                 if end is None:
  116.                     self.elements[tag] = start, getattr(self, key)
  117.  
  118.     # Interface -- reset this instance.  Loses all unprocessed data
  119.     def reset(self):
  120.         self.rawdata = ''
  121.         self.stack = []
  122.         self.nomoretags = 0
  123.         self.literal = 0
  124.         self.lineno = 1
  125.         self.__at_start = 1
  126.         self.__seen_doctype = None
  127.         self.__seen_starttag = 0
  128.         self.__use_namespaces = 0
  129.         self.__namespaces = {'xml':None}   # xml is implicitly declared
  130.         # backward compatipibility hack: if elements not overridden,
  131.         # fill it in ourselves
  132.         if self.elements is XMLParser.elements:
  133.             self.__fixelements()
  134.  
  135.     # For derived classes only -- enter literal mode (CDATA) till EOF
  136.     def setnomoretags(self):
  137.         self.nomoretags = self.literal = 1
  138.  
  139.     # For derived classes only -- enter literal mode (CDATA)
  140.     def setliteral(self, *args):
  141.         self.literal = 1
  142.  
  143.     # Interface -- feed some data to the parser.  Call this as
  144.     # often as you want, with as little or as much text as you
  145.     # want (may include '\n').  (This just saves the text, all the
  146.     # processing is done by goahead().)
  147.     def feed(self, data):
  148.         self.rawdata = self.rawdata + data
  149.         self.goahead(0)
  150.  
  151.     # Interface -- handle the remaining data
  152.     def close(self):
  153.         self.goahead(1)
  154.         if self.__fixed:
  155.             self.__fixed = 0
  156.             # remove self.elements so that we don't leak
  157.             del self.elements
  158.  
  159.     # Interface -- translate references
  160.     def translate_references(self, data, all = 1):
  161.         i = 0
  162.         while 1:
  163.             res = amp.search(data, i)
  164.             if res is None:
  165.                 return data
  166.             res = ref.match(data, res.start(0))
  167.             if res is None:
  168.                 self.syntax_error("bogus `&'")
  169.                 i =i+1
  170.                 continue
  171.             i = res.end(0)
  172.             if data[i - 1] != ';':
  173.                 self.syntax_error("`;' missing after entity/char reference")
  174.                 i = i-1
  175.             str = res.group(1)
  176.             pre = data[:res.start(0)]
  177.             post = data[i:]
  178.             if str[0] == '#':
  179.                 if str[1] == 'x':
  180.                     str = chr(string.atoi(str[2:], 16))
  181.                 else:
  182.                     str = chr(string.atoi(str[1:]))
  183.                 data = pre + str + post
  184.                 i = res.start(0)+len(str)
  185.             elif all:
  186.                 if self.entitydefs.has_key(str):
  187.                     data = pre + self.entitydefs[str] + post
  188.                     i = res.start(0)    # rescan substituted text
  189.                 else:
  190.                     self.syntax_error("reference to unknown entity `&%s;'" % str)
  191.                     # can't do it, so keep the entity ref in
  192.                     data = pre + '&' + str + ';' + post
  193.                     i = res.start(0) + len(str) + 2
  194.             else:
  195.                 # just translating character references
  196.                 pass                    # i is already postioned correctly
  197.  
  198.     # Internal -- handle data as far as reasonable.  May leave state
  199.     # and data to be processed by a subsequent call.  If 'end' is
  200.     # true, force handling all data as if followed by EOF marker.
  201.     def goahead(self, end):
  202.         rawdata = self.rawdata
  203.         i = 0
  204.         n = len(rawdata)
  205.         while i < n:
  206.             if i > 0:
  207.                 self.__at_start = 0
  208.             if self.nomoretags:
  209.                 data = rawdata[i:n]
  210.                 self.handle_data(data)
  211.                 self.lineno = self.lineno + string.count(data, '\n')
  212.                 i = n
  213.                 break
  214.             res = interesting.search(rawdata, i)
  215.             if res:
  216.                     j = res.start(0)
  217.             else:
  218.                     j = n
  219.             if i < j:
  220.                 data = rawdata[i:j]
  221.                 if self.__at_start and space.match(data) is None:
  222.                     self.syntax_error('illegal data at start of file')
  223.                 self.__at_start = 0
  224.                 if not self.stack and space.match(data) is None:
  225.                     self.syntax_error('data not in content')
  226.                 if illegal.search(data):
  227.                     self.syntax_error('illegal character in content')
  228.                 self.handle_data(data)
  229.                 self.lineno = self.lineno + string.count(data, '\n')
  230.             i = j
  231.             if i == n: break
  232.             if rawdata[i] == '<':
  233.                 if starttagopen.match(rawdata, i):
  234.                     if self.literal:
  235.                         data = rawdata[i]
  236.                         self.handle_data(data)
  237.                         self.lineno = self.lineno + string.count(data, '\n')
  238.                         i = i+1
  239.                         continue
  240.                     k = self.parse_starttag(i)
  241.                     if k < 0: break
  242.                     self.__seen_starttag = 1
  243.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  244.                     i = k
  245.                     continue
  246.                 if endtagopen.match(rawdata, i):
  247.                     k = self.parse_endtag(i)
  248.                     if k < 0: break
  249.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  250.                     i =  k
  251.                     continue
  252.                 if commentopen.match(rawdata, i):
  253.                     if self.literal:
  254.                         data = rawdata[i]
  255.                         self.handle_data(data)
  256.                         self.lineno = self.lineno + string.count(data, '\n')
  257.                         i = i+1
  258.                         continue
  259.                     k = self.parse_comment(i)
  260.                     if k < 0: break
  261.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  262.                     i = k
  263.                     continue
  264.                 if cdataopen.match(rawdata, i):
  265.                     k = self.parse_cdata(i)
  266.                     if k < 0: break
  267.                     self.lineno = self.lineno + string.count(rawdata[i:i], '\n')
  268.                     i = k
  269.                     continue
  270.                 res = xmldecl.match(rawdata, i)
  271.                 if res:
  272.                     if not self.__at_start:
  273.                         self.syntax_error("<?xml?> declaration not at start of document")
  274.                     version, encoding, standalone = res.group('version',
  275.                                                               'encoding',
  276.                                                               'standalone')
  277.                     if version[1:-1] != '1.0':
  278.                         raise RuntimeError, 'only XML version 1.0 supported'
  279.                     if encoding: encoding = encoding[1:-1]
  280.                     if standalone: standalone = standalone[1:-1]
  281.                     self.handle_xml(encoding, standalone)
  282.                     i = res.end(0)
  283.                     continue
  284.                 res = procopen.match(rawdata, i)
  285.                 if res:
  286.                     k = self.parse_proc(i)
  287.                     if k < 0: break
  288.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  289.                     i = k
  290.                     continue
  291.                 res = doctype.match(rawdata, i)
  292.                 if res:
  293.                     if self.literal:
  294.                         data = rawdata[i]
  295.                         self.handle_data(data)
  296.                         self.lineno = self.lineno + string.count(data, '\n')
  297.                         i = i+1
  298.                         continue
  299.                     if self.__seen_doctype:
  300.                         self.syntax_error('multiple DOCTYPE elements')
  301.                     if self.__seen_starttag:
  302.                         self.syntax_error('DOCTYPE not at beginning of document')
  303.                     k = self.parse_doctype(res)
  304.                     if k < 0: break
  305.                     self.__seen_doctype = res.group('name')
  306.                     self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
  307.                     i = k
  308.                     continue
  309.             elif rawdata[i] == '&':
  310.                 if self.literal:
  311.                     data = rawdata[i]
  312.                     self.handle_data(data)
  313.                     i = i+1
  314.                     continue
  315.                 res = charref.match(rawdata, i)
  316.                 if res is not None:
  317.                     i = res.end(0)
  318.                     if rawdata[i-1] != ';':
  319.                         self.syntax_error("`;' missing in charref")
  320.                         i = i-1
  321.                     if not self.stack:
  322.                         self.syntax_error('data not in content')
  323.                     self.handle_charref(res.group('char')[:-1])
  324.                     self.lineno = self.lineno + string.count(res.group(0), '\n')
  325.                     continue
  326.                 res = entityref.match(rawdata, i)
  327.                 if res is not None:
  328.                     i = res.end(0)
  329.                     if rawdata[i-1] != ';':
  330.                         self.syntax_error("`;' missing in entityref")
  331.                         i = i-1
  332.                     name = res.group('name')
  333.                     if self.entitydefs.has_key(name):
  334.                         self.rawdata = rawdata = rawdata[:res.start(0)] + self.entitydefs[name] + rawdata[i:]
  335.                         n = len(rawdata)
  336.                         i = res.start(0)
  337.                     else:
  338.                         self.syntax_error("reference to unknown entity `&%s;'" % name)
  339.                         self.unknown_entityref(name)
  340.                     self.lineno = self.lineno + string.count(res.group(0), '\n')
  341.                     continue
  342.             elif rawdata[i] == ']':
  343.                 if self.literal:
  344.                     data = rawdata[i]
  345.                     self.handle_data(data)
  346.                     i = i+1
  347.                     continue
  348.                 if n-i < 3:
  349.                     break
  350.                 if cdataclose.match(rawdata, i):
  351.                     self.syntax_error("bogus `]]>'")
  352.                 self.handle_data(rawdata[i])
  353.                 i = i+1
  354.                 continue
  355.             else:
  356.                 raise RuntimeError, 'neither < nor & ??'
  357.             # We get here only if incomplete matches but
  358.             # nothing else
  359.             break
  360.         # end while
  361.         if i > 0:
  362.             self.__at_start = 0
  363.         if end and i < n:
  364.             data = rawdata[i]
  365.             self.syntax_error("bogus `%s'" % data)
  366.             if illegal.search(data):
  367.                 self.syntax_error('illegal character in content')
  368.             self.handle_data(data)
  369.             self.lineno = self.lineno + string.count(data, '\n')
  370.             self.rawdata = rawdata[i+1:]
  371.             return self.goahead(end)
  372.         self.rawdata = rawdata[i:]
  373.         if end:
  374.             if not self.__seen_starttag:
  375.                 self.syntax_error('no elements in file')
  376.             if self.stack:
  377.                 self.syntax_error('missing end tags')
  378.                 while self.stack:
  379.                     self.finish_endtag(self.stack[-1][0])
  380.  
  381.     # Internal -- parse comment, return length or -1 if not terminated
  382.     def parse_comment(self, i):
  383.         rawdata = self.rawdata
  384.         if rawdata[i:i+4] <> '<!--':
  385.             raise RuntimeError, 'unexpected call to handle_comment'
  386.         res = commentclose.search(rawdata, i+4)
  387.         if res is None:
  388.             return -1
  389.         if doubledash.search(rawdata, i+4, res.start(0)):
  390.             self.syntax_error("`--' inside comment")
  391.         if rawdata[res.start(0)-1] == '-':
  392.             self.syntax_error('comment cannot end in three dashes')
  393.         if illegal.search(rawdata, i+4, res.start(0)):
  394.             self.syntax_error('illegal character in comment')
  395.         self.handle_comment(rawdata[i+4: res.start(0)])
  396.         return res.end(0)
  397.  
  398.     # Internal -- handle DOCTYPE tag, return length or -1 if not terminated
  399.     def parse_doctype(self, res):
  400.         rawdata = self.rawdata
  401.         n = len(rawdata)
  402.         name = res.group('name')
  403.         pubid, syslit = res.group('pubid', 'syslit')
  404.         if pubid is not None:
  405.             pubid = pubid[1:-1]         # remove quotes
  406.             pubid = string.join(string.split(pubid)) # normalize
  407.         if syslit is not None: syslit = syslit[1:-1] # remove quotes
  408.         j = k = res.end(0)
  409.         if k >= n:
  410.             return -1
  411.         if rawdata[k] == '[':
  412.             level = 0
  413.             k = k+1
  414.             dq = sq = 0
  415.             while k < n:
  416.                 c = rawdata[k]
  417.                 if not sq and c == '"':
  418.                     dq = not dq
  419.                 elif not dq and c == "'":
  420.                     sq = not sq
  421.                 elif sq or dq:
  422.                     pass
  423.                 elif level <= 0 and c == ']':
  424.                     res = endbracket.match(rawdata, k+1)
  425.                     if res is None:
  426.                         return -1
  427.                     self.handle_doctype(name, pubid, syslit, rawdata[j+1:k])
  428.                     return res.end(0)
  429.                 elif c == '<':
  430.                     level = level + 1
  431.                 elif c == '>':
  432.                     level = level - 1
  433.                     if level < 0:
  434.                         self.syntax_error("bogus `>' in DOCTYPE")
  435.                 k = k+1
  436.         res = endbracketfind.match(rawdata, k)
  437.         if res is None:
  438.             return -1
  439.         if endbracket.match(rawdata, k) is None:
  440.             self.syntax_error('garbage in DOCTYPE')
  441.         self.handle_doctype(name, pubid, syslit, None)
  442.         return res.end(0)
  443.  
  444.     # Internal -- handle CDATA tag, return length or -1 if not terminated
  445.     def parse_cdata(self, i):
  446.         rawdata = self.rawdata
  447.         if rawdata[i:i+9] <> '<![CDATA[':
  448.             raise RuntimeError, 'unexpected call to parse_cdata'
  449.         res = cdataclose.search(rawdata, i+9)
  450.         if res is None:
  451.             return -1
  452.         if illegal.search(rawdata, i+9, res.start(0)):
  453.             self.syntax_error('illegal character in CDATA')
  454.         if not self.stack:
  455.             self.syntax_error('CDATA not in content')
  456.         self.handle_cdata(rawdata[i+9:res.start(0)])
  457.         return res.end(0)
  458.  
  459.     __xml_namespace_attributes = {'ns':None, 'src':None, 'prefix':None}
  460.     # Internal -- handle a processing instruction tag
  461.     def parse_proc(self, i):
  462.         rawdata = self.rawdata
  463.         end = procclose.search(rawdata, i)
  464.         if end is None:
  465.             return -1
  466.         j = end.start(0)
  467.         if illegal.search(rawdata, i+2, j):
  468.             self.syntax_error('illegal character in processing instruction')
  469.         res = tagfind.match(rawdata, i+2)
  470.         if res is None:
  471.             raise RuntimeError, 'unexpected call to parse_proc'
  472.         k = res.end(0)
  473.         name = res.group(0)
  474.         if name == 'xml:namespace':
  475.             self.syntax_error('old-fashioned namespace declaration')
  476.             self.__use_namespaces = -1
  477.             # namespace declaration
  478.             # this must come after the <?xml?> declaration (if any)
  479.             # and before the <!DOCTYPE> (if any).
  480.             if self.__seen_doctype or self.__seen_starttag:
  481.                 self.syntax_error('xml:namespace declaration too late in document')
  482.             attrdict, namespace, k = self.parse_attributes(name, k, j)
  483.             if namespace:
  484.                 self.syntax_error('namespace declaration inside namespace declaration')
  485.             for attrname in attrdict.keys():
  486.                 if not self.__xml_namespace_attributes.has_key(attrname):
  487.                     self.syntax_error("unknown attribute `%s' in xml:namespace tag" % attrname)
  488.             if not attrdict.has_key('ns') or not attrdict.has_key('prefix'):
  489.                 self.syntax_error('xml:namespace without required attributes')
  490.             prefix = attrdict.get('prefix')
  491.             if ncname.match(prefix) is None:
  492.                 self.syntax_error('xml:namespace illegal prefix value')
  493.                 return end.end(0)
  494.             if self.__namespaces.has_key(prefix):
  495.                 self.syntax_error('xml:namespace prefix not unique')
  496.             self.__namespaces[prefix] = attrdict['ns']
  497.         else:
  498.             if string.find(string.lower(name), 'xml') >= 0:
  499.                 self.syntax_error('illegal processing instruction target name')
  500.             self.handle_proc(name, rawdata[k:j])
  501.         return end.end(0)
  502.  
  503.     # Internal -- parse attributes between i and j
  504.     def parse_attributes(self, tag, i, j):
  505.         rawdata = self.rawdata
  506.         attrdict = {}
  507.         namespace = {}
  508.         while i < j:
  509.             res = attrfind.match(rawdata, i)
  510.             if res is None:
  511.                 break
  512.             attrname, attrvalue = res.group('name', 'value')
  513.             i = res.end(0)
  514.             if attrvalue is None:
  515.                 self.syntax_error("no value specified for attribute `%s'" % attrname)
  516.                 attrvalue = attrname
  517.             elif attrvalue[:1] == "'" == attrvalue[-1:] or \
  518.                  attrvalue[:1] == '"' == attrvalue[-1:]:
  519.                 attrvalue = attrvalue[1:-1]
  520.             else:
  521.                 self.syntax_error("attribute `%s' value not quoted" % attrname)
  522.             res = xmlns.match(attrname)
  523.             if res is not None:
  524.                 # namespace declaration
  525.                 ncname = res.group('ncname')
  526.                 namespace[ncname or ''] = attrvalue or None
  527.                 if not self.__use_namespaces:
  528.                     self.__use_namespaces = len(self.stack)+1
  529.                 continue
  530.             if '<' in attrvalue:
  531.                 self.syntax_error("`<' illegal in attribute value")
  532.             if attrdict.has_key(attrname):
  533.                 self.syntax_error("attribute `%s' specified twice" % attrname)
  534.             attrvalue = string.translate(attrvalue, attrtrans)
  535.             attrdict[attrname] = self.translate_references(attrvalue)
  536.         return attrdict, namespace, i
  537.  
  538.     # Internal -- handle starttag, return length or -1 if not terminated
  539.     def parse_starttag(self, i):
  540.         rawdata = self.rawdata
  541.         # i points to start of tag
  542.         end = endbracketfind.match(rawdata, i+1)
  543.         if end is None:
  544.             return -1
  545.         tag = starttagmatch.match(rawdata, i)
  546.         if tag is None or tag.end(0) != end.end(0):
  547.             self.syntax_error('garbage in starttag')
  548.             return end.end(0)
  549.         nstag = tagname = tag.group('tagname')
  550.         if not self.__seen_starttag and self.__seen_doctype and \
  551.            tagname != self.__seen_doctype:
  552.             self.syntax_error('starttag does not match DOCTYPE')
  553.         if self.__seen_starttag and not self.stack:
  554.             self.syntax_error('multiple elements on top level')
  555.         k, j = tag.span('attrs')
  556.         attrdict, nsdict, k = self.parse_attributes(tagname, k, j)
  557.         self.stack.append((tagname, nsdict, nstag))
  558.         if self.__use_namespaces:
  559.             res = qname.match(tagname)
  560.         else:
  561.             res = None
  562.         if res is not None:
  563.             prefix, nstag = res.group('prefix', 'local')
  564.             if prefix is None:
  565.                 prefix = ''
  566.             ns = None
  567.             for t, d, nst in self.stack:
  568.                 if d.has_key(prefix):
  569.                     ns = d[prefix]
  570.             if ns is None and prefix != '':
  571.                 ns = self.__namespaces.get(prefix)
  572.             if ns is not None:
  573.                 nstag = ns + ' ' + nstag
  574.             elif prefix != '':
  575.                 nstag = prefix + ':' + nstag # undo split
  576.             self.stack[-1] = tagname, nsdict, nstag
  577.         # translate namespace of attributes
  578.         if self.__use_namespaces:
  579.             nattrdict = {}
  580.             for key, val in attrdict.items():
  581.                 res = qname.match(key)
  582.                 if res is not None:
  583.                     aprefix, key = res.group('prefix', 'local')
  584.                     if aprefix is None:
  585.                         aprefix = ''
  586.                     ans = None
  587.                     for t, d, nst in self.stack:
  588.                         if d.has_key(aprefix):
  589.                             ans = d[aprefix]
  590.                     if ans is None and aprefix != '':
  591.                         ans = self.__namespaces.get(aprefix)
  592.                     if ans is not None:
  593.                         key = ans + ' ' + key
  594.                     elif aprefix != '':
  595.                         key = aprefix + ':' + key
  596.                     elif ns is not None:
  597.                         key = ns + ' ' + key
  598.                 nattrdict[key] = val
  599.             attrdict = nattrdict
  600.         attributes = self.attributes.get(nstag)
  601.         if attributes is not None:
  602.             for key in attrdict.keys():
  603.                 if not attributes.has_key(key):
  604.                     self.syntax_error("unknown attribute `%s' in tag `%s'" % (key, tagname))
  605.             for key, val in attributes.items():
  606.                 if val is not None and not attrdict.has_key(key):
  607.                     attrdict[key] = val
  608.         method = self.elements.get(nstag, (None, None))[0]
  609.         self.finish_starttag(nstag, attrdict, method)
  610.         if tag.group('slash') == '/':
  611.             self.finish_endtag(tagname)
  612.         return tag.end(0)
  613.  
  614.     # Internal -- parse endtag
  615.     def parse_endtag(self, i):
  616.         rawdata = self.rawdata
  617.         end = endbracketfind.match(rawdata, i+1)
  618.         if end is None:
  619.             return -1
  620.         res = tagfind.match(rawdata, i+2)
  621.         if res is None:
  622.             if self.literal:
  623.                 self.handle_data(rawdata[i])
  624.                 return i+1
  625.             self.syntax_error('no name specified in end tag')
  626.             tag = ''
  627.             k = i+2
  628.         else:
  629.             tag = res.group(0)
  630.             if self.literal:
  631.                 if not self.stack or tag != self.stack[-1][0]:
  632.                     self.handle_data(rawdata[i])
  633.                     return i+1
  634.                 self.literal = 0
  635.             k = res.end(0)
  636.         if endbracket.match(rawdata, k) is None:
  637.             self.syntax_error('garbage in end tag')
  638.         self.finish_endtag(tag)
  639.         return end.end(0)
  640.  
  641.     # Internal -- finish processing of start tag
  642.     def finish_starttag(self, tagname, attrdict, method):
  643.         if method is not None:
  644.             self.handle_starttag(tagname, method, attrdict)
  645.         else:
  646.             self.unknown_starttag(tagname, attrdict)
  647.  
  648.     # Internal -- finish processing of end tag
  649.     def finish_endtag(self, tag):
  650.         if not tag:
  651.             self.syntax_error('name-less end tag')
  652.             found = len(self.stack) - 1
  653.             if found < 0:
  654.                 self.unknown_endtag(tag)
  655.                 return
  656.         else:
  657.             found = -1
  658.             for i in range(len(self.stack)):
  659.                 if tag == self.stack[i][0]:
  660.                     found = i
  661.             if found == -1:
  662.                 self.syntax_error('unopened end tag')
  663.                 method = self.elements.get(tag, (None, None))[1]
  664.                 if method is not None:
  665.                     self.handle_endtag(tag, method)
  666.                 else:
  667.                     self.unknown_endtag(tag)
  668.                 return
  669.         while len(self.stack) > found:
  670.             if found < len(self.stack) - 1:
  671.                 self.syntax_error('missing close tag for %s' % self.stack[-1][2])
  672.             nstag = self.stack[-1][2]
  673.             method = self.elements.get(nstag, (None, None))[1]
  674.             if method is not None:
  675.                 self.handle_endtag(nstag, method)
  676.             else:
  677.                 self.unknown_endtag(nstag)
  678.             if self.__use_namespaces == len(self.stack):
  679.                 self.__use_namespaces = 0
  680.             del self.stack[-1]
  681.  
  682.     # Overridable -- handle xml processing instruction
  683.     def handle_xml(self, encoding, standalone):
  684.         pass
  685.  
  686.     # Overridable -- handle DOCTYPE
  687.     def handle_doctype(self, tag, pubid, syslit, data):
  688.         pass
  689.  
  690.     # Overridable -- handle start tag
  691.     def handle_starttag(self, tag, method, attrs):
  692.         method(attrs)
  693.  
  694.     # Overridable -- handle end tag
  695.     def handle_endtag(self, tag, method):
  696.         method()
  697.  
  698.     # Example -- handle character reference, no need to override
  699.     def handle_charref(self, name):
  700.         try:
  701.             if name[0] == 'x':
  702.                 n = string.atoi(name[1:], 16)
  703.             else:
  704.                 n = string.atoi(name)
  705.         except string.atoi_error:
  706.             self.unknown_charref(name)
  707.             return
  708.         if not 0 <= n <= 255:
  709.             self.unknown_charref(name)
  710.             return
  711.         self.handle_data(chr(n))
  712.  
  713.     # Definition of entities -- derived classes may override
  714.     entitydefs = {'lt': '<',        # must use charref
  715.                   'gt': '>',
  716.                   'amp': '&',       # must use charref
  717.                   'quot': '"',
  718.                   'apos': ''',
  719.                   }
  720.  
  721.     # Example -- handle entity reference, no need to override
  722.     def handle_entityref(self, name):
  723.         table = self.entitydefs
  724.         if table.has_key(name):
  725.             self.handle_data(table[name])
  726.         else:
  727.             self.unknown_entityref(name)
  728.             return
  729.  
  730.     # Example -- handle data, should be overridden
  731.     def handle_data(self, data):
  732.         pass
  733.  
  734.     # Example -- handle cdata, could be overridden
  735.     def handle_cdata(self, data):
  736.         pass
  737.  
  738.     # Example -- handle comment, could be overridden
  739.     def handle_comment(self, data):
  740.         pass
  741.  
  742.     # Example -- handle processing instructions, could be overridden
  743.     def handle_proc(self, name, data):
  744.         pass
  745.  
  746.     # Example -- handle relatively harmless syntax errors, could be overridden
  747.     def syntax_error(self, message):
  748.         raise RuntimeError, 'Syntax error at line %d: %s' % (self.lineno, message)
  749.  
  750.     # To be overridden -- handlers for unknown objects
  751.     def unknown_starttag(self, tag, attrs): pass
  752.     def unknown_endtag(self, tag): pass
  753.     def unknown_charref(self, ref): pass
  754.     def unknown_entityref(self, ref): pass
  755.  
  756.  
  757. class TestXMLParser(XMLParser):
  758.  
  759.     def __init__(self):
  760.         self.testdata = ""
  761.         XMLParser.__init__(self)
  762.  
  763.     def handle_xml(self, encoding, standalone):
  764.         self.flush()
  765.         print 'xml: encoding =',encoding,'standalone =',standalone
  766.  
  767.     def handle_doctype(self, tag, pubid, syslit, data):
  768.         self.flush()
  769.         print 'DOCTYPE:',tag, `data`
  770.  
  771.     def handle_entity(self, name, strval, pubid, syslit, ndata):
  772.         self.flush()
  773.         print 'ENTITY:',`data`
  774.  
  775.     def handle_data(self, data):
  776.         self.testdata = self.testdata + data
  777.         if len(`self.testdata`) >= 70:
  778.             self.flush()
  779.  
  780.     def flush(self):
  781.         data = self.testdata
  782.         if data:
  783.             self.testdata = ""
  784.             print 'data:', `data`
  785.  
  786.     def handle_cdata(self, data):
  787.         self.flush()
  788.         print 'cdata:', `data`
  789.  
  790.     def handle_proc(self, name, data):
  791.         self.flush()
  792.         print 'processing:',name,`data`
  793.  
  794.     def handle_comment(self, data):
  795.         self.flush()
  796.         r = `data`
  797.         if len(r) > 68:
  798.             r = r[:32] + '...' + r[-32:]
  799.         print 'comment:', r
  800.  
  801.     def syntax_error(self, message):
  802.         print 'error at line %d:' % self.lineno, message
  803.  
  804.     def unknown_starttag(self, tag, attrs):
  805.         self.flush()
  806.         if not attrs:
  807.             print 'start tag: <' + tag + '>'
  808.         else:
  809.             print 'start tag: <' + tag,
  810.             for name, value in attrs.items():
  811.                 print name + '=' + '"' + value + '"',
  812.             print '>'
  813.  
  814.     def unknown_endtag(self, tag):
  815.         self.flush()
  816.         print 'end tag: </' + tag + '>'
  817.  
  818.     def unknown_entityref(self, ref):
  819.         self.flush()
  820.         print '*** unknown entity ref: &' + ref + ';'
  821.  
  822.     def unknown_charref(self, ref):
  823.         self.flush()
  824.         print '*** unknown char ref: &#' + ref + ';'
  825.  
  826.     def close(self):
  827.         XMLParser.close(self)
  828.         self.flush()
  829.  
  830. def test(args = None):
  831.     import sys, getopt
  832.     from time import time
  833.  
  834.     if not args:
  835.         args = sys.argv[1:]
  836.  
  837.     opts, args = getopt.getopt(args, 'st')
  838.     klass = TestXMLParser
  839.     do_time = 0
  840.     for o, a in opts:
  841.         if o == '-s':
  842.             klass = XMLParser
  843.         elif o == '-t':
  844.             do_time = 1
  845.  
  846.     if args:
  847.         file = args[0]
  848.     else:
  849.         file = 'test.xml'
  850.  
  851.     if file == '-':
  852.         f = sys.stdin
  853.     else:
  854.         try:
  855.             f = open(file, 'r')
  856.         except IOError, msg:
  857.             print file, ":", msg
  858.             sys.exit(1)
  859.  
  860.     data = f.read()
  861.     if f is not sys.stdin:
  862.         f.close()
  863.  
  864.     x = klass()
  865.     t0 = time()
  866.     try:
  867.         if do_time:
  868.             x.feed(data)
  869.             x.close()
  870.         else:
  871.             for c in data:
  872.                 x.feed(c)
  873.             x.close()
  874.     except RuntimeError, msg:
  875.         t1 = time()
  876.         print msg
  877.         if do_time:
  878.             print 'total time: %g' % (t1-t0)
  879.         sys.exit(1)
  880.     t1 = time()
  881.     if do_time:
  882.         print 'total time: %g' % (t1-t0)
  883.  
  884.  
  885. if __name__ == '__main__':
  886.     test()
  887.