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

  1. /*
  2.   clean.c -- clean up misuse of presentation markup
  3.  
  4.   (c) 1998 (W3C) MIT, INRIA, Keio University
  5.   See tidy.c for the copyright notice.
  6.  
  7.   Filters from other formats such as Microsoft Word
  8.   often make excessive use of presentation markup such
  9.   as font tags, B, I, and the align attribute. By applying
  10.   a set of production rules, it is straight forward to
  11.   transform this to use CSS.
  12.  
  13.   Some rules replace some of the children of an element by
  14.   style properties on the element, e.g.
  15.  
  16.   <p><b>...</b></p> -> <p style="font-weight: bold">...</p>
  17.  
  18.   Such rules are applied to the element's content and then
  19.   to the element itself until none of the rules more apply.
  20.   Having applied all the rules to an element, it will have
  21.   a style attribute with one or more properties. 
  22.  
  23.   Other rules strip the element they apply to, replacing
  24.   it by style properties on the contents, e.g.
  25.   
  26.   <dir><li><p>...</li></dir> -> <p style="margin-left 1em">...
  27.       
  28.   These rules are applied to an element before processing
  29.   its content and replace the current element by the first
  30.   element in the exposed content.
  31.  
  32.   After applying both sets of rules, you can replace the
  33.   style attribute by a class value and style rule in the
  34.   document head. To support this, an association of styles
  35.   and class names is built.
  36.  
  37.   A naive approach is to rely on string matching to test
  38.   when two property lists are the same. A better approach
  39.   would be to first sort the properties before matching.
  40. */
  41.  
  42. #include <stdio.h>
  43. #include <stdlib.h>
  44. #include <string.h>
  45. #include "platform.h"
  46. #include "html.h"
  47.  
  48. Node *CleanNode(Lexer *lexer, Node *node);
  49.  
  50. void FreeStyleProps(StyleProp *props)
  51. {
  52.     StyleProp *next;
  53.  
  54.     while (props)
  55.     {
  56.         next = props->next;
  57.         MemFree(props->name);
  58.         MemFree(props->value);
  59.         MemFree(props);
  60.         props = next;
  61.     }
  62. }
  63.  
  64. StyleProp *InsertProperty(StyleProp *props, char *name, char *value)
  65. {
  66.     StyleProp *first, *prev, *prop;
  67.     int cmp;
  68.  
  69.     prev = null;
  70.     first = props;
  71.  
  72.     while (props)
  73.     {
  74.         cmp = wstrcmp(props->name, name);
  75.  
  76.         if (cmp == 0)
  77.         {
  78.             /* need to merge values */
  79.             break;
  80.         }
  81.  
  82.         if (cmp > 0)
  83.         {
  84.             /* insert before this */
  85.  
  86.             prop = (StyleProp *)MemAlloc(sizeof(StyleProp));
  87.             prop->name = wstrdup(name);
  88.             prop->value = wstrdup(value);
  89.             prop->next = props;
  90.  
  91.             if (prev)
  92.                 prev->next = prop;
  93.             else
  94.                 first = prop;
  95.  
  96.             return first;
  97.         }
  98.  
  99.         prev = props;
  100.         props = props->next;
  101.     }
  102.  
  103.     prop = (StyleProp *)MemAlloc(sizeof(StyleProp));
  104.     prop->name = wstrdup(name);
  105.     prop->value = wstrdup(value);
  106.     prop->next = null;
  107.  
  108.     if (prev)
  109.         prev->next = prop;
  110.     else
  111.         first = prop;
  112.  
  113.     return first;
  114. }
  115.  
  116. /*
  117.  Create sorted linked list of properties from style string
  118.  It temporarily places nulls in place of ':' and ';' to
  119.  delimit the strings for the property name and value.
  120.  Some systems don't allow you to null literal strings,
  121.  so to avoid this, a copy is made first.
  122. */
  123. StyleProp *CreateProps(StyleProp *prop, char *style)
  124. {
  125.     char *name, *value, *name_end, *value_end;
  126.     Bool more;
  127.  
  128.     style = wstrdup(style);
  129.     name = style;
  130.  
  131.     while (*name)
  132.     {
  133.         while (*name == ' ')
  134.             ++name;
  135.  
  136.         name_end = name;
  137.  
  138.         while (*name_end)
  139.         {
  140.             if (*name_end == ':')
  141.             {
  142.                 value = name_end + 1;
  143.                 break;
  144.             }
  145.  
  146.             ++name_end;
  147.         }
  148.  
  149.         if (*name_end != ':')
  150.             break;
  151.  
  152.         while (*value == ' ')
  153.             ++value;
  154.  
  155.         value_end = value;
  156.         more = no;
  157.  
  158.         while (*value_end)
  159.         {
  160.             if (*value_end == ';')
  161.             {
  162.                 more = yes;
  163.                 break;
  164.             }
  165.  
  166.             ++value_end;
  167.         }
  168.  
  169.         *name_end = '\0';
  170.         *value_end = '\0';
  171.  
  172.         prop = InsertProperty(prop, name, value);
  173.         *name_end = ':';
  174.  
  175.         if (more)
  176.         {
  177.             *value_end = ';';
  178.             name = value_end + 1;
  179.             continue;
  180.         }
  181.  
  182.         break;
  183.     }
  184.  
  185.     MemFree(style);  /* free temporary copy */
  186.     return prop;
  187. }
  188.  
  189. char *CreatePropString(StyleProp *props)
  190. {
  191.     char *style, *p, *s;
  192.     int len;
  193.     StyleProp *prop;
  194.  
  195.     /* compute length */
  196.  
  197.     for (len = 0, prop = props; prop; prop = prop->next)
  198.     {
  199.         len += wstrlen(prop->name) + 2;
  200.         len += wstrlen(prop->value) + 2;
  201.     }
  202.  
  203.     style = (char *)MemAlloc(len+1);
  204.  
  205.     for (p = style, prop = props; prop; prop = prop->next)
  206.     {
  207.         s = prop->name;
  208.  
  209.         while(*p++ = *s++);
  210.  
  211.         *--p = ':';
  212.         *++p = ' ';
  213.         ++p;
  214.  
  215.         s = prop->value;
  216.         while(*p++ = *s++);
  217.  
  218.         if (prop->next == null)
  219.             break;
  220.  
  221.         *--p = ';';
  222.         *++p = ' ';
  223.         ++p;
  224.     }
  225.  
  226.     return style;
  227. }
  228.  
  229. /*
  230.   create string with merged properties
  231. */
  232. char *AddProperty(char *style, char *property)
  233. {
  234.     StyleProp *prop;
  235.  
  236.     prop = CreateProps(null, style);
  237.     prop = CreateProps(prop, property);
  238.     style = CreatePropString(prop);
  239.     FreeStyleProps(prop);
  240.     return style;
  241. }
  242.  
  243. void FreeStyles(Lexer *lexer)
  244. {
  245.     Style *style, *next;
  246.  
  247.     for (style = lexer->styles; style; style = next)
  248.     {
  249.         next = style->next;
  250.         
  251.         MemFree(style->tag);
  252.         MemFree(style->tag_class);
  253.         MemFree(style->properties);
  254.         MemFree(style);
  255.     }
  256. }
  257.  
  258. char *GensymClass(char *tag)
  259. {
  260.     static int n = 1;
  261.     char buf[128];
  262.  
  263.     sprintf(buf, "c%d", n++);
  264.     return wstrdup(buf);
  265. }
  266.  
  267. char *FindStyle(Lexer *lexer, char *tag, char *properties)
  268. {
  269.     Style *style;
  270.  
  271.     for (style = lexer->styles; style; style=style->next)
  272.     {
  273.         if (wstrcmp(style->tag, tag) == 0 &&
  274.             wstrcmp(style->properties, properties) == 0)
  275.             return style->tag_class;
  276.     }
  277.  
  278.     style = (Style *)MemAlloc(sizeof(Style));
  279.     style->tag = wstrdup(tag);
  280.     style->tag_class = GensymClass(tag);
  281.     style->properties = wstrdup(properties);
  282.     style->next = lexer->styles;
  283.     lexer->styles = style;
  284.     return style->tag_class;
  285. }
  286.  
  287. /*
  288.  Find style attribute in node, and replace it
  289.  by corresponding class attribute. Search for
  290.  class in style dictionary otherwise gensym
  291.  new class and add to dictionary.
  292.  
  293.  Assumes that node doesn't have a class attribute
  294. */
  295. void Style2Rule(Lexer *lexer, Node *node)
  296. {
  297.     AttVal *av;
  298.     char *classname;
  299.  
  300.     for (av = node->attributes; av; av = av->next)
  301.     {
  302.         if (wstrcmp(av->attribute, "style") == 0)
  303.             break;
  304.     }
  305.  
  306.     /* if style attribute already exists then append property */
  307.  
  308.     if (av)
  309.     {
  310.         classname = FindStyle(lexer, node->element, av->value);
  311.         MemFree(av->attribute);
  312.         MemFree(av->value);
  313.         av->attribute = wstrdup("class");
  314.         av->value = wstrdup(classname);
  315.     }
  316. }
  317.  
  318. /* create style element using rules from dictionary */
  319. void CreateStyleElement(Lexer *lexer, Node *doc)
  320. {
  321.     Node *node, *html, *head;
  322.     Style *style;
  323.     AttVal *av;
  324.  
  325.     if (lexer->styles == null)
  326.         return;
  327.  
  328.     node = NewNode();
  329.     node->type = StartTag;
  330.     node->implicit = yes;
  331.     node->element = wstrdup("style");
  332.     FindTag(node);
  333.  
  334.     /* insert type attribute */
  335.     av = (AttVal *)MemAlloc(sizeof(AttVal));
  336.     av->next = null; 
  337.     av->attribute = wstrdup("type");
  338.     av->value = wstrdup("text/css");
  339.     av->delim = '"';
  340.     av->dict = FindAttribute(av);
  341.     node->attributes = av;
  342.  
  343.     lexer->txtstart = lexer->lexsize;
  344.  
  345.     for (style = lexer->styles; style; style = style->next)
  346.     {
  347.         AddCharToLexer(lexer, ' ');
  348.         AddStringLiteral(lexer, style->tag);
  349.         AddCharToLexer(lexer, '.');
  350.         AddStringLiteral(lexer, style->tag_class);
  351.         AddCharToLexer(lexer, ' ');
  352.         AddCharToLexer(lexer, '{');
  353.         AddStringLiteral(lexer, style->properties);
  354.         AddCharToLexer(lexer, '}');
  355.         AddCharToLexer(lexer, '\n');
  356.     }
  357.  
  358.     lexer->txtend = lexer->lexsize;
  359.  
  360.     InsertNode(node, TextToken(lexer));
  361.  
  362.     /*
  363.      now insert style element into document head
  364.  
  365.      doc is root node. search its children for html node
  366.      the head node should be first child of html node
  367.     */
  368.  
  369.     for (html = doc->content; html; html = html->next)
  370.     {
  371.         if (html->tag == tag_html)
  372.         {
  373.             head = html->content;
  374.             InsertNode(head, node);
  375.             break;
  376.         }
  377.     }
  378. }
  379.  
  380. /* ensure bidirectional links are consistent */
  381. void FixNodeLinks(Node *node)
  382. {
  383.     if (node->prev)
  384.         node->prev->next = node;
  385.     else
  386.         node->parent->content = node;
  387.  
  388.     if (node->next)
  389.         node->next->prev = node;
  390.     else
  391.         node->parent->last = node;
  392. }
  393.  
  394. /*
  395.  used to strip child of node when
  396.  the node has one and only one child
  397. */
  398. void StripOnlyChild(Node *node)
  399. {
  400.     Node *child;
  401.  
  402.     child = node->content;
  403.     node->content = child->content;
  404.     node->last = child->last;
  405.     child->content = null;
  406.     FreeNode(child);
  407.  
  408.     for (child = node->content; child; child = child->next)
  409.         child->parent = node;
  410. }
  411.  
  412. /* used to strip font start and end tags */
  413. void DiscardContainer(Node *element, Node **pnode)
  414. {
  415.     Node *node, *parent = element->parent;
  416.  
  417.     if (element->content)
  418.     {
  419.         element->last->next = element->next;
  420.  
  421.         if (element->next)
  422.         {
  423.             element->next->prev = element->last;
  424.             element->last->next = element->next;
  425.         }
  426.         else
  427.             parent->last = element->last;
  428.  
  429.         if (element->prev)
  430.         {
  431.             element->content->prev = element->prev;
  432.             element->prev->next = element->content;
  433.         }
  434.         else
  435.             parent->content = element->content;
  436.  
  437.         for (node = element->content; node; node = node->next)
  438.             node->parent = parent;
  439.  
  440.         *pnode = element->content;
  441.     }
  442.     else
  443.     {
  444.         if (element->next)
  445.             element->next->prev = element->prev;
  446.         else
  447.             parent->last = element->prev;
  448.  
  449.         if (element->prev)
  450.             element->prev->next = element->next;
  451.         else
  452.             parent->content = element->next;
  453.  
  454.         *pnode = element->next;
  455.     }
  456.  
  457.     element->next = element->content = null;
  458.     FreeNode(element);
  459. }
  460.  
  461. /*
  462.  Add style property to element, creating style
  463.  attribute as needed and adding ; delimiter
  464. */
  465. void AddStyleProperty(Node *node, char *property)
  466. {
  467.     AttVal *av;
  468.  
  469.     for (av = node->attributes; av; av = av->next)
  470.     {
  471.         if (wstrcmp(av->attribute, "style") == 0)
  472.             break;
  473.     }
  474.  
  475.     /* if style attribute already exists then insert property */
  476.  
  477.     if (av)
  478.     {
  479.         char *s;
  480.  
  481.         s = AddProperty(av->value, property);
  482.         MemFree(av->value);
  483.         av->value = s;
  484.     }
  485.     else /* else create new style attribute */
  486.     {
  487.         av = (AttVal *)MemAlloc(sizeof(AttVal));
  488.         av->attribute = wstrdup("style");
  489.         av->value = wstrdup(property);
  490.         av->delim = '"';
  491.         av->dict = FindAttribute(av);
  492.         av->next = node->attributes;
  493.         node->attributes = av;
  494.     }
  495. }
  496.  
  497. /*
  498.   Create new string that consists of the
  499.   combined style properties in s1 and s2
  500.  
  501.   To merge property lists, we build a linked
  502.   list of property/values and insert properties
  503.   into the list in order, merging values for
  504.   the same property name.
  505. */
  506. char *MergeProperties(char *s1, char *s2)
  507. {
  508.     char *s;
  509.     StyleProp *prop;
  510.  
  511.     prop = CreateProps(null, s1);
  512.     prop = CreateProps(prop, s2);
  513.     s = CreatePropString(prop);
  514.     FreeStyleProps(prop);
  515.     return s;
  516. }
  517.  
  518. void MergeStyles(Node *node, Node *child)
  519. {
  520.     AttVal *av;
  521.     char *s1, *s2, *style;
  522.  
  523.     for (s2 = null, av = child->attributes; av; av = av->next)
  524.     {
  525.         if (wstrcmp(av->attribute, "style") == 0)
  526.         {
  527.             s2 = av->value;
  528.             break;
  529.         }
  530.     }
  531.  
  532.     for (s1 = null, av = node->attributes; av; av = av->next)
  533.     {
  534.         if (wstrcmp(av->attribute, "style") == 0)
  535.         {
  536.             s1 = av->value;
  537.             break;
  538.         }
  539.     }
  540.  
  541.     if (s1)
  542.     {
  543.         if (s2)  /* merge styles from both */
  544.         {
  545.             style = MergeProperties(s1, s2);
  546.             MemFree(av->value);
  547.             av->value = style;
  548.         }
  549.     }
  550.     else if (s2)  /* copy style of child */
  551.     {
  552.         av = (AttVal *)MemAlloc(sizeof(AttVal));
  553.         av->attribute = wstrdup("style");
  554.         av->value = wstrdup(s2);
  555.         av->delim = '"';
  556.         av->dict = FindAttribute(av);
  557.         av->next = node->attributes;
  558.         node->attributes = av;
  559.     }
  560. }
  561.  
  562. char *FontSize2Name(char *size)
  563. {
  564.     char *sizes[7] =
  565.       {
  566.         "50%", "60%", "80%", null,
  567.         "120%", "150%", "200%"
  568.       };
  569.  
  570.     if ('0' <= size[0] && size[0] <= '6')
  571.     {
  572.         int n = size[0] - '0';
  573.         return sizes[n];
  574.     }
  575.  
  576.     if (size[0] == '-')
  577.         return "smaller";
  578.  
  579.     return "larger";
  580. }
  581.  
  582. void AddFontFace(Node *node, char *face)
  583. {
  584.     char buf[1024];
  585.  
  586.     sprintf(buf, "font-family: %s", face);
  587.     AddStyleProperty(node, buf);
  588. }
  589.  
  590. void AddFontSize(Node *node, char *size)
  591. {
  592.     char *value, buf[1024];
  593.  
  594.     if (wstrcmp(size, "6") == 0 && node->tag == tag_p)
  595.     {
  596.         MemFree(node->element);
  597.         node->element = wstrdup("h1");
  598.         FindTag(node);
  599.         return;
  600.     }
  601.  
  602.     if (wstrcmp(size, "5") == 0 && node->tag == tag_p)
  603.     {
  604.         MemFree(node->element);
  605.         node->element = wstrdup("h2");
  606.         FindTag(node);
  607.         return;
  608.     }
  609.  
  610.     if (wstrcmp(size, "4") == 0 && node->tag == tag_p)
  611.     {
  612.         MemFree(node->element);
  613.         node->element = wstrdup("h3");
  614.         FindTag(node);
  615.         return;
  616.     }
  617.  
  618.     value = FontSize2Name(size);
  619.  
  620.     if (value)
  621.     {
  622.         sprintf(buf, "font-size: %s", value);
  623.         AddStyleProperty(node, buf);
  624.     }
  625. }
  626.  
  627. void AddFontColor(Node *node, char *color)
  628. {
  629.     char buf[1024];
  630.  
  631.     sprintf(buf, "color: %s", color);
  632.     AddStyleProperty(node, buf);
  633. }
  634.  
  635. void AddAlign(Node *node, char *align)
  636. {
  637.     char buf[1024], *p, *q;
  638.  
  639.     /* force alignment value to lower case */
  640.     for (p = buf, q = "text-align: "; *p++ = *q++;);
  641.     for (p = p-1; *p++ = ToLower(*align++););
  642.     AddStyleProperty(node, buf);
  643. }
  644.  
  645. /*
  646.  add style properties to node corresponding to
  647.  the font face, size and color attributes
  648. */
  649. void AddFontStyles(Node *node, AttVal *av)
  650. {
  651.     while (av)
  652.     {
  653.         if (wstrcmp(av->attribute, "face") == 0)
  654.             AddFontFace(node, av->value);
  655.         else if (wstrcmp(av->attribute, "size") == 0)
  656.             AddFontSize(node, av->value);
  657.         else if (wstrcmp(av->attribute, "color") == 0)
  658.             AddFontColor(node, av->value);
  659.  
  660.         av = av->next;
  661.     }
  662. }
  663.  
  664. /*
  665.     Symptom: <p align=center>
  666.     Action: <p style="text-align: center">
  667. */
  668. void TextAlign(Lexer *lexer, Node *node)
  669. {
  670.     AttVal *av, *prev;
  671.  
  672.     prev = null;
  673.  
  674.     for (av = node->attributes; av; av = av->next)
  675.     {
  676.         if (wstrcmp(av->attribute, "align") == 0)
  677.         {
  678.             if (prev)
  679.                 prev->next = av->next;
  680.             else
  681.                 node->attributes = null;
  682.  
  683.             MemFree(av->attribute);
  684.  
  685.             if (av->value)
  686.             {
  687.                 AddAlign(node, av->value);
  688.                 MemFree(av->value);
  689.             }
  690.  
  691.             MemFree(av);
  692.             break;
  693.         }
  694.  
  695.         prev = av;
  696.     }
  697. }
  698.  
  699. /*
  700.    The clean up rules use the pnode argument to return the
  701.    next node when the orignal node has been deleted
  702. */
  703.  
  704. /*
  705.     Symptom: <dir> <li> where <li> is only child
  706.     Action: coerce <dir> <li> to <div> with indent.
  707. */
  708.  
  709. Bool Dir2Div(Lexer *lexer, Node *node, Node **pnode)
  710. {
  711.     Node *child;
  712.  
  713.     if (node->tag == tag_dir || node->tag == tag_ul || node->tag == tag_ol)
  714.     {
  715.         child = node->content;
  716.  
  717.         if (child == null)
  718.             return no;
  719.  
  720.         /* check child has no peers */
  721.  
  722.         if (child->next)
  723.             return no;
  724.  
  725.         if (child->tag != tag_li)
  726.             return no;
  727.  
  728.         if (!child->implicit)
  729.             return no;
  730.  
  731.         /* coerce dir to div */
  732.  
  733.         node->tag = tag_div;
  734.         MemFree(node->element);
  735.         node->element = wstrdup("div");
  736.         AddStyleProperty(node, "margin-left: 2em");
  737.         StripOnlyChild(node);
  738.         return yes;
  739.  
  740. #if 0
  741.         content = child->content;
  742.         last = child->last;
  743.         child->content = null;
  744.  
  745.         /* adjust parent and set margin on contents of <li> */
  746.  
  747.         for (child = content; child; child = child->next)
  748.         {
  749.             child->parent = node->parent;
  750.             AddStyleProperty(child, "margin-left: 1em");
  751.         }
  752.  
  753.         /* hook first/last into sequence */
  754.  
  755.         if (content)
  756.         {
  757.             content->prev = node->prev;
  758.             last->next = node->next;
  759.             FixNodeLinks(content);
  760.             FixNodeLinks(last);
  761.         }
  762.  
  763.         node->next = null;
  764.         FreeNode(node);
  765.  
  766.         /* ensure that new node is cleaned */
  767.         *pnode = CleanNode(lexer, content);
  768.         return yes;
  769. #endif
  770.     }
  771.  
  772.     return no;
  773. }
  774.  
  775. /*
  776.     Symptom: implicit blockquote due to <ul>some text</ul>
  777.     Action: replace by <div> with indent style
  778. */
  779. Bool Blockquote2Div(Lexer *lexer, Node *node, Node **pnode)
  780. {
  781.     Node *child;
  782.  
  783.     if (node->tag == tag_blockquote && node->implicit)
  784.     {
  785.         child = node->content;
  786.  
  787.         if (child == null)
  788.             return no;
  789.  
  790.         /* coerce blockquote to div */
  791.  
  792.         node->tag = tag_div;
  793.         MemFree(node->element);
  794.         node->element = wstrdup("div");
  795.         AddStyleProperty(node, "margin-left: 2em");
  796.         return yes;
  797.     }
  798.  
  799.     return no;
  800. }
  801.  
  802.  
  803. /*
  804.     Symptom: <center>
  805.     Action: replace <center> by <div style="text-align: center">
  806. */
  807.  
  808. Bool Center2Div(Lexer *lexer, Node *node, Node **pnode)
  809. {
  810.     if (node->tag == tag_center)
  811.     {
  812.         if (DropFontTags)
  813.         {
  814.             if (node->content)
  815.             {
  816.                 Node *last = node->last, *parent = node->parent;
  817.  
  818.                 DiscardContainer(node, pnode);
  819.  
  820.                 node = InferredTag(lexer, "br");
  821.  
  822.                 if (last->next)
  823.                     last->next->prev = node;
  824.  
  825.                 node->next = last->next;
  826.                 last->next = node;
  827.                 node->prev = last;
  828.  
  829.                 if (parent->last = last)
  830.                     parent->last = node;
  831.  
  832.                 node->parent = parent;
  833.             }
  834.             else
  835.             {
  836.                 Node *prev = node->prev, *next = node->next, *parent = node->parent;
  837.                 DiscardContainer(node, pnode);
  838.  
  839.                 node = InferredTag(lexer, "br");
  840.                 node->next = next;
  841.                 node->prev = prev;
  842.                 node->parent = parent;
  843.  
  844.                 if (next)
  845.                     next->prev = node;
  846.                 else
  847.                     parent->last = node;
  848.  
  849.                 if (prev)
  850.                     prev->next = node;
  851.                 else
  852.                     parent->content = node;
  853.             }
  854.  
  855.             return yes;
  856.         }
  857.  
  858.         node->tag = tag_div;
  859.         MemFree(node->element);
  860.         node->element = wstrdup("div");
  861.         AddStyleProperty(node, "text-align: center");
  862.         return yes;
  863.     }
  864.  
  865.     return no;
  866. }
  867.  
  868. /*
  869.     Symptom <div><div>...</div></div>
  870.     Action: merge the two divs
  871.  
  872.   This is useful after nested <dir>s used by Word
  873.   for indenting have been converted to <div>s
  874. */
  875. Bool MergeDivs(Lexer *lexer, Node *node, Node **pnode)
  876. {
  877.     Node *child;
  878.  
  879.     if (node->tag != tag_div)
  880.         return no;
  881.  
  882.     child = node->content;
  883.  
  884.     if (!child)
  885.         return no;
  886.  
  887.     if (child->tag != tag_div)
  888.         return no;
  889.  
  890.     if (child->next != null)
  891.         return no;
  892.  
  893.     MergeStyles(node, child);
  894.     StripOnlyChild(node);
  895.     return yes;
  896. }
  897.  
  898. /*
  899.     Symptom: <ul><li><ul>...</ul></li></ul>
  900.     Action: discard outer list
  901. */
  902.  
  903. Bool NestedList(Lexer *lexer, Node *node, Node **pnode)
  904. {
  905.     Node *child, *list;
  906.  
  907.     if (node->tag == tag_ul || node->tag == tag_ol)
  908.     {
  909.         child = node->content;
  910.  
  911.         if (child == null)
  912.             return no;
  913.  
  914.         /* check child has no peers */
  915.  
  916.         if (child->next)
  917.             return no;
  918.  
  919.         list = child->content;
  920.  
  921.         if (!list)
  922.             return no;
  923.  
  924.         if (list->tag != node->tag)
  925.             return no;
  926.  
  927.         *pnode = node->next;
  928.  
  929.         /* move inner list node into position of outer node */
  930.         list->prev = node->prev;
  931.         list->next = node->next;
  932.         list->parent = node->parent;
  933.         FixNodeLinks(list);
  934.  
  935.         /* get rid of outer ul and its li */
  936.         child->content = null;
  937.         node->content = null;
  938.         node->next = null;
  939.         FreeNode(node);
  940.  
  941.         /*
  942.           If prev node was a list the chances are this node
  943.           should be appended to that list. Word has no way of
  944.           recognizing nested lists and just uses indents
  945.         */
  946.  
  947.         if (list->prev)
  948.         {
  949.             node = list;
  950.             list = node->prev;
  951.  
  952.             if (list->tag == tag_ul || list->tag == tag_ol)
  953.             {
  954.                 list->next = node->next;
  955.  
  956.                 if (list->next)
  957.                     list->next->prev = list;
  958.  
  959.                 child = list->last;  /* <li> */
  960.  
  961.                 node->parent = child;
  962.                 node->next = null;
  963.                 node->prev = child->last;
  964.                 FixNodeLinks(node);
  965.             }
  966.         }
  967.  
  968.         CleanNode(lexer, node);
  969.         return yes;
  970.     }
  971.  
  972.     return no;
  973. }
  974.  
  975. /*
  976.     Symptom: the only child of a block-level element is a
  977.     presentation element such as B, I or FONT
  978.  
  979.     Action: add style "font-weight: bold" to the block and
  980.     strip the <b> element, leaving its children.
  981.  
  982.   example:
  983.  
  984.     <p>
  985.       <b><font face="Arial" size="6">Draft Recommended Practice</font></b>
  986.     </p>
  987.  
  988.   becomes:
  989.  
  990.       <p style="font-weight: bold; font-family: Arial; font-size: 6">
  991.         Draft Recommended Practice
  992.       </p>
  993. */
  994. Bool BlockStyle(Lexer *lexer, Node *node, Node **pnode)
  995. {
  996.     Node *child;
  997.  
  998.     if (node->tag->model & (CM_BLOCK | CM_LIST | CM_DEFLIST | CM_TABLE))
  999.     {
  1000.         /* check for align attribute */
  1001.         if (node->tag != tag_caption)
  1002.             TextAlign(lexer, node);
  1003.  
  1004.         child = node->content;
  1005.  
  1006.         if (child == null)
  1007.             return no;
  1008.  
  1009.         /* check child has no peers */
  1010.  
  1011.         if (child->next)
  1012.             return no;
  1013.  
  1014.         if (child->tag == tag_b)
  1015.         {
  1016.             AddStyleProperty(node, "font-weight: bold");
  1017.             StripOnlyChild(node);
  1018.             return yes;
  1019.         }
  1020.  
  1021.         if (child->tag == tag_i)
  1022.         {
  1023.             AddStyleProperty(node, "font-style: italic");
  1024.             StripOnlyChild(node);
  1025.             return yes;
  1026.         }
  1027.  
  1028.         if (child->tag == tag_font)
  1029.         {
  1030.             AddFontStyles(node, child->attributes);
  1031.             StripOnlyChild(node);
  1032.             return yes;
  1033.         }
  1034.     }
  1035.  
  1036.     return no;
  1037. }
  1038.  
  1039. /* the only child of an inline element such as em */
  1040. Bool InlineStyle(Lexer *lexer, Node *node, Node **pnode)
  1041. {
  1042.     Node *child;
  1043.  
  1044.     if (node->tag != tag_font && (node->tag->model & CM_INLINE))
  1045.     {
  1046.         child = node->content;
  1047.  
  1048.         if (child == null)
  1049.             return no;
  1050.  
  1051.         /* check child has no peers */
  1052.  
  1053.         if (child->next)
  1054.             return no;
  1055.  
  1056.         if (child->tag == tag_b)
  1057.         {
  1058.             AddStyleProperty(node, "font-weight: bold");
  1059.             StripOnlyChild(node);
  1060.             return yes;
  1061.         }
  1062.  
  1063.         if (child->tag == tag_i)
  1064.         {
  1065.             AddStyleProperty(node, "font-style: italic");
  1066.             StripOnlyChild(node);
  1067.             return yes;
  1068.         }
  1069.  
  1070.         if (child->tag == tag_font)
  1071.         {
  1072.             AddFontStyles(node, child->attributes);
  1073.             StripOnlyChild(node);
  1074.             return yes;
  1075.         }
  1076.     }
  1077.  
  1078.     return no;
  1079. }
  1080.  
  1081. /*
  1082.   Replace font elements by span elements, deleting
  1083.   the font element's attributes and replacing them
  1084.   by a single style attribute.
  1085. */
  1086. Bool Font2Span(Lexer *lexer, Node *node, Node **pnode)
  1087. {
  1088.     AttVal *av, *style, *next;
  1089.  
  1090.     if (node->tag == tag_font)
  1091.     {
  1092.         if (DropFontTags)
  1093.         {
  1094.             DiscardContainer(node, pnode);
  1095.             return no;
  1096.         }
  1097.  
  1098.         /* if FONT is only child of parent element then leave alone */
  1099.         if (node->parent->content == node
  1100.             && node->next == null)
  1101.             return no;
  1102.  
  1103.         AddFontStyles(node, node->attributes);
  1104.  
  1105.         av = node->attributes;
  1106.         style = null;
  1107.  
  1108.         while (av)
  1109.         {
  1110.             next = av->next;
  1111.  
  1112.             if (wstrcmp(av->attribute, "style") == 0)
  1113.             {
  1114.                 av->next = null;
  1115.                 style = av;
  1116.             }
  1117.             else
  1118.             {
  1119.                 if (av->attribute)
  1120.                     MemFree(av->attribute);
  1121.                 if (av->value)
  1122.                     MemFree(av->value);
  1123.  
  1124.                 MemFree(av);
  1125.             }
  1126.  
  1127.             av = next;
  1128.         }
  1129.  
  1130.         node->attributes = style;
  1131.  
  1132.         node->tag = tag_span;
  1133.         MemFree(node->element);
  1134.         node->element = wstrdup("span");
  1135.  
  1136.         return yes;
  1137.     }
  1138.  
  1139.     return no;
  1140. }
  1141.  
  1142. Bool IsElement(Node *node)
  1143. {
  1144.     return (node->type == StartTag || node->type == StartEndTag ? yes : no);
  1145. }
  1146.  
  1147. /*
  1148.   Applies all matching rules to a node.
  1149. */
  1150. Node *CleanNode(Lexer *lexer, Node *node)
  1151. {
  1152.     Node *next = null;
  1153.  
  1154.     for (next = node; IsElement(node); node = next)
  1155.     {
  1156.         if (Dir2Div(lexer, node, &next))
  1157.             continue;
  1158.  
  1159.         if (Blockquote2Div(lexer, node, &next))
  1160.             continue;
  1161.  
  1162.         if (NestedList(lexer, node, &next))
  1163.             continue;
  1164.  
  1165.         if (Center2Div(lexer, node, &next))
  1166.             continue;
  1167.  
  1168.         if (MergeDivs(lexer, node, &next))
  1169.             continue;
  1170.  
  1171.         if (BlockStyle(lexer, node, &next))
  1172.             continue;
  1173.  
  1174.         if (InlineStyle(lexer, node, &next))
  1175.             continue;
  1176.  
  1177.         if (Font2Span(lexer, node, &next))
  1178.             continue;
  1179.  
  1180.         break;
  1181.     }
  1182.  
  1183.     return next;
  1184. }
  1185.  
  1186. Node *CreateStyleProperties(Lexer *lexer, Node *node)
  1187. {
  1188.     Node *child;
  1189.  
  1190.     if (node->content)
  1191.     {
  1192.         for (child = node->content; child != null; child = child->next)
  1193.         {
  1194.             child = CreateStyleProperties(lexer, child);
  1195.         }
  1196.     }
  1197.  
  1198.     return CleanNode(lexer, node);
  1199. }
  1200.  
  1201. void DefineStyleRules(Lexer *lexer, Node *node)
  1202. {
  1203.     Node *child;
  1204.  
  1205.     if (node->content)
  1206.     {
  1207.         for (child = node->content;
  1208.                 child != null; child = child->next)
  1209.         {
  1210.             DefineStyleRules(lexer, child);
  1211.         }
  1212.     }
  1213.  
  1214.     Style2Rule(lexer, node);
  1215. }
  1216.  
  1217. void CleanTree(Lexer *lexer, Node *doc)
  1218. {
  1219.     doc = CreateStyleProperties(lexer, doc);
  1220.  
  1221.     if (!DropFontTags)
  1222.     {
  1223.         DefineStyleRules(lexer, doc);
  1224.         CreateStyleElement(lexer, doc);
  1225.     }
  1226. }
  1227.  
  1228. /* replace i by em and b by strong */
  1229. void EmFromI(Node *node)
  1230. {
  1231.     while (node)
  1232.     {
  1233.         if (node->tag == tag_i)
  1234.         {
  1235.             MemFree(node->element);
  1236.             node->element = wstrdup(tag_em->name);
  1237.             node->tag = tag_em;
  1238.         }
  1239.         else if (node->tag == tag_b)
  1240.         {
  1241.             MemFree(node->element);
  1242.             node->element = wstrdup(tag_strong->name);
  1243.             node->tag = tag_strong;
  1244.         }
  1245.  
  1246.         if (node->content)
  1247.             EmFromI(node->content);
  1248.  
  1249.         node = node->next;
  1250.     }
  1251. }
  1252.  
  1253.