home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 April / PCWorld_2001-04_cd.bin / Software / TemaCD / webclean / webparser / sgmlop.c < prev   
Text File  |  2001-01-16  |  20KB  |  822 lines

  1. /*
  2.  * SGMLOP
  3.  * $Id: sgmlop.c,v 1.6 2001/01/16 19:22:15 calvin Exp $
  4.  *
  5.  * The sgmlop accelerator module
  6.  *
  7.  * This module provides a FastSGMLParser type, which is designed to
  8.  * speed up the standard sgmllib and xmllib modules.  The parser can
  9.  * be configured to support either basic SGML (enough of it to process
  10.  * HTML documents, at least) or XML.  This module also provides an
  11.  * Element type, useful for fast but simple DOM implementations.
  12.  *
  13.  * History:
  14.  * 1998-04-04 fl  Created (for coreXML)
  15.  * 1998-04-05 fl  Added close method
  16.  * 1998-04-06 fl  Added parse method, revised callback interface
  17.  * 1998-04-14 fl  Fixed parsing of PI tags
  18.  * 1998-05-14 fl  Cleaned up for first public release
  19.  * 1998-05-19 fl  Fixed xmllib compatibility: handle_proc, handle_special
  20.  * 1998-05-22 fl  Added attribute parser
  21.  * 1999-06-20 fl  Added Element data type, various bug fixes.
  22.  * 2000-05-28 fl  Fixed data truncation error (@SGMLOP1)
  23.  * 2000-05-28 fl  Added temporary workaround for unicode problem (@SGMLOP2)
  24.  * 2000-05-28 fl  Removed optional close argument (@SGMLOP3)
  25.  * 2000-05-28 fl  Raise exception on recursive feed (@SGMLOP4)
  26.  * 2000-07-05 fl  Fixed attribute handling in empty tags (@SGMLOP6)
  27.  Changes from Bastian Kleineidam <calvin@users.sourceforge.net>
  28.  * new reset function
  29.  * use METH_VARARGS in method tables
  30.  * flush unprocessed data on close
  31.  * removed element and treebuilder, dont need it and its not working anyway
  32.  * merged register() function into constructor
  33.  * give error on missing callbacks
  34.  * better start tag parsing
  35.  * direct call of unknown_starttag, unknown_endtag
  36.  * fixed bug with unquoted attrs ending with a slash:
  37.    <a href=http://foo/>bar</a>
  38.  * deleted xml parser, I only need sgml/html
  39.  *
  40.  * Copyright (c) 1998-2000 by Secret Labs AB
  41.  * Copyright (c) 1998-2000 by Fredrik Lundh
  42.  *
  43.  * fredrik@pythonware.com
  44.  * http://www.pythonware.com
  45.  *
  46.  * By obtaining, using, and/or copying this software and/or its
  47.  * associated documentation, you agree that you have read, understood,
  48.  * and will comply with the following terms and conditions:
  49.  *
  50.  * Permission to use, copy, modify, and distribute this software and its
  51.  * associated documentation for any purpose and without fee is hereby
  52.  * granted, provided that the above copyright notice appears in all
  53.  * copies, and that both that copyright notice and this permission notice
  54.  * appear in supporting documentation, and that the name of Secret Labs
  55.  * AB or the author not be used in advertising or publicity pertaining to
  56.  * distribution of the software without specific, written prior
  57.  * permission.
  58.  *
  59.  * SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
  60.  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  61.  * FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
  62.  * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  63.  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  64.  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  65.  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  */
  66.  
  67. #include "Python.h"
  68.  
  69. #include <ctype.h>
  70.  
  71. #ifdef SGMLOP_UNICODE_SUPPORT
  72. /* wide character set (experimental) */
  73. /* FIXME: under Python 1.6, the current version converts Unicode
  74.  strings to UTF-8, and parses the result as if it was an ASCII
  75.  string. */
  76. #define CHAR_T  Py_UNICODE
  77. #define ISALNUM Py_UNICODE_ISALNUM
  78. #define ISSPACE Py_UNICODE_ISSPACE
  79. #define TOLOWER Py_UNICODE_TOLOWER
  80. #else
  81. /* 8-bit character set */
  82. #define CHAR_T  char
  83. #define ISALNUM isalnum
  84. #define ISSPACE isspace
  85. #define TOLOWER tolower
  86. #endif
  87.  
  88. #if 0
  89. static int memory = 0;
  90. #define ALLOC(size, comment)\
  91.     do { memory += size; printf("%8d - %s\n", memory, comment); } while (0)
  92. #define RELEASE(size, comment)\
  93.     do { memory -= size; printf("%8d - %s\n", memory, comment); } while (0)
  94. #else
  95. #define ALLOC(size, comment)
  96. #define RELEASE(size, comment)
  97. #endif
  98.  
  99. /* ==================================================================== */
  100. /* parser data type */
  101.  
  102. /* state flags */
  103. #define MAYBE 1
  104. #define SURE 2
  105.  
  106. /* parser type definition */
  107. typedef struct {
  108.     PyObject_HEAD
  109.  
  110.     /* state attributes */
  111.     int feed;
  112.     int shorttag; /* 0=normal 2=parsing shorttag */
  113.     int doctype; /* 0=normal 1=dtd pending 2=parsing dtd */
  114.  
  115.     /* buffer (holds incomplete tags) */
  116.     char* buffer;
  117.     int bufferlen; /* current amount of data */
  118.     int buffertotal; /* actually allocated */
  119.  
  120.     /* callbacks */
  121.     PyObject* unknown_starttag;
  122.     PyObject* unknown_endtag;
  123.     PyObject* handle_proc;
  124.     PyObject* handle_special;
  125.     PyObject* handle_charref;
  126.     PyObject* handle_entityref;
  127.     PyObject* handle_data;
  128.     PyObject* handle_cdata;
  129.     PyObject* handle_comment;
  130.  
  131. } FastSGMLParserObject;
  132.  
  133. staticforward PyTypeObject FastSGMLParser_Type;
  134.  
  135. /* forward declarations */
  136. static int fastfeed(FastSGMLParserObject* self);
  137. static PyObject* attrparse(const CHAR_T *p, int len);
  138.  
  139.  
  140. /* -------------------------------------------------------------------- */
  141. /* create parser */
  142.  
  143. static PyObject* _sgmlop_new(PyObject* item) {
  144.     FastSGMLParserObject* self;
  145.  
  146.     self = PyObject_NEW(FastSGMLParserObject, &FastSGMLParser_Type);
  147.     if (self == NULL)
  148.     return NULL;
  149.  
  150.     self->feed = 0;
  151.     self->shorttag = 0;
  152.     self->doctype = 0;
  153.     self->buffer = NULL;
  154.     self->bufferlen = 0;
  155.     self->buffertotal = 0;
  156.  
  157.     /* register callbacks */
  158.     self->unknown_starttag = PyObject_GetAttrString(item, "unknown_starttag");
  159.     self->unknown_endtag = PyObject_GetAttrString(item, "unknown_endtag");
  160.     self->handle_proc = PyObject_GetAttrString(item, "handle_proc");
  161.     self->handle_special = PyObject_GetAttrString(item, "handle_special");
  162.     self->handle_charref = PyObject_GetAttrString(item, "handle_charref");
  163.     self->handle_entityref = PyObject_GetAttrString(item, "handle_entityref");
  164.     self->handle_data = PyObject_GetAttrString(item, "handle_data");
  165.     self->handle_cdata = PyObject_GetAttrString(item, "handle_cdata");
  166.     self->handle_comment = PyObject_GetAttrString(item, "handle_comment");
  167.     /* PyErr_Clear(); *//* commented out because we dont accept missing
  168.      callbacks! */
  169.     return (PyObject*) self;
  170. }
  171.  
  172.  
  173. static PyObject* _sgmlop_sgmlparser(PyObject* self, PyObject* args) {
  174.     PyObject* item;
  175.     if (!PyArg_ParseTuple(args, "O", &item))
  176.     return NULL;
  177.     return _sgmlop_new(item);
  178. }
  179.  
  180.  
  181. static void
  182. _sgmlop_dealloc(FastSGMLParserObject* self)
  183. {
  184.     if (self->buffer)
  185.     free(self->buffer);
  186.     Py_DECREF(self->unknown_starttag);
  187.     Py_DECREF(self->unknown_endtag);
  188.     Py_DECREF(self->handle_proc);
  189.     Py_DECREF(self->handle_special);
  190.     Py_DECREF(self->handle_charref);
  191.     Py_DECREF(self->handle_entityref);
  192.     Py_DECREF(self->handle_data);
  193.     Py_DECREF(self->handle_cdata);
  194.     Py_DECREF(self->handle_comment);
  195.     PyMem_DEL(self);
  196. }
  197.  
  198. /* release the internal buffer and reset all values except the function
  199.  callbacks */
  200. static void reset(FastSGMLParserObject* self) {
  201.     if (self->buffer!=NULL) {
  202.     free(self->buffer);
  203.     self->buffer = NULL;
  204.     }
  205.     self->bufferlen = 0;
  206.     self->buffertotal = 0;
  207.     self->feed = 0;
  208.     self->shorttag = 0;
  209.     self->doctype = 0;
  210. }
  211.  
  212. /* reset the parser */
  213. static PyObject* _sgmlop_reset(FastSGMLParserObject* self, PyObject* args) {
  214.     if (!PyArg_NoArgs(args))
  215.     return NULL;
  216.     reset(self);
  217.     Py_INCREF(Py_None);
  218.     return Py_None;
  219. }
  220.  
  221.  
  222. /* -------------------------------------------------------------------- */
  223. /* feed data to parser.  the parser processes as much of the data as
  224.  possible, and keeps the rest in a local buffer. */
  225.  
  226. static PyObject*
  227. feed(FastSGMLParserObject* self, char* string, int stringlen, int last)
  228. {
  229.     /* common subroutine for SGMLParser.feed and SGMLParser.close */
  230.  
  231.     int length;
  232.  
  233.     if (self->feed) {
  234.     /* dealing with recursive feeds isn's exactly trivial, so
  235.      let's just bail out before the parser messes things up */
  236.     PyErr_SetString(PyExc_AssertionError, "recursive feed");
  237.     return NULL;
  238.     }
  239.  
  240.     /* append new text block to local buffer */
  241.     if (!self->buffer) {
  242.     length = stringlen;
  243.     self->buffer = malloc(length);
  244.     self->buffertotal = stringlen;
  245.     } else {
  246.     length = self->bufferlen + stringlen;
  247.     if (length > self->buffertotal) {
  248.         self->buffer = realloc(self->buffer, length);
  249.         self->buffertotal = length;
  250.     }
  251.     }
  252.     if (!self->buffer) {
  253.     PyErr_NoMemory();
  254.     return NULL;
  255.     }
  256.     memcpy(self->buffer + self->bufferlen, string, stringlen);
  257.     self->bufferlen = length;
  258.  
  259.     self->feed = 1;
  260.  
  261.     length = fastfeed(self);
  262.  
  263.     self->feed = 0;
  264.  
  265.     if (length < 0)
  266.     return NULL;
  267.  
  268.     if (length > self->bufferlen) {
  269.     /* ran beyond the end of the buffer (internal error)*/
  270.     PyErr_SetString(PyExc_AssertionError, "buffer overrun");
  271.     return NULL;
  272.     }
  273.  
  274.     if (length > 0 && length < self->bufferlen)
  275.     /* adjust buffer */
  276.     memmove(self->buffer, self->buffer + length,
  277.         self->bufferlen - length);
  278.  
  279.     self->bufferlen = self->bufferlen - length;
  280.  
  281.     /* if data remains in the buffer even through this is the
  282.      last call, do an extra handle_data to get rid of it */
  283.     if (last) {
  284.     if (!PyObject_CallFunction(self->handle_data,
  285.                    "s#", self->buffer, self->bufferlen))
  286.         return NULL;
  287.     /* shut the parser down and release the internal buffers */
  288.     reset(self);
  289.     }
  290.  
  291.     return Py_BuildValue("i", self->bufferlen);
  292. }
  293.  
  294. static PyObject*
  295. _sgmlop_feed(FastSGMLParserObject* self, PyObject* args)
  296. {
  297.     /* feed a chunk of data to the parser */
  298.  
  299.     char* string;
  300.     int stringlen;
  301.     if (!PyArg_ParseTuple(args, "t#", &string, &stringlen))
  302.     return NULL;
  303.  
  304.     return feed(self, string, stringlen, 0);
  305. }
  306.  
  307. static PyObject*
  308. _sgmlop_close(FastSGMLParserObject* self, PyObject* args)
  309. {
  310.     /* flush parser buffers */
  311.  
  312.     if (!PyArg_NoArgs(args))
  313.     return NULL;
  314.  
  315.     return feed(self, "", 0, 1);
  316. }
  317.  
  318. static PyObject*
  319. _sgmlop_parse(FastSGMLParserObject* self, PyObject* args)
  320. {
  321.     /* feed a single chunk of data to the parser */
  322.  
  323.     char* string;
  324.     int stringlen;
  325.     if (!PyArg_ParseTuple(args, "t#", &string, &stringlen))
  326.     return NULL;
  327.  
  328.     return feed(self, string, stringlen, 1);
  329. }
  330.  
  331.  
  332. /* -------------------------------------------------------------------- */
  333. /* type interface */
  334.  
  335. static PyMethodDef _sgmlop_methods[] = {
  336.     /* incremental parsing */
  337.     {"feed", (PyCFunction) _sgmlop_feed, METH_VARARGS},
  338.     /* reset the parser */
  339.     {"reset", (PyCFunction) _sgmlop_reset, 0},
  340.     {"close", (PyCFunction) _sgmlop_close, 0},
  341.     /* one-shot parsing */
  342.     {"parse", (PyCFunction) _sgmlop_parse, METH_VARARGS},
  343.     {NULL, NULL}
  344. };
  345.  
  346. static PyObject*
  347. _sgmlop_getattr(FastSGMLParserObject* self, char* name)
  348. {
  349.     return Py_FindMethod(_sgmlop_methods, (PyObject*) self, name);
  350. }
  351.  
  352. statichere PyTypeObject FastSGMLParser_Type = {
  353.     PyObject_HEAD_INIT(NULL)
  354.     0, /* ob_size */
  355.     "FastSGMLParser", /* tp_name */
  356.     sizeof(FastSGMLParserObject), /* tp_size */
  357.     0, /* tp_itemsize */
  358.     /* methods */
  359.     (destructor)_sgmlop_dealloc, /* tp_dealloc */
  360.     0, /* tp_print */
  361.     (getattrfunc)_sgmlop_getattr, /* tp_getattr */
  362.     0 /* tp_setattr */
  363. };
  364.  
  365. /* ==================================================================== */
  366. /* python module interface */
  367.  
  368. static PyMethodDef _functions[] = {
  369.     {"SGMLParser", _sgmlop_sgmlparser, METH_VARARGS},
  370.     {NULL, NULL}
  371. };
  372.  
  373. void
  374. #ifdef WIN32
  375. __declspec(dllexport)
  376. #endif
  377. initsgmlop(void)
  378. {
  379.     /* Patch object type */
  380.     FastSGMLParser_Type.ob_type = &PyType_Type;
  381.     Py_InitModule("sgmlop", _functions);
  382. }
  383.  
  384. /* -------------------------------------------------------------------- */
  385. /* the parser does it all in a single loop, keeping the necessary
  386.  state in a few flag variables and the data buffer.  if you have
  387.  a good optimizer, this can be incredibly fast. */
  388.  
  389. #define TAG 0x100
  390. #define TAG_START 0x101
  391. #define TAG_END 0x102
  392. #define TAG_EMPTY 0x103
  393. #define DIRECTIVE 0x104
  394. #define DOCTYPE 0x105
  395. #define PI 0x106
  396. #define DTD_START 0x107
  397. #define DTD_END 0x108
  398. #define DTD_ENTITY 0x109
  399. #define CDATA 0x200
  400. #define ENTITYREF 0x400
  401. #define CHARREF 0x401
  402. #define COMMENT 0x800
  403.  
  404. static int
  405. fastfeed(FastSGMLParserObject* self)
  406. {
  407.     CHAR_T *end; /* tail */
  408.     CHAR_T *p, *q, *s; /* scanning pointers */
  409.     CHAR_T *b, *t, *e; /* token start/end */
  410.  
  411.     int token;
  412.  
  413.     s = q = p = (CHAR_T*) self->buffer;
  414.     end = (CHAR_T*) (self->buffer + self->bufferlen);
  415.  
  416.     while (p < end) {
  417.  
  418.     q = p; /* start of token */
  419.  
  420.     if (*p == '<') {
  421.         int has_attr;
  422.  
  423.         /* <tags> */
  424.         token = TAG_START;
  425.         if (++p >= end)
  426.         goto eol;
  427.  
  428.         if (*p == '!') {
  429.         /* <! directive */
  430.         if (++p >= end)
  431.             goto eol;
  432.         token = DIRECTIVE;
  433.         b = t = p;
  434.         if (*p == '-') {
  435.             int i;
  436.             /* <!-- comment --> */
  437.             token = COMMENT;
  438.             b = p + 2;
  439.             for (;;) {
  440.             if (p+3 >= end)
  441.                 goto eol;
  442.             if (p[1] != '-')
  443.                 p += 2; /* boyer moore, sort of ;-) */
  444.             else if (p[0] != '-')
  445.                 p++;
  446.             else {
  447.                 i=2;
  448.                 /* skip spaces */
  449.                 while (isspace(p[i])) {
  450.                 ++i;
  451.                 if (p+i >= end)
  452.                     goto eol;
  453.                 }
  454.                 if (p[i]=='>')
  455.                 break;
  456.                 p+=i;
  457.             }
  458.             }
  459.             e = p;
  460.             p += i+1;
  461.             goto eot;
  462.         }
  463.         } else if (*p == '?') {
  464.         token = PI;
  465.         if (++p >= end)
  466.             goto eol;
  467.         } else if (*p == '/') {
  468.         /* </endtag> */
  469.         token = TAG_END;
  470.         if (++p >= end)
  471.             goto eol;
  472.         }
  473.  
  474.         /* process tag name */
  475.         b = p;
  476.         while (ISALNUM(*p) || *p == '-' || *p == '.' ||
  477.            *p == ':' || *p == '?') {
  478.         *p = (CHAR_T) TOLOWER(*p);
  479.         if (++p >= end)
  480.             goto eol;
  481.         }
  482.  
  483.         t = p;
  484.  
  485.         has_attr = 0;
  486.  
  487.         if (*p == '/') {
  488.         /* <tag/data/ or <tag/> */
  489.         token = TAG_START;
  490.         e = p;
  491.         if (++p >= end)
  492.             goto eol;
  493.         if (*p == '>') {
  494.             /* <tag/> */
  495.             token = TAG_EMPTY;
  496.             if (++p >= end)
  497.             goto eol;
  498.         } else
  499.             /* <tag/data/ */
  500.             self->shorttag = SURE;
  501.         /* we'll generate an end tag when we stumble upon
  502.          the end slash */
  503.         } else {
  504.         /* skip attributes */
  505.         int quote = 0;
  506.         int last = 0;
  507.         int error = 0;
  508.         int state = 0;
  509.         while (*p != '>' || (quote && !error)) {
  510.             if (!ISSPACE(*p)) {
  511.             if (state==3) error=1;
  512.             has_attr = 1;
  513.             /* FIXME: note: end tags cannot have attributes! */
  514.             }
  515.             else if (state==3) state=0;
  516.             if (quote) {
  517.             if (*p == quote) {
  518.                 quote = 0;
  519.                 state = 3;
  520.             }
  521.             } else {
  522.             if (*p=='=') {
  523.                 if (state==1) error=1;
  524.                 else state=1;
  525.             }
  526.             if (*p == '"' || *p == '\'') {
  527.                 if (state!=1) error=1;
  528.                 quote = *p;
  529.                 state=2;
  530.             }
  531.             }
  532.             if (*p == '[' && !quote && self->doctype) {
  533.             self->doctype = SURE;
  534.             token = DTD_START;
  535.             e = p++;
  536.             goto eot;
  537.             }
  538.             last = *p;
  539.             if (++p >= end)
  540.             goto eol;
  541.         }
  542.  
  543.         e = p++;
  544.  
  545.         //if (last == '/') {
  546.             /* <tag/> */
  547.         //    e--;
  548.         //    token = TAG_EMPTY;
  549.                 //} else if {
  550.         if (token == PI && last == '?')
  551.             e--;
  552.  
  553.         if (self->doctype == MAYBE)
  554.             self->doctype = 0; /* there was no dtd */
  555.  
  556.         if (has_attr)
  557.             ; /* FIXME: process attributes */
  558.  
  559.         }
  560.  
  561.     } else if (*p == '/' && self->shorttag) {
  562.  
  563.         /* end of shorttag. this generates an empty end tag */
  564.         token = TAG_END;
  565.         self->shorttag = 0;
  566.         b = t = e = p;
  567.         if (++p >= end)
  568.         goto eol;
  569.  
  570.     } else if (*p == ']' && self->doctype) {
  571.  
  572.         /* end of dtd. this generates an empty end tag */
  573.         token = DTD_END;
  574.         /* FIXME: who handles the ending > !? */
  575.         b = t = e = p;
  576.         if (++p >= end)
  577.         goto eol;
  578.         self->doctype = 0;
  579.  
  580.     } else if (*p == '%' && self->doctype) {
  581.  
  582.         /* doctype entities */
  583.         token = DTD_ENTITY;
  584.         if (++p >= end)
  585.         goto eol;
  586.         b = t = p;
  587.         while (ISALNUM(*p) || *p == '.')
  588.         if (++p >= end)
  589.             goto eol;
  590.         e = p;
  591.         if (*p == ';')
  592.         p++;
  593.  
  594.     } else if (*p == '&') {
  595.  
  596.         /* entities */
  597.         token = ENTITYREF;
  598.         if (++p >= end)
  599.         goto eol;
  600.         if (*p == '#') {
  601.         token = CHARREF;
  602.         if (++p >= end)
  603.             goto eol;
  604.         }
  605.         b = t = p;
  606.         while (ISALNUM(*p) || *p == '.')
  607.         if (++p >= end)
  608.             goto eol;
  609.         e = p;
  610.         if (*p == ';')
  611.         p++;
  612.  
  613.     } else {
  614.  
  615.         /* raw data */
  616.         if (++p >= end) {
  617.         q = p;
  618.         goto eol;
  619.         }
  620.         continue;
  621.  
  622.     }
  623.  
  624.     eot: /* end of token */
  625.  
  626.     if (q != s) {
  627.         /* flush any raw data before this tag */
  628.         PyObject* res;
  629.         res = PyObject_CallFunction(self->handle_data,
  630.                     "s#", s, q-s);
  631.         if (!res)
  632.         return -1;
  633.         Py_DECREF(res);
  634.     }
  635.  
  636.     /* invoke callbacks */
  637.     if (token & TAG) {
  638.         if (token == TAG_END) {
  639.         PyObject* res;
  640.         res = PyObject_CallFunction(self->unknown_endtag,
  641.                         "s#", b, t-b);
  642.         if (!res)
  643.             return -1;
  644.         Py_DECREF(res);
  645.         } else if (token == DIRECTIVE || token == DOCTYPE) {
  646.         PyObject* res;
  647.         res = PyObject_CallFunction(self->handle_special,
  648.                         "s#", b, e-b);
  649.         if (!res)
  650.             return -1;
  651.         Py_DECREF(res);
  652.         } else if (token == PI) {
  653.         PyObject* res;
  654.         int len = t-b;
  655.         while (ISSPACE(*t))
  656.             t++;
  657.         res = PyObject_CallFunction(self->handle_proc,
  658.                         "s#s#", b, len, t, e-t);
  659.         if (!res)
  660.             return -1;
  661.         Py_DECREF(res);
  662.         } else {
  663.         PyObject* res;
  664.         PyObject* attr;
  665.         int len = t-b;
  666.         while (ISSPACE(*t))
  667.             t++;
  668.         attr = attrparse(t, e-t);
  669.         if (!attr)
  670.             return -1;
  671.         res = PyObject_CallFunction(self->unknown_starttag,
  672.                         "s#O", b, len, attr);
  673.         Py_DECREF(attr);
  674.         if (!res)
  675.             return -1;
  676.         Py_DECREF(res);
  677.         if (token == TAG_EMPTY) {
  678.             res = PyObject_CallFunction(self->unknown_endtag,
  679.                         "s#", b, len);
  680.             if (!res)
  681.             return -1;
  682.             Py_DECREF(res);
  683.         }
  684.         }
  685.     } else if (token == ENTITYREF) {
  686.         PyObject* res;
  687.         res = PyObject_CallFunction(self->handle_entityref,
  688.                     "s#", b, e-b);
  689.         if (!res)
  690.         return -1;
  691.         Py_DECREF(res);
  692.     } else if (token == CHARREF) {
  693.         PyObject* res;
  694.         res = PyObject_CallFunction(self->handle_charref,
  695.                     "s#", b, e-b);
  696.         if (!res)
  697.         return -1;
  698.         Py_DECREF(res);
  699.     } else if (token == CDATA) {
  700.         PyObject* res;
  701.         res = PyObject_CallFunction(self->handle_cdata,
  702.                     "s#", b, e-b);
  703.         if (!res)
  704.         return -1;
  705.         Py_DECREF(res);
  706.     } else if (token == COMMENT) {
  707.         PyObject* res;
  708.         res = PyObject_CallFunction(self->handle_comment,
  709.                     "s#", b, e-b);
  710.         if (!res)
  711.         return -1;
  712.         Py_DECREF(res);
  713.     }
  714.  
  715.     q = p; /* start of token */
  716.     s = p; /* start of span */
  717.     }
  718.  
  719. eol: /* end of line */
  720.     if (q != s) {
  721.     PyObject* res;
  722.     res = PyObject_CallFunction(self->handle_data,
  723.                     "s#", s, q-s);
  724.     if (!res)
  725.         return -1;
  726.     Py_DECREF(res);
  727.     }
  728.  
  729.     /* returns the number of bytes consumed in this pass */
  730.     return ((char*) q) - self->buffer;
  731. }
  732.  
  733.  
  734. static PyObject*
  735. attrparse(const CHAR_T* p, int len)
  736. {
  737.     PyObject* attrs;
  738.     PyObject* res;
  739.     PyObject* key = NULL;
  740.     PyObject* value = NULL;
  741.     const CHAR_T* end = p + len;
  742.     const CHAR_T* q;
  743.  
  744.     attrs = PyList_New(0);
  745.  
  746.     while (p < end) {
  747.  
  748.     /* skip leading space */
  749.     while (p < end && ISSPACE(*p))
  750.         p++;
  751.     if (p >= end)
  752.         break;
  753.  
  754.     /* get attribute name (key) */
  755.     q = p;
  756.     while (p < end && *p != '=' && !ISSPACE(*p))
  757.         p++;
  758.  
  759.     key = PyString_FromStringAndSize(q, p-q);
  760.     if (key == NULL)
  761.         goto err;
  762.  
  763.     value = key; /* in SGML mode, default is same as key */
  764.  
  765.     Py_INCREF(value);
  766.  
  767.     while (p < end && ISSPACE(*p))
  768.         p++;
  769.  
  770.     if (p < end && *p == '=') {
  771.  
  772.         /* attribute value found */
  773.         Py_DECREF(value);
  774.  
  775.         if (p < end)
  776.         p++;
  777.         while (p < end && ISSPACE(*p))
  778.         p++;
  779.  
  780.         q = p;
  781.         if (p < end && (*p == '"' || *p == '\'')) {
  782.         p++;
  783.         while (p < end && *p != *q)
  784.             p++;
  785.         value = PyString_FromStringAndSize(q+1, p-q-1);
  786.         if (p < end && *p == *q)
  787.             p++;
  788.         } else {
  789.         while (p < end && !ISSPACE(*p))
  790.             p++;
  791.         value = PyString_FromStringAndSize(q, p-q);
  792.         }
  793.  
  794.         if (value == NULL)
  795.         goto err;
  796.     }
  797.  
  798.     /* add to list */
  799.     res = PyTuple_New(2);
  800.     if (!res)
  801.         goto err;
  802.     PyTuple_SET_ITEM(res, 0, key);
  803.     PyTuple_SET_ITEM(res, 1, value);
  804.     if (PyList_Append(attrs, res) < 0) {
  805.         Py_DECREF(res);
  806.         goto err;
  807.     }
  808.     Py_DECREF(res);
  809.     key = NULL;
  810.     value = NULL;
  811.  
  812.     }
  813.  
  814.     return attrs;
  815.  
  816. err:
  817.     Py_XDECREF(key);
  818.     Py_XDECREF(value);
  819.     Py_DECREF(attrs);
  820.     return NULL;
  821. }
  822.