home *** CD-ROM | disk | FTP | other *** search
/ Magazyn Amiga 13 / MA_Cover_13.bin / source / c / tidy26jul99 / lexer.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-12-08  |  56.4 KB  |  2,251 lines

  1. /*
  2.   lexer.c - Lexer for html parser
  3.   
  4.   (c) 1998 (W3C) MIT, INRIA, Keio University
  5.   See tidy.c for the copyright notice.
  6. */
  7.  
  8. /*
  9.   Given a file stream fp it returns a sequence of tokens.
  10.  
  11.      GetToken(fp) gets the next token
  12.      UngetToken(fp) provides one level undo
  13.  
  14.   The tags include an attribute list:
  15.  
  16.     - linked list of attribute/value nodes
  17.     - each node has 2 null-terminated strings.
  18.     - entities are replaced in attribute values
  19.  
  20.   white space is compacted if not in preformatted mode
  21.   If not in preformatted mode then leading white space
  22.   is discarded and subsequent white space sequences
  23.   compacted to single space chars.
  24.  
  25.   If XmlTags is no then Tag names are folded to upper
  26.   case and attribute names to lower case.
  27.  
  28.  Not yet done:
  29.     -   Doctype subset and marked sections
  30. */
  31.  
  32. #include "platform.h"
  33. #include "html.h"
  34.  
  35. AttVal *ParseAttrs(Lexer *lexer, Bool *isempty);  /* forward references */
  36. void CheckAttributes(Lexer *lexer, Node *node);
  37. Node *CommentToken(Lexer *lexer);
  38.  
  39. /* used to classify chars for lexical purposes */
  40. #define MAP(c) ((unsigned)c < 128 ? lexmap[(unsigned)c] : 0)
  41. uint lexmap[128];
  42.  
  43. #define W3C_VERSIONS 5
  44.  
  45. /* the 3 namespace URIs for XHTML 1.0 */
  46. #define voyager_loose    "http://www.w3.org/TR/xhtml1/transitional"
  47. #define voyager_strict   "http://www.w3.org/TR/xhtml1/strict"
  48. #define voyager_frameset "http://www.w3.org/TR/xhtml1/frameset"
  49.  
  50. struct _vers
  51. {
  52.     char *name;
  53.     char *voyager_name;
  54.     char *profile;
  55.     int code;
  56. } W3C_Version[] =
  57. {
  58.     "HTML 2.0", "XHTML 1.0 Strict", voyager_strict, VERS_HTML20,
  59.     "HTML 3.2", "XHTML 1.0 Transitional", voyager_loose, VERS_HTML32,
  60.     "HTML 4.0", "XHTML 1.0 Strict", voyager_strict, VERS_HTML40_STRICT,
  61.     "HTML 4.0 Transitional", "XHTML 1.0 Transitional", voyager_loose, VERS_HTML40_LOOSE,
  62.     "HTML 4.0 Frameset", "XHTML 1.0 Frameset", voyager_frameset, VERS_FRAMES
  63. };
  64.  
  65. Bool IsWhite(uint c)
  66. {
  67.     uint map = MAP(c);
  68.  
  69.     return (Bool)(map & white);
  70. }
  71.  
  72. Bool IsDigit(uint c)
  73. {
  74.     uint map;
  75.  
  76.     map = MAP(c);
  77.  
  78.     return (Bool)(map & digit);
  79. }
  80.  
  81. Bool IsLetter(uint c)
  82. {
  83.     uint map;
  84.  
  85.     map = MAP(c);
  86.  
  87.     return (Bool)(map & letter);
  88. }
  89.  
  90. uint ToLower(uint c)
  91. {
  92.     uint map = MAP(c);
  93.  
  94.     if (map & uppercase)
  95.         c += 'a' - 'A';
  96.  
  97.     return c;
  98. }
  99.  
  100. uint ToUpper(uint c)
  101. {
  102.     uint map = MAP(c);
  103.  
  104.     if (map & lowercase)
  105.         c += 'A' - 'a';
  106.  
  107.     return c;
  108. }
  109.  
  110. char FoldCase(char c, Bool tocaps)
  111. {
  112.     uint map;
  113.  
  114.     if (!XmlTags)
  115.     {
  116.         map = MAP(c);
  117.  
  118.         if (tocaps)
  119.         {
  120.             if (map & lowercase)
  121.                 c += 'A' - 'a';
  122.         }
  123.         else /* force to lower case */
  124.         {
  125.             if (map & uppercase)
  126.                 c += 'a' - 'A';
  127.         }
  128.     }
  129.  
  130.     return c;
  131. }
  132.  
  133.  
  134. /*
  135.    node->type is one of these:
  136.  
  137.     #define TextNode    1
  138.     #define StartTag    2
  139.     #define EndTag      3
  140.     #define StartEndTag 4
  141. */
  142. Lexer *NewLexer(StreamIn *in)
  143. {
  144.     Lexer *lexer;
  145.  
  146.     lexer = (Lexer *)MemAlloc(sizeof(Lexer));
  147.  
  148.     if (lexer != null)
  149.     {
  150.         lexer->in = in;
  151.         lexer->lines = 1;
  152.         lexer->columns = 1;
  153.         lexer->state = LEX_CONTENT;
  154.         lexer->badAccess = 0;
  155.         lexer->badLayout = 0;
  156.         lexer->badChars = 0;
  157.         lexer->badForm = 0;
  158.         lexer->warnings = 0;
  159.         lexer->errors = no;
  160.         lexer->waswhite = no;
  161.         lexer->pushed = no;
  162.         lexer->insertspace = no;
  163.         lexer->isvoyager = no;
  164.         lexer->versions = VERS_EVERYTHING;
  165.         lexer->doctype = VERS_UNKNOWN;
  166.         lexer->bad_doctype = no;
  167.         lexer->txtstart = 0;
  168.         lexer->txtend = 0;
  169.         lexer->token = null;
  170.         lexer->lexbuf =  null;
  171.         lexer->lexlength = 0;
  172.         lexer->lexsize = 0;
  173.         lexer->inode = null;
  174.         lexer->insert = null;
  175.         lexer->istack = null;
  176.         lexer->istacklength = 0;
  177.         lexer->istacksize = 0;
  178.         lexer->istackbase = 0;
  179.         lexer->styles = null;
  180.     }
  181.  
  182.  
  183.     return lexer;
  184. }
  185.  
  186. Bool EndOfInput(Lexer *lexer)
  187. {
  188.     return  (feof(lexer->in->file));
  189. }
  190.  
  191. void FreeLexer(Lexer *lexer)
  192. {
  193.     if (lexer->pushed)
  194.         FreeNode(lexer->token);
  195.  
  196.     if (lexer->lexbuf != null)
  197.         MemFree(lexer->lexbuf);
  198.  
  199.     while (lexer->istacksize > 0)
  200.         PopInline(lexer, null);
  201.  
  202.     if (lexer->istack)
  203.         MemFree(lexer->istack);
  204.  
  205.     if (lexer->styles)
  206.         FreeStyles(lexer);
  207.  
  208.     MemFree(lexer);
  209. }
  210.  
  211. static void AddByte(Lexer *lexer, uint c)
  212. {
  213.     if (lexer->lexsize + 1 >= lexer->lexlength)
  214.     {
  215.         while (lexer->lexsize + 1 >= lexer->lexlength)
  216.         {
  217.             if (lexer->lexlength == 0)
  218.                 lexer->lexlength = 8192;
  219.             else
  220.                 lexer->lexlength = lexer->lexlength * 2;
  221.         }
  222.  
  223.         lexer->lexbuf = (char *)MemRealloc(lexer->lexbuf, lexer->lexlength*sizeof(char));
  224.     }
  225.  
  226.     lexer->lexbuf[lexer->lexsize++] = (char)c;
  227.     lexer->lexbuf[lexer->lexsize] = '\0';  /* debug */
  228. }
  229.  
  230. void ChangeChar(Lexer *lexer, char c)
  231. {
  232.     if (lexer->lexsize > 0)
  233.     {
  234.         lexer->lexbuf[lexer->lexsize-1] = c;
  235.     }
  236. }
  237.  
  238. /* store char c as UTF-8 encoded byte stream */
  239. void AddCharToLexer(Lexer *lexer, uint c)
  240. {
  241.     if (c < 128)
  242.         AddByte(lexer, c);
  243.     else if (c <= 0x7FF)
  244.     {
  245.         AddByte(lexer, 0xC0 | (c >> 6));
  246.         AddByte(lexer, 0x80 | (c & 0x3F));
  247.     }
  248.     else if (c <= 0xFFFF)
  249.     {
  250.         AddByte(lexer, 0xE0 | (c >> 12));
  251.         AddByte(lexer, 0x80 | ((c >> 6) & 0x3F));
  252.         AddByte(lexer, 0x80 | (c & 0x3F));
  253.     }
  254.     else if (c <= 0x1FFFFF)
  255.     {
  256.         AddByte(lexer, 0xF0 | (c >> 18));
  257.         AddByte(lexer, 0x80 | ((c >> 12) & 0x3F));
  258.         AddByte(lexer, 0x80 | ((c >> 6) & 0x3F));
  259.         AddByte(lexer, 0x80 | (c & 0x3F));
  260.     }
  261.     else
  262.     {
  263.         AddByte(lexer, 0xF8 | (c >> 24));
  264.         AddByte(lexer, 0x80 | ((c >> 18) & 0x3F));
  265.         AddByte(lexer, 0x80 | ((c >> 12) & 0x3F));
  266.         AddByte(lexer, 0x80 | ((c >> 6) & 0x3F));
  267.         AddByte(lexer, 0x80 | (c & 0x3F));
  268.     }
  269. }
  270.  
  271. void AddStringToLexer(Lexer *lexer, char *str)
  272. {
  273.     uint c;
  274.  
  275.     while((c = *str++))
  276.         AddCharToLexer(lexer, c);
  277. }
  278.  
  279. /*
  280.   No longer attempts to insert missing ';' for unknown
  281.   enitities unless one was present already, since this
  282.   gives unexpected results.
  283.  
  284.   For example:   <a href="something.htm?foo&bar&fred">
  285.   was tidied to: <a href="something.htm?foo&bar;&fred;">
  286.   rather than:   <a href="something.htm?foo&bar&fred">
  287.  
  288.   My thanks for Maurice Buxton for spotting this.
  289. */
  290. static void ParseEntity(Lexer *lexer)
  291. {
  292.     uint start, map;
  293.     Bool first = yes, semicolon = no;
  294.     int c, ch, startcol;
  295.  
  296.     start = lexer->lexsize - 1;  /* to start at "&" */
  297.     startcol = lexer->in->curcol - 1;
  298.  
  299.     while ((c = ReadChar(lexer->in)) != EndOfStream)
  300.     {
  301.         if (c == ';')
  302.         {
  303.             semicolon = yes;
  304.             break;
  305.         }
  306.  
  307.         if (first && c == '#')
  308.         {
  309.             AddCharToLexer(lexer, c);
  310.             first = no;
  311.             continue;
  312.         }
  313.  
  314.         first = no;
  315.         map = MAP(c);
  316.  
  317.         if (map & namechar)
  318.         {
  319.             AddCharToLexer(lexer, c);
  320.             continue;
  321.         }
  322.  
  323.         /* otherwise put it back */
  324.  
  325.         UngetChar(c, lexer->in);
  326.         break;
  327.     }
  328.  
  329.     /* make sure entity is null terminated */
  330.     lexer->lexbuf[lexer->lexsize] = '\0';
  331.  
  332.     ch = EntityCode(lexer->lexbuf+start);
  333.  
  334.     /* deal with unrecognized entities */
  335.     if (ch <= 0)
  336.     {
  337.         /* set error position just before offending chararcter */
  338.         lexer->lines = lexer->in->curline;
  339.         lexer->columns = startcol;
  340.  
  341.         if (lexer->lexsize > start +1 )
  342.         {
  343.             ReportEntityError(lexer, UNKNOWN_ENTITY, lexer->lexbuf+start, ch);
  344.  
  345.             if (semicolon)
  346.                 AddCharToLexer(lexer, ';');
  347.         }
  348.         else /* naked & */
  349.             ReportEntityError(lexer, UNESCAPED_AMPERSAND, lexer->lexbuf+start, ch);
  350.     }
  351.     else
  352.     {
  353.         if (c != ';')    /* issue warning if not terminated by ';' */
  354.         {
  355.             /* set error position just before offending chararcter */
  356.             lexer->lines = lexer->in->curline;
  357.             lexer->columns = startcol;
  358.             ReportEntityError(lexer, MISSING_SEMICOLON, lexer->lexbuf+start, c);
  359.         }
  360.  
  361.         lexer->lexsize = start;
  362.         AddCharToLexer(lexer, ch);
  363.  
  364.         if (ch == '&' && !QuoteAmpersand)
  365.         {
  366.             AddCharToLexer(lexer, 'a');
  367.             AddCharToLexer(lexer, 'm');
  368.             AddCharToLexer(lexer, 'p');
  369.             AddCharToLexer(lexer, ';');
  370.         }
  371.     }
  372. }
  373.  
  374. static char ParseTagName(Lexer *lexer)
  375. {
  376.     int map;
  377.     uint c;
  378.  
  379.     /* fold case of first char in buffer */
  380.  
  381.     c = lexer->lexbuf[lexer->txtstart];
  382.     map = MAP(c);
  383.  
  384.     if (!XmlTags && (map & uppercase) != 0)
  385.     {
  386.         c -= (uint)('A' - 'a');
  387.         lexer->lexbuf[lexer->txtstart] = c;
  388.     }
  389.  
  390.     while ((c = ReadChar(lexer->in)) != EndOfStream)
  391.     {
  392.         map = MAP(c);
  393.  
  394.         if ((map & namechar) == 0)
  395.             break;
  396.  
  397.        /* fold case of subsequent chars */
  398.  
  399.        if (!XmlTags && (map & uppercase) != 0)
  400.             c -= (uint)('A' - 'a');
  401.  
  402.        AddCharToLexer(lexer, c);
  403.     }
  404.  
  405.     lexer->txtend = lexer->lexsize;
  406.     return c;
  407. }
  408.  
  409. /*
  410.   Used for elements and text nodes
  411.   element name is null for text nodes
  412.   start and end are offsets into lexbuf
  413.   which contains the textual content of
  414.   all elements in the parse tree.
  415.  
  416.   parent and content allow traversal
  417.   of the parse tree in any direction.
  418.   attributes are represented as a linked
  419.   list of AttVal nodes which hold the
  420.   strings for attribute/value pairs.
  421. */
  422. Node *NewNode(void)
  423. {
  424.     Node *node;
  425.  
  426.     node = (Node *)MemAlloc(sizeof(Node));
  427.  
  428.     node->parent = null;
  429.     node->prev = null;
  430.     node->next = null;
  431.     node->last = null;
  432.     node->start = 0;
  433.     node->end = 0;
  434.     node->type = TextNode;
  435.     node->implicit = no;
  436.     node->tag = null;
  437.     node->was = null;
  438.     node->element = null;
  439.     node->attributes = null;
  440.     node->content = null;
  441.     return node;
  442. }
  443.  
  444. /* used to clone heading nodes when split by an <HR> */
  445. Node *CloneNode(Lexer *lexer, Node *element)
  446. {
  447.     Node *node;
  448.  
  449.     node = NewNode();
  450.     node->parent = element->parent;
  451.     node->start = lexer->lexsize;
  452.     node->end = lexer->lexsize;
  453.     node->type = element->type;
  454.     node->implicit = element->implicit;
  455.     node->tag = element->tag;
  456.     node->element = wstrdup(element->element);
  457.     node->attributes = DupAttrs(element->attributes);
  458.     return node;
  459. }
  460.  
  461. /* free node's attributes */
  462. void FreeAttrs(Node *node)
  463. {
  464.     AttVal *av;
  465.  
  466.     while (node->attributes)
  467.     {
  468.         av = node->attributes;
  469.  
  470.         if (av->attribute)
  471.             MemFree(av->attribute);
  472.         if (av->value)
  473.             MemFree(av->value);
  474.  
  475.         node->attributes = av->next;
  476.         MemFree(av);
  477.     }
  478. }
  479.  
  480. /*
  481.   Free document nodes by iterating through peers and recursing
  482.   through children. Set next to null before calling FreeNode()
  483.   to avoid freeing peer nodes. Doesn't patch up prev/next links.
  484.  */
  485. void FreeNode(Node *node)
  486. {
  487.     AttVal *av;
  488.     Node *next;
  489.  
  490.     while (node)
  491.     {
  492.         while (node->attributes)
  493.         {
  494.             av = node->attributes;
  495.  
  496.             if (av->attribute)
  497.                 MemFree(av->attribute);
  498.             if (av->value)
  499.                 MemFree(av->value);
  500.  
  501.             node->attributes = av->next;
  502.             MemFree(av);
  503.         }
  504.  
  505.         if (node->element)
  506.             MemFree(node->element);
  507.  
  508.         if (node->content)
  509.             FreeNode(node->content);
  510.  
  511.         if (node->next)
  512.         {
  513.             next = node->next;
  514.             MemFree(node);
  515.             node = next;
  516.             continue;
  517.         }
  518.  
  519.         node->element = null;
  520.         node->tag = null;
  521.  
  522. #if 0
  523.         if (_msize(node) != sizeof (Node)) /* debug */
  524.             fprintf(stderr, 
  525.             "Error in FreeNode() - trying to free corrupted node size %d vs %d\n",
  526.                 _msize(node), sizeof(Node));
  527. #endif
  528.         MemFree(node);
  529.         break;
  530.     }
  531. }
  532.  
  533. Node *TextToken(Lexer *lexer)
  534. {
  535.     Node *node;
  536.  
  537.     node = NewNode();
  538.     node->start = lexer->txtstart;
  539.     node->end = lexer->txtend;
  540.     return node;
  541. }
  542.  
  543. Node *TagToken(Lexer *lexer, uint type)
  544. {
  545.     Node *node;
  546.  
  547.     node = NewNode();
  548.     node->type = type;
  549.     node->element = wstrndup(lexer->lexbuf + lexer->txtstart,
  550.                         lexer->txtend - lexer->txtstart);
  551.     node->start = lexer->txtstart;
  552.     node->end = lexer->txtstart;
  553.  
  554.     if (type == StartTag || type == StartEndTag || type == EndTag)
  555.         FindTag(node);
  556.  
  557.     return node;
  558. }
  559.  
  560. Node *CommentToken(Lexer *lexer)
  561. {
  562.     Node *node;
  563.  
  564.     node = NewNode();
  565.     node->type = CommentTag;
  566.     node->start = lexer->txtstart;
  567.     node->end = lexer->txtend;
  568.     return node;
  569. }
  570.  
  571.  
  572. Node *DocTypeToken(Lexer *lexer)
  573. {
  574.     Node *node;
  575.  
  576.     node = NewNode();
  577.     node->type = DocTypeTag;
  578.     node->start = lexer->txtstart;
  579.     node->end = lexer->txtend;
  580.     return node;
  581. }
  582.  
  583.  
  584. Node *PIToken(Lexer *lexer)
  585. {
  586.     Node *node;
  587.  
  588.     node = NewNode();
  589.     node->type = ProcInsTag;
  590.     node->start = lexer->txtstart;
  591.     node->end = lexer->txtend;
  592.     return node;
  593. }
  594.  
  595. Node *AspToken(Lexer *lexer)
  596. {
  597.     Node *node;
  598.  
  599.     node = NewNode();
  600.     node->type = AspTag;
  601.     node->start = lexer->txtstart;
  602.     node->end = lexer->txtend;
  603.     return node;
  604. }
  605.  
  606. void AddStringLiteral(Lexer *lexer, char *str)
  607. {
  608.     unsigned char c;
  609.  
  610.     while((c = *str++) != '\0')
  611.         AddCharToLexer(lexer, c);
  612. }
  613.  
  614. /* find doctype element */
  615. Node *FindDocType(Node *root)
  616. {
  617.     Node *node;
  618.  
  619.     for (node = root->content; 
  620.             node && node->type != DocTypeTag; node = node->next);
  621.  
  622.     return node;
  623. }
  624.  
  625. void DiscardDocType(Node *root)
  626. {
  627.     Node *node;
  628.  
  629.     if (node = FindDocType(root))
  630.     {
  631.         if (node->prev)
  632.             node->prev->next = node->next;
  633.         else
  634.             node->parent->content = node->next;
  635.  
  636.         if (node->next)
  637.             node->next->prev = node->prev;
  638.  
  639.         node->next = null;
  640.         FreeNode(node);
  641.     }
  642. }
  643.  
  644. int FindGivenVersion(Lexer *lexer, Node *doctype)
  645. {
  646.     char *p, *s;
  647.     uint i, j;
  648.     int len;
  649.  
  650.     /* give up if all we are given is the system id for the doctype */
  651.     if (wstrncasecmp(lexer->lexbuf+doctype->start, "html SYSTEM ", 12) == 0)
  652.     {
  653.         /* but at least ensure the case is correct */
  654.         memcpy(lexer->lexbuf+doctype->start, "html SYSTEM", 11);
  655.         return 0;  /* unrecognized */
  656.     }
  657.  
  658.     if (wstrncasecmp(lexer->lexbuf+doctype->start, "html PUBLIC ", 12) == 0)
  659.         memcpy(lexer->lexbuf+doctype->start, "html PUBLIC", 11);
  660.     else
  661.         lexer->bad_doctype = yes;
  662.  
  663.     for (i = doctype->start; i < doctype->end; ++i)
  664.     {
  665.         if (lexer->lexbuf[i] == '"')
  666.         {
  667.             if (wstrncmp(lexer->lexbuf+i+1, "-//W3C//DTD ", 12) == 0)
  668.             {
  669.                 p = lexer->lexbuf + i + 13;
  670.  
  671.                 /* compute length of identifier e.g. "HTML 4.0 Transitional" */
  672.                 for (j = i + 13; j < doctype->end && lexer->lexbuf[j] != '/'; ++j);
  673.                 len = j - i - 13;
  674.  
  675.                 for (j = 1; j < W3C_VERSIONS; ++j)
  676.                 {
  677.                     s = W3C_Version[j].name;
  678.                     if (len == wstrlen(s) && wstrncmp(p, s, len) == 0)
  679.                         return W3C_Version[j].code;
  680.                 }
  681.  
  682.                 /* else unrecognized version */
  683.             }
  684.             else if (wstrncmp(lexer->lexbuf+i+1, "-//IETF//DTD ", 13) == 0)
  685.             {
  686.                 p = lexer->lexbuf + i + 14;
  687.  
  688.                 /* compute length of identifier e.g. "HTML 2.0" */
  689.                 for (j = i + 14; j < doctype->end && lexer->lexbuf[j] != '/'; ++j);
  690.                 len = j - i - 14;
  691.  
  692.                 s = W3C_Version[0].name;
  693.                 if (len == wstrlen(s) && wstrncmp(p, s, len) == 0)
  694.                     return W3C_Version[0].code;
  695.  
  696.                 /* else unrecognized version */
  697.             }
  698.             break;
  699.         }
  700.     }
  701.  
  702.     return 0;
  703. }
  704.  
  705. char *HTMLVersionName(Lexer *lexer)
  706. {
  707.     int guessed, j;
  708.  
  709.     guessed = HTMLVersion(lexer);
  710.  
  711.     for (j = 0; j < W3C_VERSIONS; ++j)
  712.     {
  713.         if (guessed == W3C_Version[j].code)
  714.         {
  715.             if (lexer->isvoyager)
  716.                 return W3C_Version[j].voyager_name;
  717.  
  718.             return W3C_Version[j].name;
  719.         }
  720.     }
  721.  
  722.     return null;
  723. }
  724.  
  725. void FixHTMLNameSpace(Lexer *lexer, Node *root, char *profile)
  726. {
  727.     Node *node;
  728.     AttVal *prev, *attr;
  729.  
  730.     for (node = root->content; 
  731.             node && node->tag != tag_html; node = node->next);
  732.  
  733.     if (node)
  734.     {
  735.         prev = null;
  736.  
  737.         for (attr = node->attributes; attr; attr = attr->next)
  738.         {
  739.             if (wstrcmp(attr->attribute, "xmlns") == 0)
  740.                 break;
  741.  
  742.             prev = attr;
  743.         }
  744.  
  745.         if (attr)
  746.         {
  747.             if (wstrcmp(attr->value, profile))
  748.             {
  749.                 ReportWarning(lexer, node, null, INCONSISTENT_NAMESPACE);
  750.                 MemFree(attr->value);
  751.                 attr->value = wstrdup(profile);
  752.             }
  753.         }
  754.         else
  755.         {
  756.             attr = (AttVal *)MemAlloc(sizeof(AttVal));
  757.             attr->delim = '"';
  758.             attr->attribute = wstrdup("xmlns");
  759.             attr->value = wstrdup(profile);
  760.             attr->dict = FindAttribute(attr);
  761.             attr->next = node->attributes;
  762.             node->attributes = attr;
  763.         }
  764.     }
  765. }
  766.  
  767. Bool SetXHTMLDocType(Lexer *lexer, Node *root)
  768. {
  769.     char *fpi, *sysid, *name_space = "http://www.w3.org/TR/xhtml1";
  770.     Node *doctype;
  771.  
  772.     if (doctype_mode == doctype_omit)
  773.     {
  774.         DiscardDocType(root);
  775.         return yes;
  776.     }
  777.  
  778.     doctype = FindDocType(root);
  779.  
  780.     if (doctype_mode == doctype_auto)
  781.     {
  782.         /* see what flavor of XHTML this document matches */
  783.         if (lexer->versions & VERS_HTML40_STRICT)
  784.         {  /* use XHTML strict */
  785.             fpi = "-//W3C//DTD XHTML 1.0 Strict//EN";
  786.             sysid = "http://www.w3.org/TR/xhtml1/DTD/strict.dtd";
  787.         }
  788.         else if (lexer->versions & VERS_LOOSE)
  789.         {
  790.             fpi = "-//W3C//DTD XHTML 1.0 Transitional//EN";
  791.             sysid = "http://www.w3.org/TR/xhtml1/DTD/transitional.dtd";
  792.         }
  793.         else if (lexer->versions & VERS_FRAMES)
  794.         {   /* use XHTML frames */
  795.             fpi = "-//W3C//DTD XHTML 1.0 Framset//EN";
  796.             sysid = "http://www.w3.org/TR/xhtml1/DTD/frameset.dtd";
  797.         }
  798.         else /* lets assume XHTML transitional */
  799.         {
  800.             fpi = "-//W3C//DTD XHTML 1.0 Transitional//EN";
  801.             sysid = "http://www.w3.org/TR/xhtml1l/DTD/transitional.dtd";
  802.         }
  803.     }
  804.     else if (doctype_mode == doctype_strict)
  805.     {
  806.         fpi = "-//W3C//DTD XHTML 1.0 Strict//EN";
  807.         sysid = "http://www.w3.org/TR/xhtml1/DTD/strict.dtd";
  808.     }
  809.     else if (doctype_mode == doctype_loose)
  810.     {
  811.         fpi = "-//W3C//DTD XHTML 1.0 Transitional//EN";
  812.         sysid = "http://www.w3.org/TR/xhtml1/DTD/transitional.dtd";
  813.     }
  814.  
  815.     FixHTMLNameSpace(lexer, root, name_space);
  816.  
  817.     if (!doctype)
  818.     {
  819.         doctype = NewNode();
  820.         doctype->type = DocTypeTag;
  821.         doctype->next = root->content;
  822.         root->content = doctype;
  823.     }
  824.  
  825.     if (doctype_mode == doctype_user && doctype_str)
  826.     {
  827.         fpi = doctype_str;
  828.         sysid = "";
  829.     }
  830.  
  831.     lexer->txtstart = lexer->txtend = lexer->lexsize;
  832.  
  833.    /* add public identifier */
  834.     AddStringLiteral(lexer, "html PUBLIC ");
  835.  
  836.     /* check if the fpi is quoted or not */
  837.     if (fpi[0] == '"')
  838.         AddStringLiteral(lexer, fpi);
  839.     else
  840.     {
  841.         AddStringLiteral(lexer, "\"");
  842.         AddStringLiteral(lexer, fpi);
  843.         AddStringLiteral(lexer, "\"");
  844.     }
  845.  
  846.     if ((unsigned)(wstrlen(sysid) + 6) >= wraplen)
  847.         AddStringLiteral(lexer, "\n\"");
  848.     else
  849.         AddStringLiteral(lexer, "\n    \"");
  850.  
  851.     /* add system identifier */
  852.     AddStringLiteral(lexer, sysid);
  853.     AddStringLiteral(lexer, "\"");
  854.  
  855.     lexer->txtend = lexer->lexsize;
  856.  
  857.     doctype->start = lexer->txtstart;
  858.     doctype->end = lexer->txtend;
  859.  
  860.     return no;
  861. }
  862.  
  863. /* fixup doctype if missing */
  864. Bool FixDocType(Lexer *lexer, Node *root)
  865. {
  866.     Node *doctype;
  867.     int guessed = VERS_HTML40_STRICT, i;
  868.  
  869.     if (lexer->bad_doctype)
  870.         ReportWarning(lexer, null, null, MALFORMED_DOCTYPE);
  871.  
  872.     if (doctype_mode == doctype_omit)
  873.     {
  874.         DiscardDocType(root);
  875.         return yes;
  876.     }
  877.  
  878.     if (XmlOut)
  879.         return yes;
  880.  
  881.     doctype = FindDocType(root);
  882.  
  883.     if (doctype_mode == doctype_strict)
  884.     {
  885.         FreeNode(doctype);
  886.         doctype = null;
  887.         guessed = VERS_HTML40_STRICT;
  888.     }
  889.     else if (doctype_mode == doctype_loose)
  890.     {
  891.         FreeNode(doctype);
  892.         doctype = null;
  893.         guessed = VERS_HTML40_LOOSE;
  894.     }
  895.     else if (doctype_mode == doctype_auto)
  896.     {
  897.         if (doctype)
  898.         {
  899.             if (lexer->doctype == VERS_UNKNOWN)
  900.                 return no;
  901.  
  902.             switch (lexer->doctype)
  903.             {
  904.             case VERS_UNKNOWN:
  905.                 return no;
  906.  
  907.             case VERS_HTML20:
  908.                 if (lexer->versions & VERS_HTML20)
  909.                     return yes;
  910.  
  911.                 break; /* to replace old version by new */
  912.  
  913.             case VERS_HTML32:
  914.                 if (lexer->versions & VERS_HTML32)
  915.                     return yes;
  916.  
  917.                 break; /* to replace old version by new */
  918.  
  919.             case VERS_HTML40_STRICT:
  920.                 if (lexer->versions & VERS_HTML40_STRICT)
  921.                     return yes;
  922.  
  923.                 break; /* to replace old version by new */
  924.  
  925.             case VERS_HTML40_LOOSE:
  926.                 if (lexer->versions & VERS_HTML40_LOOSE)
  927.                     return yes;
  928.  
  929.                 break; /* to replace old version by new */
  930.  
  931.             case VERS_FRAMES:
  932.                 if (lexer->versions & VERS_FRAMES)
  933.                     return yes;
  934.  
  935.                 break; /* to replace old version by new */
  936.             }
  937.  
  938.             ReportWarning(lexer, null, null, INCONSISTENT_VERSION);
  939.         }
  940.  
  941.         /* choose new doctype */
  942.         guessed = HTMLVersion(lexer);
  943.     }
  944.  
  945.     if (guessed == VERS_UNKNOWN)
  946.         return no;
  947.  
  948.     /* for XML use the Voyager system identifier */
  949.     if (XmlOut || XmlTags || lexer->isvoyager)
  950.     {
  951.         if (doctype)
  952.             DiscardElement(lexer, doctype);
  953.  
  954.         for (i = 0; i < W3C_VERSIONS; ++i)
  955.         {
  956.             if (guessed == W3C_Version[i].code)
  957.             {
  958.                 FixHTMLNameSpace(lexer, root, W3C_Version[i].profile);
  959.                 break;
  960.             }
  961.         }
  962.  
  963.         return yes;
  964.     }
  965.  
  966.     if (!doctype)
  967.     {
  968.         doctype = NewNode();
  969.         doctype->type = DocTypeTag;
  970.         doctype->next = root->content;
  971.         root->content = doctype;
  972.     }
  973.  
  974.     lexer->txtstart = lexer->txtend = lexer->lexsize;
  975.  
  976.    /* use the appropriate public identifier */
  977.     AddStringLiteral(lexer, "html PUBLIC ");
  978.  
  979.     if (doctype_mode == doctype_user && doctype_str)
  980.         AddStringLiteral(lexer, doctype_str);
  981.     else if (guessed == VERS_HTML20)
  982.         AddStringLiteral(lexer, "\"-//IETF//DTD HTML 2.0//EN\"");
  983.     else
  984.     {
  985.         AddStringLiteral(lexer, "\"-//W3C//DTD ");
  986.  
  987.         for (i = 0; i < W3C_VERSIONS; ++i)
  988.         {
  989.             if (guessed == W3C_Version[i].code)
  990.             {
  991.                 AddStringLiteral(lexer, W3C_Version[i].name);
  992.                 break;
  993.             }
  994.         }
  995.  
  996.         AddStringLiteral(lexer, "//EN\"");
  997.     }
  998.  
  999.     lexer->txtend = lexer->lexsize;
  1000.  
  1001.     doctype->start = lexer->txtstart;
  1002.     doctype->end = lexer->txtend;
  1003.  
  1004.     return yes;
  1005. }
  1006.  
  1007. /* ensure XML document starts with <?XML version="1.0"?> */
  1008. Bool FixXMLPI(Lexer *lexer, Node *root)
  1009. {
  1010.     Node *xml;
  1011.     char *s;
  1012.  
  1013.     if( root->content && root->content->type == ProcInsTag)
  1014.     {
  1015.         s = &lexer->lexbuf[root->content->start];
  1016.  
  1017.         if (s[0] == 'x' && s[1] == 'm' && s[2] == 'l')
  1018.             return yes;
  1019.     }
  1020.  
  1021.     xml = NewNode();
  1022.     xml->type = ProcInsTag;
  1023.     xml->next = root->content;
  1024.  
  1025.     if (root->content)
  1026.     {
  1027.         root->content->prev = xml;
  1028.         xml->next = root->content;
  1029.     }
  1030.     
  1031.     root->content = xml;
  1032.  
  1033.     lexer->txtstart = lexer->txtend = lexer->lexsize;
  1034.     AddStringLiteral(lexer, "xml version=\"1.0\"");
  1035.     lexer->txtend = lexer->lexsize;
  1036.  
  1037.     xml->start = lexer->txtstart;
  1038.     xml->end = lexer->txtend;
  1039.     return no;
  1040. }
  1041.  
  1042. Node *InferredTag(Lexer *lexer, char *name)
  1043. {
  1044.     Node *node;
  1045.  
  1046.     node = NewNode();
  1047.     node->type = StartTag;
  1048.     node->implicit = yes;
  1049.     node->element = wstrdup(name);
  1050.     node->start = lexer->txtstart;
  1051.     node->end = lexer->txtend;
  1052.     FindTag(node);
  1053.     return node;
  1054. }
  1055.  
  1056. /*
  1057.   create a text node for the contents of
  1058.   a CDATA element like style or script
  1059.   which ends with </foo> for some foo.
  1060. */
  1061. Node *GetCDATA(Lexer *lexer, Node *container)
  1062. {
  1063.     int c, lastc, start, i, len;
  1064.  
  1065.     lexer->lines = lexer->in->curline;
  1066.     lexer->columns = lexer->in->curcol;
  1067.     lexer->waswhite = no;
  1068.     lexer->txtstart = lexer->txtend = lexer->lexsize;
  1069.  
  1070.     lastc = '\0';
  1071.     start = -1;
  1072.  
  1073.     while ((c = ReadChar(lexer->in)) != EndOfStream)
  1074.     {
  1075.         /* treat \r\n as \n and \r as \n */
  1076.  
  1077.         if (c == '/' && lastc == '<')
  1078.             start = lexer->lexsize + 1;  /* to first letter */
  1079.         else if (c == '>' && start >= 0)
  1080.         {
  1081.             if (((len = lexer->lexsize - start) == wstrlen(container->element)) &&
  1082.                 wstrncasecmp(lexer->lexbuf+start, container->element, len) == 0)
  1083.             {
  1084.                 lexer->txtend = start - 2;
  1085.                 break;
  1086.             }
  1087.  
  1088.             lexer->lines = lexer->in->curline;
  1089.             lexer->columns = lexer->in->curcol - 3;
  1090.  
  1091.             ReportWarning(lexer, null, null, BAD_CDATA_CONTENT);
  1092.  
  1093.             /* if javascript insert backslash before / */
  1094.  
  1095.             if (IsJavaScript(container))
  1096.             {
  1097.                 for (i = lexer->lexsize; i > start-1; --i)
  1098.                     lexer->lexbuf[i] = lexer->lexbuf[i-1];
  1099.  
  1100.                 lexer->lexbuf[start-1] = '\\';
  1101.                 lexer->lexsize++;
  1102.             }
  1103.  
  1104.             start = -1;
  1105.         }
  1106.         else if (c == '\r')
  1107.         {
  1108.             c = ReadChar(lexer->in);
  1109.  
  1110.             if (c != '\n')
  1111.                 UngetChar(c, lexer->in);
  1112.  
  1113.             c = '\n';
  1114.         }
  1115.  
  1116.         AddCharToLexer(lexer, (uint)c);
  1117.         lexer->txtend = lexer->lexsize;
  1118.         lastc = c;
  1119.     }
  1120.  
  1121.     if (c == EndOfStream)
  1122.         ReportWarning(lexer, container, null, MISSING_ENDTAG_FOR);
  1123.  
  1124.     if (lexer->txtend > lexer->txtstart)
  1125.         return lexer->token = TextToken(lexer);
  1126.  
  1127.     return null;
  1128. }
  1129.  
  1130. void UngetToken(Lexer *lexer)
  1131. {
  1132.     lexer->pushed = yes;
  1133. }
  1134.  
  1135. /*
  1136.   modes for GetToken()
  1137.  
  1138.   MixedContent   -- for elements which don't accept PCDATA
  1139.   Preformatted       -- white space preserved as is
  1140.   IgnoreMarkup       -- for CDATA elements such as script, style
  1141. */
  1142.  
  1143. Node *GetToken(Lexer *lexer, uint mode)
  1144. {
  1145.     uint map;
  1146.     int c, lastc, comments;
  1147.     Bool isempty;
  1148.     AttVal *attributes;
  1149.  
  1150.     if (lexer->pushed)
  1151.     {
  1152.         lexer->pushed = no;
  1153.         return lexer->token;
  1154.     }
  1155.  
  1156.     /* at start of block elements, unclosed inline
  1157.        elements are inserted into the token stream */
  1158.      
  1159.     if (lexer->insert || lexer->inode)
  1160.         return InsertedToken(lexer);
  1161.  
  1162.     lexer->lines = lexer->in->curline;
  1163.     lexer->columns = lexer->in->curcol;
  1164.     lexer->waswhite = no;
  1165.  
  1166.     lexer->txtstart = lexer->txtend = lexer->lexsize;
  1167.  
  1168.     while ((c = ReadChar(lexer->in)) != EndOfStream)
  1169.     {
  1170.         if (lexer->insertspace && mode != IgnoreWhitespace)
  1171.         {
  1172.             AddCharToLexer(lexer, ' ');
  1173.             lexer->waswhite = yes;
  1174.             lexer->insertspace = no;
  1175.         }
  1176.  
  1177.         /* treat \r\n as \n and \r as \n */
  1178.  
  1179.         if (c == '\r')
  1180.         {
  1181.             c = ReadChar(lexer->in);
  1182.  
  1183.             if (c != '\n')
  1184.                 UngetChar(c, lexer->in);
  1185.  
  1186.             c = '\n';
  1187.         }
  1188.  
  1189.         AddCharToLexer(lexer, (uint)c);
  1190.  
  1191.         switch (lexer->state)
  1192.         {
  1193.             case LEX_CONTENT:  /* element content */
  1194.                 map = MAP(c);
  1195.  
  1196.                 /*
  1197.                  Discard white space if appropriate. Its cheaper
  1198.                  to do this here rather than in parser methods
  1199.                  for elements that don't have mixed content.
  1200.                 */
  1201.                 if ((map & white) && (mode == IgnoreWhitespace) 
  1202.                       && lexer->lexsize == lexer->txtstart + 1)
  1203.                 {
  1204.                     --(lexer->lexsize);
  1205.                     lexer->waswhite = no;
  1206.                     lexer->lines = lexer->in->curline;
  1207.                     lexer->columns = lexer->in->curcol;
  1208.                     continue;
  1209.                 }
  1210.  
  1211.                 if (c == '<')
  1212.                 {
  1213.                     lexer->state = LEX_GT;
  1214.                     continue;
  1215.                 }
  1216.  
  1217.                 if ((map & white) != 0)
  1218.                 {
  1219.                     /* was previous char white? */
  1220.                     if (lexer->waswhite)
  1221.                     {
  1222.                         if (mode != Preformatted && mode != IgnoreMarkup)
  1223.                         {
  1224.                             --(lexer->lexsize);
  1225.                             lexer->lines = lexer->in->curline;
  1226.                             lexer->columns = lexer->in->curcol;
  1227.                         }
  1228.                     }
  1229.                     else /* prev char wasn't white */
  1230.                     {
  1231.                         lexer->waswhite = yes;
  1232.                         lastc = c;
  1233.  
  1234.                         if (mode != Preformatted && mode != IgnoreMarkup && c != ' ')
  1235.                             ChangeChar(lexer, ' ');
  1236.                     }
  1237.  
  1238.                     continue;
  1239.                 }
  1240.                 else if (c == '&' && mode != IgnoreMarkup)
  1241.                     ParseEntity(lexer);
  1242.  
  1243.                 lexer->waswhite = no;
  1244.                 continue;
  1245.  
  1246.             case LEX_GT:  /* < */
  1247.  
  1248.                 /* check for endtag */
  1249.                 if (c == '/')
  1250.                 {
  1251.                     if ((c = ReadChar(lexer->in)) == EndOfStream)
  1252.                     {
  1253.                         UngetChar(c, lexer->in);
  1254.                         continue;
  1255.                     }
  1256.  
  1257.                     AddCharToLexer(lexer, c);
  1258.                     map = MAP(c);
  1259.  
  1260.                     if ((map & letter) != 0)
  1261.                     {
  1262.                         lexer->lexsize -= 3;
  1263.                         lexer->txtend = lexer->lexsize;
  1264.                         UngetChar(c, lexer->in);
  1265.                         lexer->state = LEX_ENDTAG;
  1266.                         lexer->lexbuf[lexer->lexsize] = '\0';  /* debug */
  1267.                         lexer->in->curcol -= 2;
  1268.  
  1269.                         /* if some text before the </ return it now */
  1270.                         if (lexer->txtend > lexer->txtstart)
  1271.                         {
  1272.                             /* trim space char before end tag */
  1273.                             if (mode == IgnoreWhitespace && lexer->lexbuf[lexer->lexsize - 1] == ' ')
  1274.                             {
  1275.                                 lexer->lexsize -= 1;
  1276.                                 lexer->txtend = lexer->lexsize;
  1277.                             }
  1278.  
  1279.                             return lexer->token = TextToken(lexer);
  1280.                         }
  1281.  
  1282.                         continue;       /* no text so keep going */
  1283.                     }
  1284.  
  1285.                     /* otherwise treat as CDATA */
  1286.                     lexer->waswhite = no;
  1287.                     lexer->state = LEX_CONTENT;
  1288.                     continue;
  1289.                 }
  1290.  
  1291.                 if (mode == IgnoreMarkup)
  1292.                 {
  1293.                     /* otherwise treat as CDATA */
  1294.                     lexer->waswhite = no;
  1295.                     lexer->state = LEX_CONTENT;
  1296.                     continue;
  1297.                 }
  1298.  
  1299.                 /*
  1300.                    look out for comments, doctype or marked sections
  1301.                    this isn't quite right, but its getting there ...
  1302.                 */
  1303.                 if (c == '!')
  1304.                 {
  1305.                     c = ReadChar(lexer->in);
  1306.  
  1307.                     if (c == '-')
  1308.                     {
  1309.                         c = ReadChar(lexer->in);
  1310.  
  1311.                         if (c == '-')
  1312.                         {
  1313.                             lexer->state = LEX_COMMENT;  /* comment */
  1314.                             lexer->lexsize -= 2;
  1315.                             lexer->txtend = lexer->lexsize;
  1316.                             comments = 0;
  1317.  
  1318.                             /* if some text before < return it now */
  1319.                             if (lexer->txtend > lexer->txtstart)
  1320.                                 return lexer->token = TextToken(lexer);
  1321.  
  1322.                             lexer->txtstart = lexer->lexsize;
  1323.                             continue;
  1324.                         }
  1325.  
  1326.                         ReportWarning(lexer, null, null, BAD_COMMENT);
  1327.                     }
  1328.                     else if (c == 'd' || c == 'D')
  1329.                     {
  1330.                         lexer->state = LEX_DOCTYPE; /* doctype */
  1331.                         lexer->lexsize -= 2;
  1332.                         lexer->txtend = lexer->lexsize;
  1333.                         mode = IgnoreWhitespace;
  1334.  
  1335.                         /* skip until white space or '>' */
  1336.  
  1337.                         for (;;)
  1338.                         {
  1339.                             c = ReadChar(lexer->in);
  1340.  
  1341.                             if (c == EndOfStream || c == '>')
  1342.                             {
  1343.                                 UngetChar(c, lexer->in);
  1344.                                 break;
  1345.                             }
  1346.  
  1347.                             map = MAP(c);
  1348.  
  1349.                             if (!(map & white))
  1350.                                 continue;
  1351.  
  1352.                             /* and skip to end of whitespace */
  1353.  
  1354.                             for (;;)
  1355.                             {
  1356.                                 c = ReadChar(lexer->in);
  1357.  
  1358.                                 if (c == EndOfStream || c == '>')
  1359.                                 {
  1360.                                     UngetChar(c, lexer->in);
  1361.                                     break;
  1362.                                 }
  1363.  
  1364.                                 map = MAP(c);
  1365.  
  1366.                                 if (map & white)
  1367.                                     continue;
  1368.  
  1369.                                 UngetChar(c, lexer->in);
  1370.                                     break;
  1371.                             }
  1372.  
  1373.                             break;
  1374.                         }
  1375.  
  1376.                         /* if some text before < return it now */
  1377.                         if (lexer->txtend > lexer->txtstart)
  1378.                             return lexer->token = TextToken(lexer);
  1379.  
  1380.                         lexer->txtstart = lexer->lexsize;
  1381.                         continue;
  1382.                     }
  1383.  
  1384.                     /* otherwise swallow chars up to and including next '>' */
  1385.                     while ((c = ReadChar(lexer->in)) != '>')
  1386.                     {
  1387.                         if (c == -1)
  1388.                         {
  1389.                             UngetChar(c, lexer->in);
  1390.                             break;
  1391.                         }
  1392.                     }
  1393.  
  1394.                     lexer->lexsize -= 2;
  1395.                     lexer->lexbuf[lexer->lexsize] = '\0';
  1396.                     lexer->state = LEX_CONTENT;
  1397.                     continue;
  1398.                 }
  1399.  
  1400.                 /*
  1401.                    processing instructions
  1402.                 */
  1403.  
  1404.                 if (c == '?')
  1405.                 {
  1406.                     lexer->lexsize -= 2;
  1407.                     lexer->state = LEX_PROCINSTR;
  1408.                     lexer->txtend = lexer->lexsize;
  1409.  
  1410.                     /* if some text before < return it now */
  1411.                     if (lexer->txtend > lexer->txtstart)
  1412.                         return lexer->token = TextToken(lexer);
  1413.  
  1414.                     lexer->txtstart = lexer->lexsize;
  1415.                     continue;
  1416.                 }
  1417.  
  1418.                 /* Microsoft ASP's e.g. <% ... server-code ... %> */
  1419.                 if (c == '%')
  1420.                 {
  1421.                     lexer->lexsize -= 2;
  1422.                     lexer->state = LEX_ASP;
  1423.                     lexer->txtend = lexer->lexsize;
  1424.  
  1425.                     /* if some text before < return it now */
  1426.                     if (lexer->txtend > lexer->txtstart)
  1427.                         return lexer->token = TextToken(lexer);
  1428.  
  1429.                     lexer->txtstart = lexer->lexsize;
  1430.                     continue;
  1431.                 }
  1432.  
  1433.                 map = MAP(c);
  1434.  
  1435.                 /* check for start tag */
  1436.                 if ((map & letter) != 0)
  1437.                 {
  1438.                     UngetChar(c, lexer->in);     /* push back letter */
  1439.                     lexer->lexsize -= 2;      /* discard "<" + letter */
  1440.                     lexer->txtend = lexer->lexsize;
  1441.                     lexer->state = LEX_STARTTAG;         /* ready to read tag name */
  1442.  
  1443.                     /* if some text before < return it now */
  1444.                     if (lexer->txtend > lexer->txtstart)
  1445.                         return lexer->token = TextToken(lexer);
  1446.  
  1447.                     continue;       /* no text so keep going */
  1448.                 }
  1449.  
  1450.                 /* otherwise treat as CDATA */
  1451.                 lexer->state = LEX_CONTENT;
  1452.                 lexer->waswhite = no;
  1453.                 continue;
  1454.  
  1455.             case LEX_ENDTAG:  /* </letter */
  1456.                 lexer->txtstart = lexer->lexsize - 1;
  1457.                 lexer->in->curcol += 2;
  1458.                 c = ParseTagName(lexer);
  1459.                 lexer->token = TagToken(lexer, EndTag);  /* create endtag token */
  1460.                 lexer->lexsize = lexer->txtend = lexer->txtstart;
  1461.  
  1462.                 /* skip to '>' */
  1463.                 while (c != '>')
  1464.                 {
  1465.                     c = ReadChar(lexer->in);
  1466.  
  1467.                     if (c == EndOfStream)
  1468.                         break;
  1469.                 }
  1470.  
  1471.                 if (c == EndOfStream)
  1472.                 {
  1473.                     UngetChar(c, lexer->in);
  1474.                     continue;
  1475.                 }
  1476.  
  1477.                 lexer->state = LEX_CONTENT;
  1478.                 lexer->waswhite = no;
  1479.                 return lexer->token;  /* the endtag token */
  1480.  
  1481.             case LEX_STARTTAG: /* first letter of tagname */
  1482.                 lexer->txtstart = lexer->lexsize - 1; /* set txtstart to first letter */
  1483.                 c = ParseTagName(lexer);
  1484.                 isempty = no;
  1485.                 attributes = null;
  1486.                 lexer->token = TagToken(lexer, (isempty ? StartEndTag : StartTag));
  1487.  
  1488.                 /* parse attributes, consuming closing ">" */
  1489.                 if (c != '>')
  1490.                 {
  1491.                     if (c == '/')
  1492.                         UngetChar(c, lexer->in);
  1493.  
  1494.                     attributes = ParseAttrs(lexer, &isempty);
  1495.                 }
  1496.  
  1497.                 if (isempty)
  1498.                     lexer->token->type = StartEndTag;
  1499.  
  1500.                 lexer->token->attributes = attributes;
  1501.                 lexer->lexsize = lexer->txtend = lexer->txtstart;
  1502.  
  1503.  
  1504.                 /* swallow newline following start tag */
  1505.                 /* special check needed for CRLF sequence */
  1506.  
  1507.                 c = ReadChar(lexer->in);
  1508.  
  1509.                 if (c == '\r')
  1510.                 {
  1511.                     c = ReadChar(lexer->in);
  1512.  
  1513.                     if (c != '\n')
  1514.                         UngetChar(c, lexer->in);
  1515.                 }
  1516.                 else if (c != '\n' && c != '\f')
  1517.                     UngetChar(c, lexer->in);
  1518.  
  1519.                 lexer->state = LEX_CONTENT;
  1520.                 lexer->waswhite = yes;  /* to swallow leading whitespace */
  1521.  
  1522.                 if (lexer->token->tag == null)
  1523.                     ReportError(lexer, null, lexer->token, UNKNOWN_ELEMENT);
  1524.                 else if (!XmlTags)
  1525.                 {
  1526.                     lexer->versions &= lexer->token->tag->versions;
  1527.                     
  1528.                     if (lexer->token->tag->versions & VERS_PROPRIETARY)
  1529.                     {
  1530.                         if (!MakeClean && (lexer->token->tag == tag_nobr ||
  1531.                                                 lexer->token->tag == tag_wbr))
  1532.                             ReportWarning(lexer, null, lexer->token, PROPRIETARY_ELEMENT);
  1533.                     }
  1534.  
  1535.                     if (lexer->token->tag->chkattrs)
  1536.                         lexer->token->tag->chkattrs(lexer, lexer->token);
  1537.                     else
  1538.                         CheckAttributes(lexer, lexer->token);
  1539.                 }
  1540.  
  1541.                  return lexer->token;  /* return start tag */
  1542.  
  1543.             case LEX_COMMENT:  /* seen <!-- so look for --> */
  1544.  
  1545.                 /* look for 1st - */
  1546.                 if (c != '-')
  1547.                 {
  1548.                     if (comments > 2 && c == '>')
  1549.                         ReportWarning(lexer, null, null, BAD_COMMENT);
  1550.  
  1551.                     comments = -1;
  1552.                     continue;
  1553.                 }
  1554.  
  1555.                 /* now look for 2nd - */
  1556.  
  1557.                 c = ReadChar(lexer->in);
  1558.  
  1559.                 if (c == EndOfStream)
  1560.                 {
  1561.                     ReportWarning(lexer, null, null, BAD_COMMENT);
  1562.                     UngetChar(c, lexer->in);
  1563.                     continue;
  1564.                 }
  1565.  
  1566.                 AddCharToLexer(lexer, c);
  1567.  
  1568.                 if (c != '-')
  1569.                 {
  1570.                     comments = 0;
  1571.                     continue;
  1572.                 }
  1573.  
  1574.                 lexer->state = LEX_ENDCOMMENT;
  1575.                 continue;
  1576.  
  1577.             case LEX_ENDCOMMENT:  /* seen <!-- .... -- */
  1578.                 if (c == '>')
  1579.                 {
  1580.                     lexer->lexsize -= 3;
  1581.                     lexer->txtend = lexer->lexsize;
  1582.                     lexer->lexbuf[lexer->lexsize] = '\0';
  1583.                     lexer->state = LEX_CONTENT;
  1584.                     lexer->waswhite = no;
  1585.                     return lexer->token = CommentToken(lexer);
  1586.                 }
  1587.  
  1588.                 /* only whitespace is allowed between comments */
  1589.  
  1590.                 for (;;)
  1591.                 {
  1592.                     map = MAP(c);
  1593.  
  1594.                     if((map & white) == 0)
  1595.                         break;
  1596.  
  1597.                     comments = 0;
  1598.                     c = ReadChar(lexer->in);
  1599.                     AddCharToLexer(lexer, c);
  1600.                 }
  1601.  
  1602.                 /* '--' marks the start of another comment */
  1603.  
  1604.                 if (c == '-')
  1605.                 {
  1606.                     c = ReadChar(lexer->in);
  1607.                     AddCharToLexer(lexer, c);
  1608.  
  1609.                     if (c == '-')
  1610.                     {
  1611.                         lexer->state = LEX_COMMENT;  /* comment */
  1612.                         lexer->txtend = lexer->lexsize;
  1613.  
  1614.                         /*
  1615.                          rule of thumb for detecting error where
  1616.                          author has forgetten to close the comment
  1617.                         */
  1618.                         if (comments >= 0)
  1619.                             comments++;
  1620.                         continue;
  1621.                     }
  1622.                 }
  1623.  
  1624.                 /*
  1625.                   SGML comment syntax is truly daft!!!
  1626.  
  1627.                   A comment declaration consists of `<!' followed by zero or
  1628.                   more comments followed by `>'. Each comment starts with
  1629.                   `--' and includes all text up to and including the next
  1630.                   occurrence of `--'. In a comment declaration, white space
  1631.                   is allowed after each comment, but not before the first
  1632.                   comment.  The entire comment declaration is ignored.
  1633.  
  1634.                     <!-- another -- -- comment -->
  1635.                     <!-- --->  is bad, so is <!-- foo ----- bar -->
  1636.                 */
  1637.  
  1638.                 /* set error position just before offending chararcter */
  1639.                 lexer->lines = lexer->in->curline;
  1640.                 lexer->columns = lexer->in->curcol - 1;
  1641.                 ReportWarning(lexer, null, null, BAD_COMMENT);
  1642.  
  1643.                 /*
  1644.                    this is extremely likely to be the intended end of
  1645.                    the comment, so make it so, to avoid knock on errors
  1646.                 */
  1647.                 if (c == '>')
  1648.                 {
  1649.                     lexer->lexsize -= 3;
  1650.                     lexer->txtend = lexer->lexsize;
  1651.                     lexer->lexbuf[lexer->lexsize] = '\0';
  1652.                     lexer->state = LEX_CONTENT;
  1653.                     lexer->waswhite = no;
  1654.                     return lexer->token = CommentToken(lexer);
  1655.                 }
  1656.  
  1657.  
  1658.                 /* treat other chars as part of the comment*/
  1659.                 lexer->state = LEX_COMMENT;  /* comment */
  1660.                 UngetChar(c, lexer->in);
  1661.                 continue;
  1662.  
  1663.  
  1664.             case LEX_DOCTYPE:  /* seen <!d so look for '>' munging whitespace */
  1665.                 map = MAP(c);
  1666.  
  1667.                 if (map & white)
  1668.                 {
  1669.                     if (lexer->waswhite)
  1670.                         lexer->lexsize -= 1;
  1671.  
  1672.                     lexer->waswhite = yes;
  1673.                 }
  1674.                 else
  1675.                     lexer->waswhite = no;
  1676.  
  1677.                 if (c != '>')
  1678.                     continue;
  1679.  
  1680.                 lexer->lexsize -= 1;
  1681.                 lexer->txtend = lexer->lexsize;
  1682.                 lexer->lexbuf[lexer->lexsize] = '\0';
  1683.                 lexer->state = LEX_CONTENT;
  1684.                 lexer->waswhite = no;
  1685.                 lexer->token = DocTypeToken(lexer);
  1686.                 /* make a note of the version named by the doctype */
  1687.                 lexer->doctype = FindGivenVersion(lexer, lexer->token);
  1688.                 return lexer->token;
  1689.  
  1690.             case LEX_PROCINSTR:  /* seen <? so look for '>' */
  1691.                 if (XmlPIs)  /* insist on ?> as terminator */
  1692.                 {
  1693.                     if (c != '?')
  1694.                         continue;
  1695.  
  1696.                     /* now look for '>' */
  1697.                     c = ReadChar(lexer->in);
  1698.  
  1699.                     if (c == EndOfStream)
  1700.                     {
  1701.                         ReportWarning(lexer, null, null, UNEXPECTED_END_OF_FILE);
  1702.                         UngetChar(c, lexer->in);
  1703.                         continue;
  1704.                     }
  1705.  
  1706.                     AddCharToLexer(lexer, c);
  1707.                 }
  1708.  
  1709.                 if (c != '>')
  1710.                     continue;
  1711.  
  1712.                 lexer->lexsize -= 1;
  1713.                 lexer->txtend = lexer->lexsize;
  1714.                 lexer->lexbuf[lexer->lexsize] = '\0';
  1715.                 lexer->state = LEX_CONTENT;
  1716.                 lexer->waswhite = no;
  1717.                 return lexer->token = PIToken(lexer);
  1718.  
  1719.             case LEX_ASP:  /* seen <% so look for "%>" */
  1720.                 if (c != '%')
  1721.                     continue;
  1722.  
  1723.                 /* now look for '>' */
  1724.                 c = ReadChar(lexer->in);
  1725.  
  1726.  
  1727.                 if (c != '>')
  1728.                 {
  1729.                     UngetChar(c, lexer->in);
  1730.                     continue;
  1731.                 }
  1732.  
  1733.                 lexer->lexsize -= 1;
  1734.                 lexer->txtend = lexer->lexsize;
  1735.                 lexer->lexbuf[lexer->lexsize] = '\0';
  1736.                 lexer->state = LEX_CONTENT;
  1737.                 lexer->waswhite = no;
  1738.                 return lexer->token = AspToken(lexer);
  1739.         }
  1740.     }
  1741.  
  1742.     if (lexer->state == LEX_CONTENT)  /* text string */
  1743.     {
  1744.         lexer->txtend = lexer->lexsize;
  1745.  
  1746.         if (lexer->txtend > lexer->txtstart)
  1747.         {
  1748.             UngetChar(c, lexer->in);
  1749.  
  1750.             if (lexer->lexbuf[lexer->lexsize - 1] == ' ')
  1751.             {
  1752.                 lexer->lexsize -= 1;
  1753.                 lexer->txtend = lexer->lexsize;
  1754.             }
  1755.  
  1756.             return lexer->token = TextToken(lexer);
  1757.         }
  1758.     }
  1759.     else if (lexer->state == LEX_COMMENT) /* comment */
  1760.     {
  1761.         if (c == EndOfStream)
  1762.             ReportWarning(lexer, null, null, BAD_COMMENT);
  1763.  
  1764.         lexer->txtend = lexer->lexsize;
  1765.         lexer->lexbuf[lexer->lexsize] = '\0';
  1766.         lexer->state = LEX_CONTENT;
  1767.         lexer->waswhite = no;
  1768.         return lexer->token = CommentToken(lexer);
  1769.     }
  1770.  
  1771.     return 0;
  1772. }
  1773.  
  1774. static void MapStr(char *str, uint code)
  1775. {
  1776.     uint i;
  1777.  
  1778.     while (*str)
  1779.     {
  1780.         i = (uint)(*str++);
  1781.         lexmap[i] |= code;
  1782.     }
  1783. }
  1784.  
  1785. void InitMap(void)
  1786. {
  1787.     MapStr("\r\n\f", newline|white);
  1788.     MapStr(" \t", white);
  1789.     MapStr("-.:_", namechar);
  1790.     MapStr("0123456789", digit|namechar);
  1791.     MapStr("abcdefghijklmnopqrstuvwxyz", lowercase|letter|namechar);
  1792.     MapStr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", uppercase|letter|namechar);
  1793. }
  1794.  
  1795. /*
  1796.  parser for ASP within start tags
  1797.  
  1798.  Some people use ASP for to customize attributes
  1799.  Tidy isn't really well suited to dealing with ASP
  1800.  This is a workaround for attributes, but won't
  1801.  deal with the case where the ASP is used to tailor
  1802.  the attribute value. Here is an example of a work
  1803.  around for using ASP in attribute values:
  1804.  
  1805.   href="<%=rsSchool.Fields("ID").Value%>"
  1806.  
  1807.  where the ASP that generates the attribute value
  1808.  is masked from Tidy by the quotemarks.
  1809.  
  1810. */
  1811.  
  1812. Node *ParseAsp(Lexer *lexer)
  1813. {
  1814.     uint c;
  1815.     Node *asp = null;
  1816.  
  1817.     lexer->txtstart = lexer->lexsize;
  1818.  
  1819.     for (;;)
  1820.     {
  1821.         c = ReadChar(lexer->in);
  1822.         AddCharToLexer(lexer, c);
  1823.  
  1824.  
  1825.         if (c != '%')
  1826.             continue;
  1827.  
  1828.         c = ReadChar(lexer->in);
  1829.         AddCharToLexer(lexer, c);
  1830.  
  1831.         if (c == '>')
  1832.             break;
  1833.     }
  1834.  
  1835.     lexer->lexsize -= 2;
  1836.     lexer->txtend = lexer->lexsize;
  1837.  
  1838.     if (lexer->txtend > lexer->txtstart)
  1839.         asp = AspToken(lexer);
  1840.  
  1841.     lexer->txtstart = lexer->txtend;
  1842.     return asp;
  1843. }   
  1844.  
  1845.  
  1846.  
  1847. /* consumes the '>' terminating start tags */
  1848. char  *ParseAttribute(Lexer *lexer, Bool *isempty, Node **asp)
  1849. {
  1850.     int map, start, len = 0;
  1851.     char *attr;
  1852.     uint c;
  1853.  
  1854.     *asp = null;  /* clear asp pointer */
  1855.  
  1856.  /* skip white space before the attribute */
  1857.  
  1858.     for (;;)
  1859.     {
  1860.         c = ReadChar(lexer->in);
  1861.  
  1862.  
  1863.         if (c == '/')
  1864.         {
  1865.             c = ReadChar(lexer->in);
  1866.  
  1867.             if (c == '>')
  1868.             {
  1869.                 *isempty = yes;
  1870.                 return null;
  1871.             }
  1872.  
  1873.             UngetChar(c, lexer->in);
  1874.             c = '/';
  1875.             break;
  1876.         }
  1877.  
  1878.         if (c == '>')
  1879.             return null;
  1880.  
  1881.         if (c =='<')
  1882.         {
  1883.             c = ReadChar(lexer->in);
  1884.  
  1885.             if (c == '%')
  1886.             {
  1887.                 *asp = ParseAsp(lexer);
  1888.                 return null;
  1889.             }
  1890.  
  1891.             UngetChar(c, lexer->in);
  1892.             ReportAttrError(lexer, lexer->token, null, UNEXPECTED_GT);
  1893.             return null;
  1894.         }
  1895.  
  1896.         if (c == '"' || c == '\'')
  1897.         {
  1898.             ReportAttrError(lexer, lexer->token, null, UNEXPECTED_QUOTEMARK);
  1899.             continue;
  1900.         }
  1901.  
  1902.         if (c == EndOfStream)
  1903.         {
  1904.             ReportAttrError(lexer, lexer->token, null, UNEXPECTED_END_OF_FILE);
  1905.             UngetChar(c, lexer->in);
  1906.             return null;
  1907.         }
  1908.  
  1909.         map = MAP(c);
  1910.  
  1911.         if ((map & white) == 0)
  1912.            break;
  1913.     }
  1914.  
  1915.     start = lexer->lexsize;
  1916.  
  1917.     for (;;)
  1918.     {
  1919.      /* but push back '=' for parseValue() */
  1920.         if (c == '=' || c == '>')
  1921.         {
  1922.             UngetChar(c, lexer->in);
  1923.             break;
  1924.         }
  1925.  
  1926.         if (c == '<' || c == EndOfStream)
  1927.         {
  1928.             UngetChar(c, lexer->in);
  1929.             break;
  1930.         }
  1931.  
  1932.         map = MAP(c);
  1933.  
  1934.         if ((map & white) != 0)
  1935.             break;
  1936.  
  1937.      /* what should be done about non-namechar characters? */
  1938.      /* currently these are incorporated into the attr name */
  1939.  
  1940.         if (!XmlTags && (map & uppercase) != 0)
  1941.             c += (uint)('a' - 'A');
  1942.  
  1943.         ++len;
  1944.         AddCharToLexer(lexer, c);
  1945.  
  1946.         c = ReadChar(lexer->in);
  1947.     }
  1948.  
  1949.     attr = (len > 0 ? wstrndup(lexer->lexbuf+start, len) : null);
  1950.     lexer->lexsize = start;
  1951.  
  1952.     return attr;
  1953. }
  1954.  
  1955. /* values start with "=" or " = " etc. */
  1956. /* doesn't consume the ">" at end of start tag */
  1957.  
  1958. char *ParseValue(Lexer *lexer, char *name, Bool foldCase, Bool *isempty, int *pdelim)
  1959. {
  1960.     int len = 0, start, map;
  1961.     Bool seen_gt = no;
  1962.     uint c, lastc, delim, quotewarning;
  1963.     char *value;
  1964.  
  1965.     delim = (char) 0;
  1966.     *pdelim = '"';
  1967.  
  1968.  /* skip white space before the '=' */
  1969.  
  1970.     for (;;)
  1971.     {
  1972.         c = ReadChar(lexer->in);
  1973.  
  1974.         if (c == EndOfStream)
  1975.         {
  1976.             UngetChar(c, lexer->in);
  1977.             break;
  1978.         }
  1979.  
  1980.         map = MAP(c);
  1981.  
  1982.         if ((map & white) == 0)
  1983.            break;
  1984.     }
  1985.  
  1986. /*
  1987.   c should be '=' if there is a value
  1988.   other legal possibilities are white
  1989.   space, '/' and '>'
  1990. */
  1991.  
  1992.     if (c != '=')
  1993.     {
  1994.         UngetChar(c, lexer->in);
  1995.         return null;
  1996.     }
  1997.  
  1998.  /* skip white space after '=' */
  1999.  
  2000.     for (;;)
  2001.     {
  2002.         c = ReadChar(lexer->in);
  2003.  
  2004.         if (c == EndOfStream)
  2005.         {
  2006.             UngetChar(c, lexer->in);
  2007.             break;
  2008.         }
  2009.  
  2010.         map = MAP(c);
  2011.  
  2012.         if ((map & white) == 0)
  2013.            break;
  2014.     }
  2015.  
  2016.  /* check for quote marks */
  2017.  
  2018.     if (c == '"' || c == '\'')
  2019.         delim = c;
  2020.     else
  2021.         UngetChar(c, lexer->in);
  2022.  
  2023.  /*
  2024.    and read the value string
  2025.    check for quote mark if needed
  2026.  */
  2027.  
  2028.     quotewarning = 0;
  2029.     start = lexer->lexsize;
  2030.     c = '\0';
  2031.  
  2032.     for (;;)
  2033.     {
  2034.         lastc = c;  /* track last character */
  2035.         c = ReadChar(lexer->in);
  2036.  
  2037.         if (c == EndOfStream)
  2038.         {
  2039.             ReportAttrError(lexer, lexer->token, null, UNEXPECTED_END_OF_FILE);
  2040.             UngetChar(c, lexer->in);
  2041.             break;
  2042.         }
  2043.  
  2044.         if (delim == (char)0)
  2045.         {
  2046.             if (c == '>')
  2047.             {
  2048.                 UngetChar(c, lexer->in);
  2049.                 break;
  2050.             }
  2051.  
  2052.             if (c == '<')
  2053.             {
  2054.                 UngetChar(c, lexer->in);
  2055.                 ReportAttrError(lexer, lexer->token, null, UNEXPECTED_GT);
  2056.                 break;
  2057.             }
  2058.  
  2059.             /*
  2060.              For cases like <br clear=all/> need to avoid treating /> as
  2061.              part of the attribute value, however care is needed to avoid
  2062.              so treating <a href=http://www.acme.com/> in this way, which
  2063.              would map the <a> tag to <a href="http://www.acme.com"/>
  2064.             */
  2065.             if (c == '/')
  2066.             {
  2067.                 /* peek ahead in case of /> */
  2068.                 c = ReadChar(lexer->in);
  2069.  
  2070.                 if (c == '>' && !IsUrl(name))
  2071.                 {
  2072.                     *isempty = yes;
  2073.                     UngetChar(c, lexer->in);
  2074.                     break;
  2075.                 }
  2076.  
  2077.                 /* unget peeked char */
  2078.                 UngetChar(c, lexer->in);
  2079.                 c = '/';
  2080.             }
  2081.         }
  2082.         else  /* delim is '\'' or '"' */
  2083.         {
  2084.             if (c == delim)
  2085.                 break;
  2086.  
  2087.             /* treat CRLF, CR and LF as single line break */
  2088.  
  2089.             if (c == '\r')
  2090.             {
  2091.                 if ((c = ReadChar(lexer->in)) != '\n')
  2092.                     UngetChar(c, lexer->in);
  2093.  
  2094.                 c = '\n';
  2095.             }
  2096.  
  2097.             if (c == '\n' || c == '<' || c == '>')
  2098.                 ++quotewarning;
  2099.  
  2100.             if (c == '>')
  2101.                 seen_gt = yes;
  2102.         }
  2103.  
  2104.         if (c == '&')
  2105.         {
  2106.             AddCharToLexer(lexer, c);
  2107.             ParseEntity(lexer);
  2108.             continue;
  2109.         }
  2110.  
  2111.         /*
  2112.          kludge for JavaScript attribute values
  2113.          with line continuations in string literals
  2114.         */
  2115.         if (c == '\\')
  2116.         {
  2117.             c = ReadChar(lexer->in);
  2118.  
  2119.             if (c != '\n')
  2120.             {
  2121.                 UngetChar(c, lexer->in);
  2122.                 c = '\\';
  2123.             }
  2124.         }
  2125.  
  2126.         map = MAP(c);
  2127.  
  2128.         if (map & white)
  2129.         {
  2130.             if (delim == (char)0)
  2131.                 break;
  2132.  
  2133.             c = ' ';
  2134.  
  2135.             if (lastc == ' ')
  2136.                 continue;
  2137.         }
  2138.         else if (foldCase && (map & uppercase) != 0)
  2139.             c += (uint)('a' - 'A');
  2140.  
  2141.         AddCharToLexer(lexer, c);
  2142.     }
  2143.  
  2144.     if (quotewarning > 10 && seen_gt)
  2145.     {
  2146.         /*
  2147.            there is almost certainly a missing trailling quote mark
  2148.            as we have see too many newlines, < or > characters.
  2149.  
  2150.            an exception is made for Javascript attributes and the
  2151.            javascript URL scheme which may legitimately include < and >
  2152.         */
  2153.         if (!IsScript(name) &&
  2154.             !(IsUrl(name) && wstrncmp(lexer->lexbuf+start, "javascript:", 11) == 0))
  2155.                 ReportError(lexer, null, null, SUSPECTED_MISSING_QUOTE); 
  2156.     }
  2157.  
  2158.     len = lexer->lexsize - start;
  2159.     lexer->lexsize = start;
  2160.  
  2161.  
  2162.     if (len > 0 || delim)
  2163.         value = wstrndup(lexer->lexbuf+start, len);
  2164.     else
  2165.         value = null;
  2166.  
  2167.     /* note delimiter if given */
  2168.     *pdelim = (delim ? delim : '"');
  2169.  
  2170.     return value;
  2171. }
  2172.  
  2173. Bool IsValidAttrName( char *attr)
  2174. {
  2175.     uint map;
  2176.     uint c;
  2177.     int i = 0;
  2178.  
  2179.     for( i = 0; i < wstrlen(attr); i++)
  2180.     {
  2181.         c = attr[i];
  2182.         map = MAP(c);
  2183.  
  2184.         if (map & namechar)
  2185.             continue;
  2186.  
  2187.         return no;
  2188.     }
  2189.  
  2190.     return yes;
  2191. }
  2192.  
  2193. /* swallows closing '>' */
  2194.  
  2195. AttVal *ParseAttrs(Lexer *lexer, Bool *isempty)
  2196. {
  2197.     AttVal *av, *list;
  2198.     char *attribute, *value;
  2199.     int delim;
  2200.     Node *asp;
  2201.  
  2202.     list = null;
  2203.  
  2204.     for (; !EndOfInput(lexer);)
  2205.     {
  2206.         attribute = ParseAttribute(lexer, isempty, &asp);
  2207.  
  2208.         if (attribute == null)
  2209.         {
  2210.             /* check if attributes are created by ASP markup */
  2211.             if (asp)
  2212.             {
  2213.                 av = (AttVal *)MemAlloc(sizeof(AttVal));
  2214.                 av->next =list; 
  2215.                 av->delim = '\0';
  2216.                 av->asp = asp;
  2217.                 av->attribute = null;
  2218.                 av->value = null;
  2219.                 av->dict = null;
  2220.                 list = av;
  2221.                 continue;
  2222.             }
  2223.  
  2224.             break;
  2225.         }
  2226.  
  2227.         value = ParseValue(lexer, attribute, no, isempty, &delim);
  2228.  
  2229.         if (attribute && IsValidAttrName(attribute))
  2230.         {
  2231.             av = (AttVal *)MemAlloc(sizeof(AttVal));
  2232.             av->next =list; 
  2233.             av->delim = delim;
  2234.             av->asp = null;
  2235.             av->attribute = attribute;
  2236.             av->value = value;
  2237.             av->dict = FindAttribute(av);
  2238.             list = av;
  2239.         }
  2240.         else
  2241.         {
  2242.             ReportAttrError(lexer, lexer->token, attribute, BAD_ATTRIBUTE_VALUE);
  2243.             MemFree(attribute);
  2244.             MemFree(value);
  2245.         }
  2246.     }
  2247.  
  2248.     return list;
  2249. }
  2250.  
  2251.