home *** CD-ROM | disk | FTP | other *** search
/ Freelog 125 / Freelog_MarsAvril2015_No125.iso / Bureautique / OpenOffice / Apache_OpenOffice_4.1.1_Win_x86_install_fr.exe / openoffice1.cab / sgmllib.py < prev    next >
Text File  |  2014-07-29  |  18KB  |  554 lines

  1. """A parser for SGML, using the derived class as a 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).  RCDATA is
  9. # not supported at all.
  10.  
  11.  
  12. from warnings import warnpy3k
  13. warnpy3k("the sgmllib module has been removed in Python 3.0",
  14.          stacklevel=2)
  15. del warnpy3k
  16.  
  17. import markupbase
  18. import re
  19.  
  20. __all__ = ["SGMLParser", "SGMLParseError"]
  21.  
  22. # Regular expressions used for parsing
  23.  
  24. interesting = re.compile('[&<]')
  25. incomplete = re.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|'
  26.                            '<([a-zA-Z][^<>]*|'
  27.                               '/([a-zA-Z][^<>]*)?|'
  28.                               '![^<>]*)?')
  29.  
  30. entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
  31. charref = re.compile('&#([0-9]+)[^0-9]')
  32.  
  33. starttagopen = re.compile('<[>a-zA-Z]')
  34. shorttagopen = re.compile('<[a-zA-Z][-.a-zA-Z0-9]*/')
  35. shorttag = re.compile('<([a-zA-Z][-.a-zA-Z0-9]*)/([^/]*)/')
  36. piclose = re.compile('>')
  37. endbracket = re.compile('[<>]')
  38. tagfind = re.compile('[a-zA-Z][-_.a-zA-Z0-9]*')
  39. attrfind = re.compile(
  40.     r'\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)(\s*=\s*'
  41.     r'(\'[^\']*\'|"[^"]*"|[][\-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?')
  42.  
  43.  
  44. class SGMLParseError(RuntimeError):
  45.     """Exception raised for all parse errors."""
  46.     pass
  47.  
  48.  
  49. # SGML parser base class -- find tags and call handler functions.
  50. # Usage: p = SGMLParser(); p.feed(data); ...; p.close().
  51. # The dtd is defined by deriving a class which defines methods
  52. # with special names to handle tags: start_foo and end_foo to handle
  53. # <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
  54. # (Tags are converted to lower case for this purpose.)  The data
  55. # between tags is passed to the parser by calling self.handle_data()
  56. # with some data as argument (the data may be split up in arbitrary
  57. # chunks).  Entity references are passed by calling
  58. # self.handle_entityref() with the entity reference as argument.
  59.  
  60. class SGMLParser(markupbase.ParserBase):
  61.     # Definition of entities -- derived classes may override
  62.     entity_or_charref = re.compile('&(?:'
  63.       '([a-zA-Z][-.a-zA-Z0-9]*)|#([0-9]+)'
  64.       ')(;?)')
  65.  
  66.     def __init__(self, verbose=0):
  67.         """Initialize and reset this instance."""
  68.         self.verbose = verbose
  69.         self.reset()
  70.  
  71.     def reset(self):
  72.         """Reset this instance. Loses all unprocessed data."""
  73.         self.__starttag_text = None
  74.         self.rawdata = ''
  75.         self.stack = []
  76.         self.lasttag = '???'
  77.         self.nomoretags = 0
  78.         self.literal = 0
  79.         markupbase.ParserBase.reset(self)
  80.  
  81.     def setnomoretags(self):
  82.         """Enter literal mode (CDATA) till EOF.
  83.  
  84.         Intended for derived classes only.
  85.         """
  86.         self.nomoretags = self.literal = 1
  87.  
  88.     def setliteral(self, *args):
  89.         """Enter literal mode (CDATA).
  90.  
  91.         Intended for derived classes only.
  92.         """
  93.         self.literal = 1
  94.  
  95.     def feed(self, data):
  96.         """Feed some data to the parser.
  97.  
  98.         Call this as often as you want, with as little or as much text
  99.         as you want (may include '\n').  (This just saves the text,
  100.         all the processing is done by goahead().)
  101.         """
  102.  
  103.         self.rawdata = self.rawdata + data
  104.         self.goahead(0)
  105.  
  106.     def close(self):
  107.         """Handle the remaining data."""
  108.         self.goahead(1)
  109.  
  110.     def error(self, message):
  111.         raise SGMLParseError(message)
  112.  
  113.     # Internal -- handle data as far as reasonable.  May leave state
  114.     # and data to be processed by a subsequent call.  If 'end' is
  115.     # true, force handling all data as if followed by EOF marker.
  116.     def goahead(self, end):
  117.         rawdata = self.rawdata
  118.         i = 0
  119.         n = len(rawdata)
  120.         while i < n:
  121.             if self.nomoretags:
  122.                 self.handle_data(rawdata[i:n])
  123.                 i = n
  124.                 break
  125.             match = interesting.search(rawdata, i)
  126.             if match: j = match.start()
  127.             else: j = n
  128.             if i < j:
  129.                 self.handle_data(rawdata[i:j])
  130.             i = j
  131.             if i == n: break
  132.             if rawdata[i] == '<':
  133.                 if starttagopen.match(rawdata, i):
  134.                     if self.literal:
  135.                         self.handle_data(rawdata[i])
  136.                         i = i+1
  137.                         continue
  138.                     k = self.parse_starttag(i)
  139.                     if k < 0: break
  140.                     i = k
  141.                     continue
  142.                 if rawdata.startswith("</", i):
  143.                     k = self.parse_endtag(i)
  144.                     if k < 0: break
  145.                     i = k
  146.                     self.literal = 0
  147.                     continue
  148.                 if self.literal:
  149.                     if n > (i + 1):
  150.                         self.handle_data("<")
  151.                         i = i+1
  152.                     else:
  153.                         # incomplete
  154.                         break
  155.                     continue
  156.                 if rawdata.startswith("<!--", i):
  157.                         # Strictly speaking, a comment is --.*--
  158.                         # within a declaration tag <!...>.
  159.                         # This should be removed,
  160.                         # and comments handled only in parse_declaration.
  161.                     k = self.parse_comment(i)
  162.                     if k < 0: break
  163.                     i = k
  164.                     continue
  165.                 if rawdata.startswith("<?", i):
  166.                     k = self.parse_pi(i)
  167.                     if k < 0: break
  168.                     i = i+k
  169.                     continue
  170.                 if rawdata.startswith("<!", i):
  171.                     # This is some sort of declaration; in "HTML as
  172.                     # deployed," this should only be the document type
  173.                     # declaration ("<!DOCTYPE html...>").
  174.                     k = self.parse_declaration(i)
  175.                     if k < 0: break
  176.                     i = k
  177.                     continue
  178.             elif rawdata[i] == '&':
  179.                 if self.literal:
  180.                     self.handle_data(rawdata[i])
  181.                     i = i+1
  182.                     continue
  183.                 match = charref.match(rawdata, i)
  184.                 if match:
  185.                     name = match.group(1)
  186.                     self.handle_charref(name)
  187.                     i = match.end(0)
  188.                     if rawdata[i-1] != ';': i = i-1
  189.                     continue
  190.                 match = entityref.match(rawdata, i)
  191.                 if match:
  192.                     name = match.group(1)
  193.                     self.handle_entityref(name)
  194.                     i = match.end(0)
  195.                     if rawdata[i-1] != ';': i = i-1
  196.                     continue
  197.             else:
  198.                 self.error('neither < nor & ??')
  199.             # We get here only if incomplete matches but
  200.             # nothing else
  201.             match = incomplete.match(rawdata, i)
  202.             if not match:
  203.                 self.handle_data(rawdata[i])
  204.                 i = i+1
  205.                 continue
  206.             j = match.end(0)
  207.             if j == n:
  208.                 break # Really incomplete
  209.             self.handle_data(rawdata[i:j])
  210.             i = j
  211.         # end while
  212.         if end and i < n:
  213.             self.handle_data(rawdata[i:n])
  214.             i = n
  215.         self.rawdata = rawdata[i:]
  216.         # XXX if end: check for empty stack
  217.  
  218.     # Extensions for the DOCTYPE scanner:
  219.     _decl_otherchars = '='
  220.  
  221.     # Internal -- parse processing instr, return length or -1 if not terminated
  222.     def parse_pi(self, i):
  223.         rawdata = self.rawdata
  224.         if rawdata[i:i+2] != '<?':
  225.             self.error('unexpected call to parse_pi()')
  226.         match = piclose.search(rawdata, i+2)
  227.         if not match:
  228.             return -1
  229.         j = match.start(0)
  230.         self.handle_pi(rawdata[i+2: j])
  231.         j = match.end(0)
  232.         return j-i
  233.  
  234.     def get_starttag_text(self):
  235.         return self.__starttag_text
  236.  
  237.     # Internal -- handle starttag, return length or -1 if not terminated
  238.     def parse_starttag(self, i):
  239.         self.__starttag_text = None
  240.         start_pos = i
  241.         rawdata = self.rawdata
  242.         if shorttagopen.match(rawdata, i):
  243.             # SGML shorthand: <tag/data/ == <tag>data</tag>
  244.             # XXX Can data contain &... (entity or char refs)?
  245.             # XXX Can data contain < or > (tag characters)?
  246.             # XXX Can there be whitespace before the first /?
  247.             match = shorttag.match(rawdata, i)
  248.             if not match:
  249.                 return -1
  250.             tag, data = match.group(1, 2)
  251.             self.__starttag_text = '<%s/' % tag
  252.             tag = tag.lower()
  253.             k = match.end(0)
  254.             self.finish_shorttag(tag, data)
  255.             self.__starttag_text = rawdata[start_pos:match.end(1) + 1]
  256.             return k
  257.         # XXX The following should skip matching quotes (' or ")
  258.         # As a shortcut way to exit, this isn't so bad, but shouldn't
  259.         # be used to locate the actual end of the start tag since the
  260.         # < or > characters may be embedded in an attribute value.
  261.         match = endbracket.search(rawdata, i+1)
  262.         if not match:
  263.             return -1
  264.         j = match.start(0)
  265.         # Now parse the data between i+1 and j into a tag and attrs
  266.         attrs = []
  267.         if rawdata[i:i+2] == '<>':
  268.             # SGML shorthand: <> == <last open tag seen>
  269.             k = j
  270.             tag = self.lasttag
  271.         else:
  272.             match = tagfind.match(rawdata, i+1)
  273.             if not match:
  274.                 self.error('unexpected call to parse_starttag')
  275.             k = match.end(0)
  276.             tag = rawdata[i+1:k].lower()
  277.             self.lasttag = tag
  278.         while k < j:
  279.             match = attrfind.match(rawdata, k)
  280.             if not match: break
  281.             attrname, rest, attrvalue = match.group(1, 2, 3)
  282.             if not rest:
  283.                 attrvalue = attrname
  284.             else:
  285.                 if (attrvalue[:1] == "'" == attrvalue[-1:] or
  286.                     attrvalue[:1] == '"' == attrvalue[-1:]):
  287.                     # strip quotes
  288.                     attrvalue = attrvalue[1:-1]
  289.                 attrvalue = self.entity_or_charref.sub(
  290.                     self._convert_ref, attrvalue)
  291.             attrs.append((attrname.lower(), attrvalue))
  292.             k = match.end(0)
  293.         if rawdata[j] == '>':
  294.             j = j+1
  295.         self.__starttag_text = rawdata[start_pos:j]
  296.         self.finish_starttag(tag, attrs)
  297.         return j
  298.  
  299.     # Internal -- convert entity or character reference
  300.     def _convert_ref(self, match):
  301.         if match.group(2):
  302.             return self.convert_charref(match.group(2)) or \
  303.                 '&#%s%s' % match.groups()[1:]
  304.         elif match.group(3):
  305.             return self.convert_entityref(match.group(1)) or \
  306.                 '&%s;' % match.group(1)
  307.         else:
  308.             return '&%s' % match.group(1)
  309.  
  310.     # Internal -- parse endtag
  311.     def parse_endtag(self, i):
  312.         rawdata = self.rawdata
  313.         match = endbracket.search(rawdata, i+1)
  314.         if not match:
  315.             return -1
  316.         j = match.start(0)
  317.         tag = rawdata[i+2:j].strip().lower()
  318.         if rawdata[j] == '>':
  319.             j = j+1
  320.         self.finish_endtag(tag)
  321.         return j
  322.  
  323.     # Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>)
  324.     def finish_shorttag(self, tag, data):
  325.         self.finish_starttag(tag, [])
  326.         self.handle_data(data)
  327.         self.finish_endtag(tag)
  328.  
  329.     # Internal -- finish processing of start tag
  330.     # Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag
  331.     def finish_starttag(self, tag, attrs):
  332.         try:
  333.             method = getattr(self, 'start_' + tag)
  334.         except AttributeError:
  335.             try:
  336.                 method = getattr(self, 'do_' + tag)
  337.             except AttributeError:
  338.                 self.unknown_starttag(tag, attrs)
  339.                 return -1
  340.             else:
  341.                 self.handle_starttag(tag, method, attrs)
  342.                 return 0
  343.         else:
  344.             self.stack.append(tag)
  345.             self.handle_starttag(tag, method, attrs)
  346.             return 1
  347.  
  348.     # Internal -- finish processing of end tag
  349.     def finish_endtag(self, tag):
  350.         if not tag:
  351.             found = len(self.stack) - 1
  352.             if found < 0:
  353.                 self.unknown_endtag(tag)
  354.                 return
  355.         else:
  356.             if tag not in self.stack:
  357.                 try:
  358.                     method = getattr(self, 'end_' + tag)
  359.                 except AttributeError:
  360.                     self.unknown_endtag(tag)
  361.                 else:
  362.                     self.report_unbalanced(tag)
  363.                 return
  364.             found = len(self.stack)
  365.             for i in range(found):
  366.                 if self.stack[i] == tag: found = i
  367.         while len(self.stack) > found:
  368.             tag = self.stack[-1]
  369.             try:
  370.                 method = getattr(self, 'end_' + tag)
  371.             except AttributeError:
  372.                 method = None
  373.             if method:
  374.                 self.handle_endtag(tag, method)
  375.             else:
  376.                 self.unknown_endtag(tag)
  377.             del self.stack[-1]
  378.  
  379.     # Overridable -- handle start tag
  380.     def handle_starttag(self, tag, method, attrs):
  381.         method(attrs)
  382.  
  383.     # Overridable -- handle end tag
  384.     def handle_endtag(self, tag, method):
  385.         method()
  386.  
  387.     # Example -- report an unbalanced </...> tag.
  388.     def report_unbalanced(self, tag):
  389.         if self.verbose:
  390.             print '*** Unbalanced </' + tag + '>'
  391.             print '*** Stack:', self.stack
  392.  
  393.     def convert_charref(self, name):
  394.         """Convert character reference, may be overridden."""
  395.         try:
  396.             n = int(name)
  397.         except ValueError:
  398.             return
  399.         if not 0 <= n <= 127:
  400.             return
  401.         return self.convert_codepoint(n)
  402.  
  403.     def convert_codepoint(self, codepoint):
  404.         return chr(codepoint)
  405.  
  406.     def handle_charref(self, name):
  407.         """Handle character reference, no need to override."""
  408.         replacement = self.convert_charref(name)
  409.         if replacement is None:
  410.             self.unknown_charref(name)
  411.         else:
  412.             self.handle_data(replacement)
  413.  
  414.     # Definition of entities -- derived classes may override
  415.     entitydefs = \
  416.             {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': '\''}
  417.  
  418.     def convert_entityref(self, name):
  419.         """Convert entity references.
  420.  
  421.         As an alternative to overriding this method; one can tailor the
  422.         results by setting up the self.entitydefs mapping appropriately.
  423.         """
  424.         table = self.entitydefs
  425.         if name in table:
  426.             return table[name]
  427.         else:
  428.             return
  429.  
  430.     def handle_entityref(self, name):
  431.         """Handle entity references, no need to override."""
  432.         replacement = self.convert_entityref(name)
  433.         if replacement is None:
  434.             self.unknown_entityref(name)
  435.         else:
  436.             self.handle_data(replacement)
  437.  
  438.     # Example -- handle data, should be overridden
  439.     def handle_data(self, data):
  440.         pass
  441.  
  442.     # Example -- handle comment, could be overridden
  443.     def handle_comment(self, data):
  444.         pass
  445.  
  446.     # Example -- handle declaration, could be overridden
  447.     def handle_decl(self, decl):
  448.         pass
  449.  
  450.     # Example -- handle processing instruction, could be overridden
  451.     def handle_pi(self, data):
  452.         pass
  453.  
  454.     # To be overridden -- handlers for unknown objects
  455.     def unknown_starttag(self, tag, attrs): pass
  456.     def unknown_endtag(self, tag): pass
  457.     def unknown_charref(self, ref): pass
  458.     def unknown_entityref(self, ref): pass
  459.  
  460.  
  461. class TestSGMLParser(SGMLParser):
  462.  
  463.     def __init__(self, verbose=0):
  464.         self.testdata = ""
  465.         SGMLParser.__init__(self, verbose)
  466.  
  467.     def handle_data(self, data):
  468.         self.testdata = self.testdata + data
  469.         if len(repr(self.testdata)) >= 70:
  470.             self.flush()
  471.  
  472.     def flush(self):
  473.         data = self.testdata
  474.         if data:
  475.             self.testdata = ""
  476.             print 'data:', repr(data)
  477.  
  478.     def handle_comment(self, data):
  479.         self.flush()
  480.         r = repr(data)
  481.         if len(r) > 68:
  482.             r = r[:32] + '...' + r[-32:]
  483.         print 'comment:', r
  484.  
  485.     def unknown_starttag(self, tag, attrs):
  486.         self.flush()
  487.         if not attrs:
  488.             print 'start tag: <' + tag + '>'
  489.         else:
  490.             print 'start tag: <' + tag,
  491.             for name, value in attrs:
  492.                 print name + '=' + '"' + value + '"',
  493.             print '>'
  494.  
  495.     def unknown_endtag(self, tag):
  496.         self.flush()
  497.         print 'end tag: </' + tag + '>'
  498.  
  499.     def unknown_entityref(self, ref):
  500.         self.flush()
  501.         print '*** unknown entity ref: &' + ref + ';'
  502.  
  503.     def unknown_charref(self, ref):
  504.         self.flush()
  505.         print '*** unknown char ref: &#' + ref + ';'
  506.  
  507.     def unknown_decl(self, data):
  508.         self.flush()
  509.         print '*** unknown decl: [' + data + ']'
  510.  
  511.     def close(self):
  512.         SGMLParser.close(self)
  513.         self.flush()
  514.  
  515.  
  516. def test(args = None):
  517.     import sys
  518.  
  519.     if args is None:
  520.         args = sys.argv[1:]
  521.  
  522.     if args and args[0] == '-s':
  523.         args = args[1:]
  524.         klass = SGMLParser
  525.     else:
  526.         klass = TestSGMLParser
  527.  
  528.     if args:
  529.         file = args[0]
  530.     else:
  531.         file = 'test.html'
  532.  
  533.     if file == '-':
  534.         f = sys.stdin
  535.     else:
  536.         try:
  537.             f = open(file, 'r')
  538.         except IOError, msg:
  539.             print file, ":", msg
  540.             sys.exit(1)
  541.  
  542.     data = f.read()
  543.     if f is not sys.stdin:
  544.         f.close()
  545.  
  546.     x = klass()
  547.     for c in data:
  548.         x.feed(c)
  549.     x.close()
  550.  
  551.  
  552. if __name__ == '__main__':
  553.     test()
  554.