Edgewall Software

Changeset 185 for trunk/markup/input.py


Ignore:
Timestamp:
Aug 11, 2006, 6:34:35 PM (17 years ago)
Author:
cmlenz
Message:
  • Coalesce adjacent text events that the parsers would produce when text crossed the buffer boundaries. Fixes #26.
  • Fix handling of character and entity references in the HTML parser
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/markup/input.py

    r184 r185  
    1212# history and logs, available at http://markup.edgewall.org/log/.
    1313
     14from itertools import chain
    1415from xml.parsers import expat
    1516try:
     
    2223
    2324from markup.core import Attributes, Markup, QName, Stream
     25from markup.core import DOCTYPE, START, END, START_NS, END_NS, TEXT, \
     26                        START_CDATA, END_CDATA, PI, COMMENT
     27
     28__all__ = ['ParseError', 'XMLParser', 'XML', 'HTMLParser', 'HTML']
    2429
    2530
     
    8388        self._queue = []
    8489
     90    def parse(self):
     91        def _generate():
     92            try:
     93                bufsize = 4 * 1024 # 4K
     94                done = False
     95                while 1:
     96                    while not done and len(self._queue) == 0:
     97                        data = self.source.read(bufsize)
     98                        if data == '': # end of data
     99                            if hasattr(self, 'expat'):
     100                                self.expat.Parse('', True)
     101                                del self.expat # get rid of circular references
     102                            done = True
     103                        else:
     104                            self.expat.Parse(data, False)
     105                    for event in self._queue:
     106                        yield event
     107                    self._queue = []
     108                    if done:
     109                        break
     110            except expat.ExpatError, e:
     111                msg = str(e)
     112                if self.filename:
     113                    msg += ', in ' + self.filename
     114                raise ParseError(msg, self.filename, e.lineno, e.offset)
     115        return Stream(_generate()).filter(CoalesceFilter())
     116
    85117    def __iter__(self):
    86         try:
    87             bufsize = 4 * 1024 # 4K
    88             done = False
    89             while 1:
    90                 while not done and len(self._queue) == 0:
    91                     data = self.source.read(bufsize)
    92                     if data == '': # end of data
    93                         if hasattr(self, 'expat'):
    94                             self.expat.Parse('', True)
    95                             del self.expat # get rid of circular references
    96                         done = True
    97                     else:
    98                         self.expat.Parse(data, False)
    99                 for event in self._queue:
    100                     yield event
    101                 self._queue = []
    102                 if done:
    103                     break
    104         except expat.ExpatError, e:
    105             msg = str(e)
    106             if self.filename:
    107                 msg += ', in ' + self.filename
    108             raise ParseError(msg, self.filename, e.lineno, e.offset)
     118        return iter(self.parse())
    109119
    110120    def _enqueue(self, kind, data=None, pos=None):
    111121        if pos is None:
    112122            pos = self._getpos()
    113         if kind is Stream.TEXT:
     123        if kind is TEXT:
    114124            # Expat reports the *end* of the text event as current position. We
    115125            # try to fix that up here as much as possible. Unfortunately, the
     
    135145
    136146    def _handle_start(self, tag, attrib):
    137         self._enqueue(Stream.START, (QName(tag), Attributes(attrib.items())))
     147        self._enqueue(START, (QName(tag), Attributes(attrib.items())))
    138148
    139149    def _handle_end(self, tag):
    140         self._enqueue(Stream.END, QName(tag))
     150        self._enqueue(END, QName(tag))
    141151
    142152    def _handle_data(self, text):
    143         self._enqueue(Stream.TEXT, text)
     153        self._enqueue(TEXT, text)
    144154
    145155    def _handle_doctype(self, name, sysid, pubid, has_internal_subset):
    146         self._enqueue(Stream.DOCTYPE, (name, pubid, sysid))
     156        self._enqueue(DOCTYPE, (name, pubid, sysid))
    147157
    148158    def _handle_start_ns(self, prefix, uri):
    149         self._enqueue(Stream.START_NS, (prefix or '', uri))
     159        self._enqueue(START_NS, (prefix or '', uri))
    150160
    151161    def _handle_end_ns(self, prefix):
    152         self._enqueue(Stream.END_NS, prefix or '')
     162        self._enqueue(END_NS, prefix or '')
    153163
    154164    def _handle_start_cdata(self):
    155         self._enqueue(Stream.START_CDATA)
     165        self._enqueue(START_CDATA)
    156166
    157167    def _handle_end_cdata(self):
    158         self._enqueue(Stream.END_CDATA)
     168        self._enqueue(END_CDATA)
    159169
    160170    def _handle_pi(self, target, data):
    161         self._enqueue(Stream.PI, (target, data))
     171        self._enqueue(PI, (target, data))
    162172
    163173    def _handle_comment(self, text):
    164         self._enqueue(Stream.COMMENT, text)
     174        self._enqueue(COMMENT, text)
    165175
    166176    def _handle_other(self, text):
     
    169179            try:
    170180                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
    171                 self._enqueue(Stream.TEXT, text)
     181                self._enqueue(TEXT, text)
    172182            except KeyError:
    173183                lineno, offset = self._getpos()
     
    209219        self._open_tags = []
    210220
     221    def parse(self):
     222        def _generate():
     223            try:
     224                bufsize = 4 * 1024 # 4K
     225                done = False
     226                while 1:
     227                    while not done and len(self._queue) == 0:
     228                        data = self.source.read(bufsize)
     229                        if data == '': # end of data
     230                            self.close()
     231                            done = True
     232                        else:
     233                            self.feed(data)
     234                    for kind, data, pos in self._queue:
     235                        yield kind, data, pos
     236                    self._queue = []
     237                    if done:
     238                        open_tags = self._open_tags
     239                        open_tags.reverse()
     240                        for tag in open_tags:
     241                            yield END, QName(tag), pos
     242                        break
     243            except html.HTMLParseError, e:
     244                msg = '%s: line %d, column %d' % (e.msg, e.lineno, e.offset)
     245                if self.filename:
     246                    msg += ', in %s' % self.filename
     247                raise ParseError(msg, self.filename, e.lineno, e.offset)
     248        return Stream(_generate()).filter(CoalesceFilter())
     249
    211250    def __iter__(self):
    212         try:
    213             bufsize = 4 * 1024 # 4K
    214             done = False
    215             while 1:
    216                 while not done and len(self._queue) == 0:
    217                     data = self.source.read(bufsize)
    218                     if data == '': # end of data
    219                         self.close()
    220                         done = True
    221                     else:
    222                         self.feed(data)
    223                 for kind, data, pos in self._queue:
    224                     yield kind, data, pos
    225                 self._queue = []
    226                 if done:
    227                     open_tags = self._open_tags
    228                     open_tags.reverse()
    229                     for tag in open_tags:
    230                         yield Stream.END, QName(tag), pos
    231                     break
    232         except html.HTMLParseError, e:
    233             msg = '%s: line %d, column %d' % (e.msg, e.lineno, e.offset)
    234             if self.filename:
    235                 msg += ', in %s' % self.filename
    236             raise ParseError(msg, self.filename, e.lineno, e.offset)
     251        return iter(self.parse())
    237252
    238253    def _enqueue(self, kind, data, pos=None):
     
    252267            fixed_attrib.append((name, unicode(value)))
    253268
    254         self._enqueue(Stream.START, (QName(tag), Attributes(fixed_attrib)))
     269        self._enqueue(START, (QName(tag), Attributes(fixed_attrib)))
    255270        if tag in self._EMPTY_ELEMS:
    256             self._enqueue(Stream.END, QName(tag))
     271            self._enqueue(END, QName(tag))
    257272        else:
    258273            self._open_tags.append(tag)
     
    264279                if open_tag.lower() == tag.lower():
    265280                    break
    266                 self._enqueue(Stream.END, QName(open_tag))
    267             self._enqueue(Stream.END, QName(tag))
     281                self._enqueue(END, QName(open_tag))
     282            self._enqueue(END, QName(tag))
    268283
    269284    def handle_data(self, text):
    270         self._enqueue(Stream.TEXT, text)
     285        self._enqueue(TEXT, text)
    271286
    272287    def handle_charref(self, name):
    273         self._enqueue(Stream.TEXT, Markup('&#%s;' % name))
     288        text = unichr(int(name))
     289        self._enqueue(TEXT, text)
    274290
    275291    def handle_entityref(self, name):
    276         self._enqueue(Stream.TEXT, Markup('&%s;' % name))
     292        try:
     293            text = unichr(htmlentitydefs.name2codepoint[name])
     294        except KeyError:
     295            text = '&%s;' % name
     296        self._enqueue(TEXT, text)
    277297
    278298    def handle_pi(self, data):
    279299        target, data = data.split(maxsplit=1)
    280300        data = data.rstrip('?')
    281         self._enqueue(Stream.PI, (target.strip(), data.strip()))
     301        self._enqueue(PI, (target.strip(), data.strip()))
    282302
    283303    def handle_comment(self, text):
    284         self._enqueue(Stream.COMMENT, text)
     304        self._enqueue(COMMENT, text)
    285305
    286306
    287307def HTML(text):
    288308    return Stream(list(HTMLParser(StringIO(text))))
     309
     310
     311class CoalesceFilter(object):
     312    """Coalesces adjacent TEXT events into a single event."""
     313
     314    def __call__(self, stream, ctxt=None):
     315        textbuf = []
     316        textpos = None
     317        for kind, data, pos in chain(stream, [(None, None, None)]):
     318            if kind is TEXT:
     319                textbuf.append(data)
     320                if textpos is None:
     321                    textpos = pos
     322            else:
     323                if textbuf:
     324                    yield TEXT, u''.join(textbuf), textpos
     325                    del textbuf[:]
     326                    textpos = None
     327                if kind:
     328                    yield kind, data, pos
Note: See TracChangeset for help on using the changeset viewer.