OSDN Git Service

Initial commit
[wordring-tm/wordring-tm.git] / third_party / tidy-html5-master / src / clean.c
1 /*
2   clean.c -- clean up misuse of presentation markup
3
4   (c) 1998-2008 (W3C) MIT, ERCIM, Keio University
5   See tidy.h 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
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46
47 #include "tidy-int.h"
48 #include "clean.h"
49 #include "lexer.h"
50 #include "parser.h"
51 #include "attrs.h"
52 #include "message.h"
53 #include "tmbstr.h"
54 #include "utf8.h"
55
56 static Node* CleanNode( TidyDocImpl* doc, Node *node );
57
58 static void RenameElem( TidyDocImpl* doc, Node* node, TidyTagId tid )
59 {
60     const Dict* dict = TY_(LookupTagDef)( tid );
61     TidyDocFree( doc, node->element );
62     node->element = TY_(tmbstrdup)( doc->allocator, dict->name );
63     node->tag = dict;
64 }
65
66 static void FreeStyleProps(TidyDocImpl* doc, StyleProp *props)
67 {
68     StyleProp *next;
69
70     while (props)
71     {
72         next = props->next;
73         TidyDocFree(doc, props->name);
74         TidyDocFree(doc, props->value);
75         TidyDocFree(doc, props);
76         props = next;
77     }
78 }
79
80 static StyleProp *InsertProperty( TidyDocImpl* doc, StyleProp* props, ctmbstr name, ctmbstr value )
81 {
82     StyleProp *first, *prev, *prop;
83     int cmp;
84
85     prev = NULL;
86     first = props;
87
88     while (props)
89     {
90         cmp = TY_(tmbstrcmp)(props->name, name);
91
92         if (cmp == 0)
93         {
94             /* this property is already defined, ignore new value */
95             return first;
96         }
97
98         if (cmp > 0)
99         {
100             /* insert before this */
101
102             prop = (StyleProp *)TidyDocAlloc(doc, sizeof(StyleProp));
103             prop->name = TY_(tmbstrdup)(doc->allocator, name);
104             prop->value = TY_(tmbstrdup)(doc->allocator, value);
105             prop->next = props;
106
107             if (prev)
108                 prev->next = prop;
109             else
110                 first = prop;
111
112             return first;
113         }
114
115         prev = props;
116         props = props->next;
117     }
118
119     prop = (StyleProp *)TidyDocAlloc(doc, sizeof(StyleProp));
120     prop->name = TY_(tmbstrdup)(doc->allocator, name);
121     prop->value = TY_(tmbstrdup)(doc->allocator, value);
122     prop->next = NULL;
123
124     if (prev)
125         prev->next = prop;
126     else
127         first = prop;
128
129     return first;
130 }
131
132 /*
133  Create sorted linked list of properties from style string
134  It temporarily places nulls in place of ':' and ';' to
135  delimit the strings for the property name and value.
136  Some systems don't allow you to NULL literal strings,
137  so to avoid this, a copy is made first.
138 */
139 static StyleProp* CreateProps( TidyDocImpl* doc, StyleProp* prop, ctmbstr style )
140 {
141     tmbstr name, value = NULL, name_end, value_end, line;
142     Bool more;
143
144     line = TY_(tmbstrdup)(doc->allocator, style);
145     name = line;
146
147     while (*name)
148     {
149         while (*name == ' ')
150             ++name;
151
152         name_end = name;
153
154         while (*name_end)
155         {
156             if (*name_end == ':')
157             {
158                 value = name_end + 1;
159                 break;
160             }
161
162             ++name_end;
163         }
164
165         if (*name_end != ':')
166             break;
167
168         while ( value && *value == ' ')
169             ++value;
170
171         value_end = value;
172         more = no;
173
174         while (*value_end)
175         {
176             if (*value_end == ';')
177             {
178                 more = yes;
179                 break;
180             }
181
182             ++value_end;
183         }
184
185         *name_end = '\0';
186         *value_end = '\0';
187
188         prop = InsertProperty(doc, prop, name, value);
189         *name_end = ':';
190
191         if (more)
192         {
193             *value_end = ';';
194             name = value_end + 1;
195             continue;
196         }
197
198         break;
199     }
200
201     TidyDocFree(doc, line);  /* free temporary copy */
202     return prop;
203 }
204
205 static tmbstr CreatePropString(TidyDocImpl* doc, StyleProp *props)
206 {
207     tmbstr style, p, s;
208     uint len;
209     StyleProp *prop;
210
211     /* compute length */
212
213     for (len = 0, prop = props; prop; prop = prop->next)
214     {
215         len += TY_(tmbstrlen)(prop->name) + 2;
216         if (prop->value)
217             len += TY_(tmbstrlen)(prop->value) + 2;
218     }
219
220     style = (tmbstr) TidyDocAlloc(doc, len+1);
221     style[0] = '\0';
222
223     for (p = style, prop = props; prop; prop = prop->next)
224     {
225         s = prop->name;
226
227         while((*p++ = *s++))
228             continue;
229
230         if (prop->value)
231         {
232             *--p = ':';
233             *++p = ' ';
234             ++p;
235
236             s = prop->value;
237             while((*p++ = *s++))
238                 continue;
239         }
240         if (prop->next == NULL)
241             break;
242
243         *--p = ';';
244         *++p = ' ';
245         ++p;
246     }
247
248     return style;
249 }
250
251 /*
252   create string with merged properties
253 static tmbstr AddProperty( ctmbstr style, ctmbstr property )
254 {
255     tmbstr line;
256     StyleProp *prop;
257
258     prop = CreateProps(doc, NULL, style);
259     prop = CreateProps(doc, prop, property);
260     line = CreatePropString(doc, prop);
261     FreeStyleProps(doc, prop);
262     return line;
263 }
264 */
265
266 void TY_(FreeStyles)( TidyDocImpl* doc )
267 {
268     Lexer* lexer = doc->lexer;
269     if ( lexer )
270     {
271         TagStyle *style, *next;
272         for ( style = lexer->styles; style; style = next )
273         {
274             next = style->next;
275             TidyDocFree( doc, style->tag );
276             TidyDocFree( doc, style->tag_class );
277             TidyDocFree( doc, style->properties );
278             TidyDocFree( doc, style );
279         }
280     }
281 }
282
283 static tmbstr GensymClass( TidyDocImpl* doc )
284 {
285     tmbchar buf[512];  /* CSSPrefix is limited to 256 characters */
286     ctmbstr pfx = cfgStr(doc, TidyCSSPrefix);
287     if ( pfx == NULL || *pfx == 0 )
288       pfx = "c";
289
290     TY_(tmbsnprintf)(buf, sizeof(buf), "%s%u", pfx, ++doc->nClassId );
291     return TY_(tmbstrdup)(doc->allocator, buf);
292 }
293
294 static ctmbstr FindStyle( TidyDocImpl* doc, ctmbstr tag, ctmbstr properties )
295 {
296     Lexer* lexer = doc->lexer;
297     TagStyle* style;
298
299     for (style = lexer->styles; style; style=style->next)
300     {
301         if (TY_(tmbstrcmp)(style->tag, tag) == 0 &&
302             TY_(tmbstrcmp)(style->properties, properties) == 0)
303             return style->tag_class;
304     }
305
306     style = (TagStyle *)TidyDocAlloc( doc, sizeof(TagStyle) );
307     style->tag = TY_(tmbstrdup)(doc->allocator, tag);
308     style->tag_class = GensymClass( doc );
309     style->properties = TY_(tmbstrdup)( doc->allocator, properties );
310     style->next = lexer->styles;
311     lexer->styles = style;
312     return style->tag_class;
313 }
314
315 /*
316  Add class="foo" to node
317 */
318 static void AddClass( TidyDocImpl* doc, Node* node, ctmbstr classname )
319 {
320     AttVal *classattr = TY_(AttrGetById)(node, TidyAttr_CLASS);;
321
322     /*
323      if there already is a class attribute
324      then append class name after a space.
325     */
326     if (classattr)
327         TY_(AppendToClassAttr)( doc, classattr, classname );
328     else /* create new class attribute */
329         TY_(AddAttribute)( doc, node, "class", classname );
330 }
331
332 void TY_(AddStyleAsClass)( TidyDocImpl* doc, Node *node, ctmbstr stylevalue )
333 {
334     ctmbstr classname;
335
336     classname = FindStyle( doc, node->element, stylevalue );
337     AddClass( doc, node, classname);
338 }
339
340 /*
341  Find style attribute in node, and replace it
342  by corresponding class attribute. Search for
343  class in style dictionary otherwise gensym
344  new class and add to dictionary.
345
346  Assumes that node doesn't have a class attribute
347 */
348 static void Style2Rule( TidyDocImpl* doc, Node *node)
349 {
350     AttVal *styleattr, *classattr;
351     ctmbstr classname;
352
353     styleattr = TY_(AttrGetById)(node, TidyAttr_STYLE);
354
355     if (styleattr)
356     {
357         /* fix for http://tidy.sf.net/bug/850215 */
358         if (!styleattr->value)
359         {
360             TY_(RemoveAttribute)(doc, node, styleattr);
361             return;
362         }
363
364         classname = FindStyle( doc, node->element, styleattr->value );
365         classattr = TY_(AttrGetById)(node, TidyAttr_CLASS);
366
367         /*
368          if there already is a class attribute
369          then append class name after an underscore
370         */
371         if (classattr)
372         {
373             TY_(AppendToClassAttr)( doc, classattr, classname );
374             TY_(RemoveAttribute)( doc, node, styleattr );
375         }
376         else /* reuse style attribute for class attribute */
377         {
378             TidyDocFree(doc, styleattr->attribute);
379             TidyDocFree(doc, styleattr->value);
380             styleattr->attribute = TY_(tmbstrdup)(doc->allocator, "class");
381             styleattr->value = TY_(tmbstrdup)(doc->allocator, classname);
382         }
383     }
384 }
385
386 static void AddColorRule( Lexer* lexer, ctmbstr selector, ctmbstr color )
387 {
388     if ( selector && color )
389     {
390         TY_(AddStringLiteral)(lexer, selector);
391         TY_(AddStringLiteral)(lexer, " { color: ");
392         TY_(AddStringLiteral)(lexer, color);
393         TY_(AddStringLiteral)(lexer, " }\n");
394     }
395 }
396
397 /*
398  move presentation attribs from body to style element
399
400  background="foo" ->  body { background-image: url(foo) }
401  bgcolor="foo"    ->  body { background-color: foo }
402  text="foo"       ->  body { color: foo }
403  link="foo"       ->  :link { color: foo }
404  vlink="foo"      ->  :visited { color: foo }
405  alink="foo"      ->  :active { color: foo }
406 */
407 static void CleanBodyAttrs( TidyDocImpl* doc, Node* body )
408 {
409     Lexer* lexer  = doc->lexer;
410     tmbstr bgurl   = NULL;
411     tmbstr bgcolor = NULL;
412     tmbstr color   = NULL;
413     AttVal* attr;
414     
415     if (NULL != (attr = TY_(AttrGetById)(body, TidyAttr_BACKGROUND)))
416     {
417         bgurl = attr->value;
418         attr->value = NULL;
419         TY_(RemoveAttribute)( doc, body, attr );
420     }
421
422     if (NULL != (attr = TY_(AttrGetById)(body, TidyAttr_BGCOLOR)))
423     {
424         bgcolor = attr->value;
425         attr->value = NULL;
426         TY_(RemoveAttribute)( doc, body, attr );
427     }
428
429     if (NULL != (attr = TY_(AttrGetById)(body, TidyAttr_TEXT)))
430     {
431         color = attr->value;
432         attr->value = NULL;
433         TY_(RemoveAttribute)( doc, body, attr );
434     }
435
436     if ( bgurl || bgcolor || color )
437     {
438         TY_(AddStringLiteral)(lexer, " body {\n");
439         if (bgurl)
440         {
441             TY_(AddStringLiteral)(lexer, "  background-image: url(");
442             TY_(AddStringLiteral)(lexer, bgurl);
443             TY_(AddStringLiteral)(lexer, ");\n");
444             TidyDocFree(doc, bgurl);
445         }
446         if (bgcolor)
447         {
448             TY_(AddStringLiteral)(lexer, "  background-color: ");
449             TY_(AddStringLiteral)(lexer, bgcolor);
450             TY_(AddStringLiteral)(lexer, ";\n");
451             TidyDocFree(doc, bgcolor);
452         }
453         if (color)
454         {
455             TY_(AddStringLiteral)(lexer, "  color: ");
456             TY_(AddStringLiteral)(lexer, color);
457             TY_(AddStringLiteral)(lexer, ";\n");
458             TidyDocFree(doc, color);
459         }
460
461         TY_(AddStringLiteral)(lexer, " }\n");
462     }
463
464     if (NULL != (attr = TY_(AttrGetById)(body, TidyAttr_LINK)))
465     {
466         AddColorRule(lexer, " :link", attr->value);
467         TY_(RemoveAttribute)( doc, body, attr );
468     }
469
470     if (NULL != (attr = TY_(AttrGetById)(body, TidyAttr_VLINK)))
471     {
472         AddColorRule(lexer, " :visited", attr->value);
473         TY_(RemoveAttribute)( doc, body, attr );
474     }
475
476     if (NULL != (attr = TY_(AttrGetById)(body, TidyAttr_ALINK)))
477     {
478         AddColorRule(lexer, " :active", attr->value);
479         TY_(RemoveAttribute)( doc, body, attr );
480     }
481 }
482
483 static Bool NiceBody( TidyDocImpl* doc )
484 {
485     Node* node = TY_(FindBody)(doc);
486     if (node)
487     {
488         if (TY_(AttrGetById)(node, TidyAttr_BACKGROUND) ||
489             TY_(AttrGetById)(node, TidyAttr_BGCOLOR)    ||
490             TY_(AttrGetById)(node, TidyAttr_TEXT)       ||
491             TY_(AttrGetById)(node, TidyAttr_LINK)       ||
492             TY_(AttrGetById)(node, TidyAttr_VLINK)      ||
493             TY_(AttrGetById)(node, TidyAttr_ALINK))
494         {
495             doc->badLayout |= USING_BODY;
496             return no;
497         }
498     }
499
500     return yes;
501 }
502
503 /* create style element using rules from dictionary */
504 static void CreateStyleElement( TidyDocImpl* doc )
505 {
506     Lexer* lexer = doc->lexer;
507     Node *node, *head, *body;
508     TagStyle *style;
509     AttVal *av;
510
511     if ( lexer->styles == NULL && NiceBody(doc) )
512         return;
513
514     node = TY_(NewNode)( doc->allocator, lexer );
515     node->type = StartTag;
516     node->implicit = yes;
517     node->element = TY_(tmbstrdup)(doc->allocator, "style");
518     TY_(FindTag)( doc, node );
519
520     /* insert type attribute */
521     av = TY_(NewAttributeEx)( doc, "type", "text/css", '"' );
522     TY_(InsertAttributeAtStart)( node, av );
523
524     body = TY_(FindBody)( doc );
525     lexer->txtstart = lexer->lexsize;
526     if ( body )
527         CleanBodyAttrs( doc, body );
528
529     for (style = lexer->styles; style; style = style->next)
530     {
531         TY_(AddCharToLexer)(lexer, ' ');
532         TY_(AddStringLiteral)(lexer, style->tag);
533         TY_(AddCharToLexer)(lexer, '.');
534         TY_(AddStringLiteral)(lexer, style->tag_class);
535         TY_(AddCharToLexer)(lexer, ' ');
536         TY_(AddCharToLexer)(lexer, '{');
537         TY_(AddStringLiteral)(lexer, style->properties);
538         TY_(AddCharToLexer)(lexer, '}');
539         TY_(AddCharToLexer)(lexer, '\n');
540     }
541
542     lexer->txtend = lexer->lexsize;
543
544     TY_(InsertNodeAtEnd)( node, TY_(TextToken)(lexer) );
545
546     /*
547      now insert style element into document head
548
549      doc is root node. search its children for html node
550      the head node should be first child of html node
551     */
552     if ( NULL != (head = TY_(FindHEAD)( doc )) )
553         TY_(InsertNodeAtEnd)( head, node );
554 }
555
556
557 /* ensure bidirectional links are consistent */
558 void TY_(FixNodeLinks)(Node *node)
559 {
560     Node *child;
561
562     if (node->prev)
563         node->prev->next = node;
564     else
565         node->parent->content = node;
566
567     if (node->next)
568         node->next->prev = node;
569     else
570         node->parent->last = node;
571
572     for (child = node->content; child; child = child->next)
573         child->parent = node;
574 }
575
576 /*
577  used to strip child of node when
578  the node has one and only one child
579 */
580 static void StripOnlyChild(TidyDocImpl* doc, Node *node)
581 {
582     Node *child;
583
584     child = node->content;
585     node->content = child->content;
586     node->last = child->last;
587     child->content = NULL;
588     TY_(FreeNode)(doc, child);
589
590     for (child = node->content; child; child = child->next)
591         child->parent = node;
592 }
593
594 /*
595   used to strip font start and end tags.
596   Extricate "element", replace it by its content and delete it.
597 */
598 static void DiscardContainer( TidyDocImpl* doc, Node *element, Node **pnode)
599 {
600     if (element->content)
601     {
602         Node *node, *parent = element->parent;
603
604         element->last->next = element->next;
605
606         if (element->next)
607         {
608             element->next->prev = element->last;
609         }
610         else
611             parent->last = element->last;
612
613         if (element->prev)
614         {
615             element->content->prev = element->prev;
616             element->prev->next = element->content;
617         }
618         else
619             parent->content = element->content;
620
621         for (node = element->content; node; node = node->next)
622             node->parent = parent;
623
624         *pnode = element->content;
625
626         element->next = element->content = NULL;
627         TY_(FreeNode)(doc, element);
628     }
629     else
630     {
631         *pnode = TY_(DiscardElement)(doc, element);
632     }
633 }
634
635 /*
636   Create new string that consists of the
637   combined style properties in s1 and s2
638
639   To merge property lists, we build a linked
640   list of property/values and insert properties
641   into the list in order, merging values for
642   the same property name.
643 */
644 static tmbstr MergeProperties( TidyDocImpl* doc, ctmbstr s1, ctmbstr s2 )
645 {
646     tmbstr s;
647     StyleProp *prop;
648
649     prop = CreateProps(doc, NULL, s1);
650     prop = CreateProps(doc, prop, s2);
651     s = CreatePropString(doc, prop);
652     FreeStyleProps(doc, prop);
653     return s;
654 }
655
656 /*
657  Add style property to element, creating style
658  attribute as needed and adding ; delimiter
659 */
660 void TY_(AddStyleProperty)(TidyDocImpl* doc, Node *node, ctmbstr property )
661 {
662     AttVal *av = TY_(AttrGetById)(node, TidyAttr_STYLE);
663
664     /* if style attribute already exists then insert property */
665
666     if ( av )
667     {
668         if (av->value != NULL)
669         {
670             tmbstr s = MergeProperties( doc, av->value, property );
671             TidyDocFree( doc, av->value );
672             av->value = s;
673         }
674         else
675         {
676             av->value = TY_(tmbstrdup)( doc->allocator, property );
677         }
678     }
679     else /* else create new style attribute */
680     {
681         av = TY_(NewAttributeEx)( doc, "style", property, '"' );
682         TY_(InsertAttributeAtStart)( node, av );
683     }
684 }
685
686 static void MergeClasses(TidyDocImpl* doc, Node *node, Node *child)
687 {
688     AttVal *av;
689     tmbstr s1, s2, names;
690
691     for (s2 = NULL, av = child->attributes; av; av = av->next)
692     {
693         if (attrIsCLASS(av))
694         {
695             s2 = av->value;
696             break;
697         }
698     }
699
700     for (s1 = NULL, av = node->attributes; av; av = av->next)
701     {
702         if (attrIsCLASS(av))
703         {
704             s1 = av->value;
705             break;
706         }
707     }
708
709     if (s1)
710     {
711         if (s2)  /* merge class names from both */
712         {
713             uint l1, l2;
714             l1 = TY_(tmbstrlen)(s1);
715             l2 = TY_(tmbstrlen)(s2);
716             names = (tmbstr) TidyDocAlloc(doc, l1 + l2 + 2);
717             TY_(tmbstrcpy)(names, s1);
718             names[l1] = ' ';
719             TY_(tmbstrcpy)(names+l1+1, s2);
720             TidyDocFree(doc, av->value);
721             av->value = names;
722         }
723     }
724     else if (s2)  /* copy class names from child */
725     {
726         av = TY_(NewAttributeEx)( doc, "class", s2, '"' );
727         TY_(InsertAttributeAtStart)( node, av );
728     }
729 }
730
731 static void MergeStyles(TidyDocImpl* doc, Node *node, Node *child)
732 {
733     AttVal *av;
734     tmbstr s1, s2, style;
735
736     /*
737        the child may have a class attribute used
738        for attaching styles, if so the class name
739        needs to be copied to node's class
740     */
741     MergeClasses(doc, node, child);
742
743     for (s2 = NULL, av = child->attributes; av; av = av->next)
744     {
745         if (attrIsSTYLE(av))
746         {
747             s2 = av->value;
748             break;
749         }
750     }
751
752     for (s1 = NULL, av = node->attributes; av; av = av->next)
753     {
754         if (attrIsSTYLE(av))
755         {
756             s1 = av->value;
757             break;
758         }
759     }
760
761     if (s1)
762     {
763         if (s2)  /* merge styles from both */
764         {
765             style = MergeProperties(doc, s1, s2);
766             TidyDocFree(doc, av->value);
767             av->value = style;
768         }
769     }
770     else if (s2)  /* copy style of child */
771     {
772         av = TY_(NewAttributeEx)( doc, "style", s2, '"' );
773         TY_(InsertAttributeAtStart)( node, av );
774     }
775 }
776
777 static ctmbstr FontSize2Name(ctmbstr size)
778 {
779     static const ctmbstr sizes[7] =
780     {
781         "60%", "70%", "80%", NULL,
782         "120%", "150%", "200%"
783     };
784
785     /* increment of 0.8 */
786     static const ctmbstr minussizes[] =
787     {
788         "100%", "80%", "64%", "51%",
789         "40%", "32%", "26%"
790     };
791
792     /* increment of 1.2 */
793     static const ctmbstr plussizes[] =
794     {
795         "100%", "120%", "144%", "172%",
796         "207%", "248%", "298%"
797     };
798
799     if (size[0] == '\0')
800         return NULL;
801
802     if ('0' <= size[0] && size[0] <= '6')
803     {
804         int n = size[0] - '0';
805         return sizes[n];
806     }
807
808     if (size[0] == '-')
809     {
810         if ('0' <= size[1] && size[1] <= '6')
811         {
812             int n = size[1] - '0';
813             return minussizes[n];
814         }
815         return "smaller"; /*"70%"; */
816     }
817
818     if ('0' <= size[1] && size[1] <= '6')
819     {
820         int n = size[1] - '0';
821         return plussizes[n];
822     }
823
824     return "larger"; /* "140%" */
825 }
826
827 static void AddFontFace( TidyDocImpl* doc, Node *node, ctmbstr face )
828 {
829     tmbchar buf[256];
830     TY_(tmbsnprintf)(buf, sizeof(buf), "font-family: %s", face );
831     TY_(AddStyleProperty)( doc, node, buf );
832 }
833
834 static void AddFontSize( TidyDocImpl* doc, Node* node, ctmbstr size )
835 {
836     ctmbstr value = NULL;
837
838     if (nodeIsP(node))
839     {
840         if (TY_(tmbstrcmp)(size, "6") == 0)
841             value = "h1";
842         else if (TY_(tmbstrcmp)(size, "5") == 0)
843             value = "h2";
844         else if (TY_(tmbstrcmp)(size, "4") == 0)
845             value = "h3";
846
847         if (value)
848         {
849             TidyDocFree(doc, node->element);
850             node->element = TY_(tmbstrdup)(doc->allocator, value);
851             TY_(FindTag)(doc, node);
852             return;
853         }
854     }
855
856     value = FontSize2Name(size);
857
858     if (value)
859     {
860         tmbchar buf[64];
861         TY_(tmbsnprintf)(buf, sizeof(buf), "font-size: %s", value);
862         TY_(AddStyleProperty)( doc, node, buf );
863     }
864 }
865
866 static void AddFontColor( TidyDocImpl* doc, Node *node, ctmbstr color)
867 {
868     tmbchar buf[128];
869     TY_(tmbsnprintf)(buf, sizeof(buf), "color: %s", color);
870     TY_(AddStyleProperty)( doc, node, buf );
871 }
872
873 /* force alignment value to lower case */
874 static void AddAlign( TidyDocImpl* doc, Node *node, ctmbstr align )
875 {
876     uint i;
877     tmbchar buf[128];
878
879     TY_(tmbstrcpy)( buf, "text-align: " );
880     for ( i = 12; i < sizeof(buf)/sizeof(buf[0])-1; ++i )
881     {
882         if ( (buf[i] = (tmbchar)TY_(ToLower)(*align++)) == '\0' )
883             break;
884     }
885     buf[i] = '\0';
886     TY_(AddStyleProperty)( doc, node, buf );
887 }
888
889 /*
890  add style properties to node corresponding to
891  the font face, size and color attributes
892 */
893 static void AddFontStyles( TidyDocImpl* doc, Node *node, AttVal *av)
894 {
895     while (av)
896     {
897         if (AttrHasValue(av))
898         {
899             if (attrIsFACE(av))
900                 AddFontFace( doc, node, av->value );
901             else if (attrIsSIZE(av))
902                 AddFontSize( doc, node, av->value );
903             else if (attrIsCOLOR(av))
904                 AddFontColor( doc, node, av->value );
905         }
906         av = av->next;
907     }
908 }
909
910 /*
911     Symptom: <p align=center>
912     Action: <p style="text-align: center">
913 */
914 static void TextAlign( TidyDocImpl* doc, Node* node )
915 {
916     AttVal *av, *prev;
917
918     prev = NULL;
919
920     for (av = node->attributes; av; av = av->next)
921     {
922         if (attrIsALIGN(av))
923         {
924             if (prev)
925                 prev->next = av->next;
926             else
927                 node->attributes = av->next;
928
929             if (av->value)
930                 AddAlign( doc, node, av->value );
931
932             TY_(FreeAttribute)(doc, av);
933             break;
934         }
935
936         prev = av;
937     }
938 }
939
940 /*
941     Symptom: <table bgcolor="red">
942     Action: <table style="background-color: red">
943 */
944 static void TableBgColor( TidyDocImpl* doc, Node* node )
945 {
946     AttVal* attr;
947     tmbchar buf[256];
948
949     if (NULL != (attr = TY_(AttrGetById)(node, TidyAttr_BGCOLOR)))
950     {
951         TY_(tmbsnprintf)(buf, sizeof(buf), "background-color: %s", attr->value );
952         TY_(RemoveAttribute)( doc, node, attr );
953         TY_(AddStyleProperty)( doc, node, buf );
954     }
955 }
956
957 /*
958    The clean up rules use the pnode argument to return the
959    next node when the original node has been deleted
960 */
961
962 /*
963     Symptom: <dir> <li> where <li> is only child
964     Action: coerce <dir> <li> to <div> with indent.
965 */
966
967 static Bool Dir2Div( TidyDocImpl* doc, Node *node, Node **ARG_UNUSED(pnode))
968 {
969     Node *child;
970
971     if ( nodeIsDIR(node) || nodeIsUL(node) || nodeIsOL(node) )
972     {
973         child = node->content;
974
975         if (child == NULL)
976             return no;
977
978         /* check child has no peers */
979
980         if (child->next)
981             return no;
982
983         if ( !nodeIsLI(child) )
984             return no;
985
986         if ( !child->implicit )
987             return no;
988
989         /* coerce dir to div */
990         node->tag = TY_(LookupTagDef)( TidyTag_DIV );
991         TidyDocFree( doc, node->element );
992         node->element = TY_(tmbstrdup)(doc->allocator, "div");
993         TY_(AddStyleProperty)( doc, node, "margin-left: 2em" );
994         StripOnlyChild( doc, node );
995         return yes;
996     }
997
998     return no;
999 }
1000
1001 /*
1002     Symptom: <center>
1003     Action: replace <center> by <div style="text-align: center">
1004 */
1005
1006 static Bool Center2Div( TidyDocImpl* doc, Node *node, Node **pnode)
1007 {
1008     if ( nodeIsCENTER(node) )
1009     {
1010 #if 0 // 00000000 what is this doing inside an nodeIsCENTER(node)??? 0000000
1011         if ( cfgBool(doc, TidyDropFontTags) )
1012         {
1013             if (node->content)
1014             {
1015                 Node *last = node->last;
1016                 DiscardContainer( doc, node, pnode );
1017
1018                 node = TY_(InferredTag)(doc, TidyTag_BR);
1019                 TY_(InsertNodeAfterElement)(last, node);
1020             }
1021             else
1022             {
1023                 Node *prev = node->prev, *next = node->next,
1024                      *parent = node->parent;
1025                 DiscardContainer( doc, node, pnode );
1026
1027                 node = TY_(InferredTag)(doc, TidyTag_BR);
1028                 if (next)
1029                     TY_(InsertNodeBeforeElement)(next, node);
1030                 else if (prev)
1031                     TY_(InsertNodeAfterElement)(prev, node);
1032                 else
1033                     TY_(InsertNodeAtStart)(parent, node);
1034             }
1035
1036             return yes;
1037         }
1038 #endif // 00000000 what is this doing inside an nodeIsCENTER(node)??? 0000000
1039         RenameElem( doc, node, TidyTag_DIV );
1040         TY_(AddStyleProperty)( doc, node, "text-align: center" );
1041         return yes;
1042     }
1043
1044     return no;
1045 }
1046
1047 /* Copy child attributes to node. Duplicate attributes are overwritten.
1048    Unique attributes (such as ID) disable the action.
1049    Attributes style and class are not dealt with. A call to MergeStyles
1050    will do that.
1051 */
1052 static Bool CopyAttrs( TidyDocImpl* doc, Node *node, Node *child)
1053 {
1054     AttVal *av1, *av2;
1055     TidyAttrId id;
1056
1057     /* Detect attributes that cannot be merged or overwritten. */
1058     if (TY_(AttrGetById)(child, TidyAttr_ID) != NULL
1059         && TY_(AttrGetById)(node, TidyAttr_ID) != NULL)
1060         return no;
1061
1062     /* Move child attributes to node. Attributes in node
1063      can be overwritten or merged. */
1064     for (av2 = child->attributes; av2; )
1065     {
1066         /* Dealt by MergeStyles. */
1067         if (attrIsSTYLE(av2) || attrIsCLASS(av2))
1068         {
1069             av2 = av2->next;
1070             continue;
1071         }
1072         /* Avoid duplicates in node */
1073         if ((id=AttrId(av2)) != TidyAttr_UNKNOWN
1074             && (av1=TY_(AttrGetById)(node, id))!= NULL)
1075             TY_(RemoveAttribute)( doc, node, av1 );
1076
1077         /* Move attribute from child to node */
1078         TY_(DetachAttribute)( child, av2 );
1079         av1 = av2;
1080         av2 = av2->next;
1081         av1->next = NULL;
1082         TY_(InsertAttributeAtEnd)( node, av1 );
1083     }
1084
1085     return yes;
1086 }
1087
1088 /*
1089     Symptom <XX><XX>...</XX></XX>
1090     Action: merge the two XXs
1091
1092   For instance, this is useful after nested <dir>s used by Word
1093   for indenting have been converted to <div>s
1094
1095   If state is "no", no merging.
1096   If state is "yes", inner element is discarded. Only Style and Class
1097   attributes are merged using MergeStyles().
1098   If state is "auto", atttibutes are merged as described in CopyAttrs().
1099   Style and Class attributes are merged using MergeStyles().
1100 */
1101 static Bool MergeNestedElements( TidyDocImpl* doc,
1102                                  TidyTagId Id, TidyTriState state, Node *node,
1103                                  Node **ARG_UNUSED(pnode))
1104 {
1105     Node *child;
1106
1107     if ( state == TidyNoState
1108          || !TagIsId(node, Id) )
1109         return no;
1110
1111     child = node->content;
1112
1113     if ( child == NULL
1114          || child->next != NULL
1115          || !TagIsId(child, Id) )
1116         return no;
1117
1118     if ( state == TidyAutoState
1119          && CopyAttrs(doc, node, child) == no )
1120         return no;
1121
1122     MergeStyles( doc, node, child );
1123     StripOnlyChild( doc, node );
1124     return yes;
1125 }
1126
1127 /*
1128     Symptom: <ul><li><ul>...</ul></li></ul>
1129     Action: discard outer list
1130 */
1131
1132 static Bool NestedList( TidyDocImpl* doc, Node *node, Node **pnode )
1133 {
1134     Node *child, *list;
1135
1136     if ( nodeIsUL(node) || nodeIsOL(node) )
1137     {
1138         child = node->content;
1139
1140         if (child == NULL)
1141             return no;
1142
1143         /* check child has no peers */
1144
1145         if (child->next)
1146             return no;
1147
1148         list = child->content;
1149
1150         if (!list)
1151             return no;
1152
1153         if (list->tag != node->tag)
1154             return no;
1155
1156         /* check list has no peers */
1157         if (list->next)
1158             return no;
1159
1160         *pnode = list;  /* Set node to resume iteration */
1161
1162         /* move inner list node into position of outer node */
1163         list->prev = node->prev;
1164         list->next = node->next;
1165         list->parent = node->parent;
1166         TY_(FixNodeLinks)(list);
1167
1168         /* get rid of outer ul and its li */
1169         child->content = NULL;
1170         TY_(FreeNode)( doc, child ); /* See test #427841. */
1171         child = NULL;
1172         node->content = NULL;
1173         node->next = NULL;
1174         TY_(FreeNode)( doc, node );
1175         node = NULL;
1176
1177         /*
1178           If prev node was a list the chances are this node
1179           should be appended to that list. Word has no way of
1180           recognizing nested lists and just uses indents
1181         */
1182
1183         if (list->prev)
1184         {
1185             if ( (nodeIsUL(list->prev) || nodeIsOL(list->prev))
1186                  && list->prev->last )
1187             {
1188                 node = list;
1189                 list = node->prev;
1190
1191                 child = list->last;  /* <li> */
1192
1193                 list->next = node->next;
1194                 TY_(FixNodeLinks)(list);
1195
1196                 node->parent = child;
1197                 node->next = NULL;
1198                 node->prev = child->last;
1199                 TY_(FixNodeLinks)(node);
1200                 CleanNode( doc, node );
1201             }
1202         }
1203
1204         return yes;
1205     }
1206
1207     return no;
1208 }
1209
1210 /* Find CSS equivalent in a SPAN element */
1211 static
1212 Bool FindCSSSpanEq( Node *node, ctmbstr *s, Bool deprecatedOnly )
1213 {
1214     struct
1215     {
1216         TidyTagId id;
1217         ctmbstr CSSeq;
1218         Bool deprecated;
1219     }
1220     const CSS_SpanEq[] =
1221         {
1222             { TidyTag_B, "font-weight: bold", no },
1223             { TidyTag_I, "font-style: italic", no },
1224             { TidyTag_S, "text-decoration: line-through", yes},
1225             { TidyTag_STRIKE, "text-decoration: line-through", yes},
1226             { TidyTag_U, "text-decoration: underline", yes},
1227             { TidyTag_UNKNOWN, NULL, no }
1228         };
1229     uint i;
1230
1231     for (i=0; CSS_SpanEq[i].CSSeq; ++i)
1232         if ( (!deprecatedOnly || CSS_SpanEq[i].deprecated)
1233              && TagIsId(node, CSS_SpanEq[i].id) )
1234         {
1235             *s = CSS_SpanEq[i].CSSeq;
1236             return yes;
1237         }
1238     return no; 
1239 }
1240
1241 /* Necessary conditions to apply BlockStyle(). */
1242 static Bool CanApplyBlockStyle( Node *node )
1243 {
1244     if (TY_(nodeHasCM)(node,CM_BLOCK | CM_LIST | CM_DEFLIST | CM_TABLE)
1245         && !nodeIsDIV(node) && !nodeIsP(node)
1246         && !nodeIsTABLE(node) && !nodeIsTR(node) && !nodeIsLI(node) )
1247     {
1248         return yes;
1249     }
1250     return no;
1251 }
1252
1253 /*
1254   Symptom: the only child of a block-level element is a
1255   presentation element such as B, I or FONT
1256
1257   Action: add style "font-weight: bold" to the block and
1258   strip the <b> element, leaving its children.
1259
1260   example:
1261
1262     <p>
1263       <b><font face="Arial" size="6">Draft Recommended Practice</font></b>
1264     </p>
1265
1266   becomes:
1267
1268       <p style="font-weight: bold; font-family: Arial; font-size: 6">
1269         Draft Recommended Practice
1270       </p>
1271
1272   This code also replaces the align attribute by a style attribute.
1273   However, to avoid CSS problems with Navigator 4, this isn't done
1274   for the elements: caption, tr and table
1275 */
1276 static Bool BlockStyle( TidyDocImpl* doc, Node *node, Node **ARG_UNUSED(pnode) )
1277 {
1278     Node *child;
1279     ctmbstr CSSeq;
1280
1281     /* check for bgcolor */
1282     if (   nodeIsTABLE(node)
1283         || nodeIsTD(node) || nodeIsTH(node) || nodeIsTR( node ))
1284         TableBgColor( doc, node );
1285
1286     if (CanApplyBlockStyle(node))
1287     {
1288         /* check for align attribute */
1289         if ( !nodeIsCAPTION(node) )
1290             TextAlign( doc, node );
1291
1292         child = node->content;
1293         if (child == NULL)
1294             return no;
1295
1296         /* check child has no peers */
1297         if (child->next)
1298             return no;
1299
1300         if ( FindCSSSpanEq(child, &CSSeq, no) )
1301         {
1302             MergeStyles( doc, node, child );
1303             TY_(AddStyleProperty)( doc, node, CSSeq );
1304             StripOnlyChild( doc, node );
1305             return yes;
1306         }
1307         else if ( nodeIsFONT(child) )
1308         {
1309             MergeStyles( doc, node, child );
1310             AddFontStyles( doc, node, child->attributes );
1311             StripOnlyChild( doc, node );
1312             return yes;
1313         }
1314     }
1315
1316     return no;
1317 }
1318
1319 /* Necessary conditions to apply InlineStyle(). */
1320 static Bool CanApplyInlineStyle( Node *node )
1321 {
1322     return !nodeIsFONT(node) && TY_(nodeHasCM)(node, CM_INLINE|CM_ROW);
1323 }
1324
1325 /* the only child of table cell or an inline element such as em */
1326 static Bool InlineStyle( TidyDocImpl* doc, Node *node, Node **ARG_UNUSED(pnode) )
1327 {
1328     Node *child;
1329     ctmbstr CSSeq;
1330
1331     if ( CanApplyInlineStyle(node) )
1332     {
1333         child = node->content;
1334
1335         if (child == NULL)
1336             return no;
1337
1338         /* check child has no peers */
1339
1340         if (child->next)
1341             return no;
1342
1343         if ( FindCSSSpanEq(child, &CSSeq, no) )
1344         {
1345             MergeStyles( doc, node, child );
1346             TY_(AddStyleProperty)( doc, node, CSSeq );
1347             StripOnlyChild( doc, node );
1348             return yes;
1349         }
1350         else if ( nodeIsFONT(child) )
1351         {
1352             MergeStyles( doc, node, child );
1353             AddFontStyles( doc, node, child->attributes );
1354             StripOnlyChild( doc, node );
1355             return yes;
1356         }
1357     }
1358
1359     return no;
1360 }
1361
1362 /*
1363     Transform element to equivalent CSS
1364 */
1365 static Bool InlineElementToCSS( TidyDocImpl* doc, Node* node,
1366                                 Node **ARG_UNUSED(pnode)  )
1367 {
1368     ctmbstr CSSeq;
1369
1370     /* if node is the only child of parent element then leave alone
1371           Do so only if BlockStyle may be succesful. */
1372     if ( node->parent->content == node && node->next == NULL &&
1373          (CanApplyBlockStyle(node->parent)
1374           || CanApplyInlineStyle(node->parent)) )
1375         return no;
1376
1377     if ( FindCSSSpanEq(node, &CSSeq, yes) )
1378     {
1379         RenameElem( doc, node, TidyTag_SPAN );
1380         TY_(AddStyleProperty)( doc, node, CSSeq );
1381         return yes;
1382     }
1383     return no;
1384
1385
1386 /*
1387   Replace font elements by span elements, deleting
1388   the font element's attributes and replacing them
1389   by a single style attribute.
1390 */
1391 static Bool Font2Span( TidyDocImpl* doc, Node *node, Node **pnode )
1392 {
1393     AttVal *av, *style, *next;
1394
1395     if ( nodeIsFONT(node) )
1396     {
1397         if ( cfgBool(doc, TidyDropFontTags) )
1398         {
1399             DiscardContainer( doc, node, pnode );
1400             return yes;
1401         }
1402
1403         /* if node is the only child of parent element then leave alone
1404           Do so only if BlockStyle may be succesful. */
1405         if ( node->parent->content == node && node->next == NULL &&
1406              CanApplyBlockStyle(node->parent) )
1407             return no;
1408
1409         AddFontStyles( doc, node, node->attributes );
1410
1411         /* extract style attribute and free the rest */
1412         av = node->attributes;
1413         style = NULL;
1414
1415         while (av)
1416         {
1417             next = av->next;
1418
1419             if (attrIsSTYLE(av))
1420             {
1421                 av->next = NULL;
1422                 style = av;
1423             }
1424             else
1425             {
1426                 TY_(FreeAttribute)( doc, av );
1427             }
1428             av = next;
1429         }
1430
1431         node->attributes = style;
1432         RenameElem( doc, node, TidyTag_SPAN );
1433         return yes;
1434     }
1435
1436     return no;
1437 }
1438
1439 /*
1440   Applies all matching rules to a node.
1441 */
1442 Node* CleanNode( TidyDocImpl* doc, Node *node )
1443 {
1444     Node *next = NULL;
1445     TidyTriState mergeDivs = cfgAutoBool(doc, TidyMergeDivs);
1446     TidyTriState mergeSpans = cfgAutoBool(doc, TidyMergeSpans);
1447
1448     for (next = node; TY_(nodeIsElement)(node); node = next)
1449     {
1450         if ( Dir2Div(doc, node, &next) )
1451             continue;
1452
1453         /* Special case: true result means
1454         ** that arg node and its parent no longer exist.
1455         ** So we must jump back up the CreateStyleProperties()
1456         ** call stack until we have a valid node reference.
1457         */
1458         if ( NestedList(doc, node, &next) )
1459             return next;
1460
1461         if ( Center2Div(doc, node, &next) )
1462             continue;
1463
1464         if ( MergeNestedElements(doc, TidyTag_DIV, mergeDivs, node, &next) )
1465             continue;
1466
1467         if ( MergeNestedElements(doc, TidyTag_SPAN, mergeSpans, node, &next) )
1468             continue;
1469
1470         if ( BlockStyle(doc, node, &next) )
1471             continue;
1472
1473         if ( InlineStyle(doc, node, &next) )
1474             continue;
1475
1476         if ( InlineElementToCSS(doc, node, &next) )
1477             continue;
1478
1479         if ( Font2Span(doc, node, &next) )
1480             continue;
1481
1482         break;
1483     }
1484
1485     return next;
1486 }
1487
1488 /* Special case: if the current node is destroyed by
1489 ** CleanNode() lower in the tree, this node and its parent
1490 ** no longer exist.  So we must jump back up the CleanTree()
1491 ** call stack until we have a valid node reference.
1492 */
1493
1494 static Node* CleanTree( TidyDocImpl* doc, Node *node )
1495 {
1496     if (node->content)
1497     {
1498         Node *child;
1499         for (child = node->content; child != NULL; child = child->next)
1500         {
1501             child = CleanTree( doc, child );
1502             if ( !child )
1503                 break;
1504         }
1505     }
1506
1507     return CleanNode( doc, node );
1508 }
1509
1510 static void DefineStyleRules( TidyDocImpl* doc, Node *node )
1511 {
1512     Node *child;
1513
1514     if (node->content)
1515     {
1516         for (child = node->content;
1517                 child != NULL; child = child->next)
1518         {
1519             DefineStyleRules( doc, child );
1520         }
1521     }
1522
1523     Style2Rule( doc, node );
1524 }
1525
1526 void TY_(CleanDocument)( TidyDocImpl* doc )
1527 {
1528     /* placeholder.  CleanTree()/CleanNode() will not
1529     ** zap root element 
1530     */
1531     CleanTree( doc, &doc->root );
1532
1533     if ( cfgBool(doc, TidyMakeClean) )
1534     {
1535         DefineStyleRules( doc, &doc->root );
1536         CreateStyleElement( doc );
1537     }
1538 }
1539
1540 /* simplifies <b><b> ... </b> ...</b> etc. */
1541 void TY_(NestedEmphasis)( TidyDocImpl* doc, Node* node )
1542 {
1543     Node *next;
1544
1545     while (node)
1546     {
1547         next = node->next;
1548
1549         if ( (nodeIsB(node) || nodeIsI(node))
1550              && node->parent && node->parent->tag == node->tag)
1551         {
1552             /* strip redundant inner element */
1553             DiscardContainer( doc, node, &next );
1554             node = next;
1555             continue;
1556         }
1557
1558         if ( node->content )
1559             TY_(NestedEmphasis)( doc, node->content );
1560
1561         node = next;
1562     }
1563 }
1564
1565
1566
1567 /* replace i by em and b by strong */
1568 void TY_(EmFromI)( TidyDocImpl* doc, Node* node )
1569 {
1570     while (node)
1571     {
1572         if ( nodeIsI(node) )
1573             RenameElem( doc, node, TidyTag_EM );
1574         else if ( nodeIsB(node) )
1575             RenameElem( doc, node, TidyTag_STRONG );
1576
1577         if ( node->content )
1578             TY_(EmFromI)( doc, node->content );
1579
1580         node = node->next;
1581     }
1582 }
1583
1584 static Bool HasOneChild(Node *node)
1585 {
1586     return (node->content && node->content->next == NULL);
1587 }
1588
1589 /*
1590  Some people use dir or ul without an li
1591  to indent the content. The pattern to
1592  look for is a list with a single implicit
1593  li. This is recursively replaced by an
1594  implicit blockquote.
1595 */
1596 void TY_(List2BQ)( TidyDocImpl* doc, Node* node )
1597 {
1598     while (node)
1599     {
1600         if (node->content)
1601             TY_(List2BQ)( doc, node->content );
1602
1603         if ( node->tag && node->tag->parser == TY_(ParseList) &&
1604              HasOneChild(node) && node->content->implicit )
1605         {
1606             StripOnlyChild( doc, node );
1607             RenameElem( doc, node, TidyTag_BLOCKQUOTE );
1608             node->implicit = yes;
1609         }
1610
1611         node = node->next;
1612     }
1613 }
1614
1615
1616 /*
1617  Replace implicit blockquote by div with an indent
1618  taking care to reduce nested blockquotes to a single
1619  div with the indent set to match the nesting depth
1620 */
1621 void TY_(BQ2Div)( TidyDocImpl* doc, Node *node )
1622 {
1623     tmbchar indent_buf[ 32 ];
1624     uint indent;
1625
1626     while (node)
1627     {
1628         if ( nodeIsBLOCKQUOTE(node) && node->implicit )
1629         {
1630             indent = 1;
1631
1632             while( HasOneChild(node) &&
1633                    nodeIsBLOCKQUOTE(node->content) &&
1634                    node->implicit)
1635             {
1636                 ++indent;
1637                 StripOnlyChild( doc, node );
1638             }
1639
1640             if (node->content)
1641                 TY_(BQ2Div)( doc, node->content );
1642
1643             TY_(tmbsnprintf)(indent_buf, sizeof(indent_buf), "margin-left: %dem",
1644                              2*indent);
1645
1646             RenameElem( doc, node, TidyTag_DIV );
1647             TY_(AddStyleProperty)(doc, node, indent_buf );
1648         }
1649         else if (node->content)
1650             TY_(BQ2Div)( doc, node->content );
1651
1652         node = node->next;
1653     }
1654 }
1655
1656
1657 static Node* FindEnclosingCell( TidyDocImpl* ARG_UNUSED(doc), Node *node)
1658 {
1659     Node *check;
1660
1661     for ( check=node; check; check = check->parent )
1662     {
1663       if ( nodeIsTD(check) )
1664         return check;
1665     }
1666     return NULL;
1667 }
1668
1669 /* node is <![if ...]> prune up to <![endif]> */
1670 static Node* PruneSection( TidyDocImpl* doc, Node *node )
1671 {
1672     Lexer* lexer = doc->lexer;
1673
1674     for (;;)
1675     {
1676         ctmbstr lexbuf = lexer->lexbuf + node->start;
1677         if ( TY_(tmbstrncmp)(lexbuf, "if !supportEmptyParas", 21) == 0 )
1678         {
1679           Node* cell = FindEnclosingCell( doc, node );
1680           if ( cell )
1681           {
1682             /* Need to put &nbsp; into cell so it doesn't look weird
1683             */
1684             Node* nbsp = TY_(NewLiteralTextNode)( lexer, "\240" );
1685             assert( (byte)'\240' == (byte)160 );
1686             TY_(InsertNodeBeforeElement)( node, nbsp );
1687           }
1688         }
1689
1690         /* discard node and returns next, unless it is a text node */
1691         if ( node->type == TextNode )
1692             node = node->next;
1693         else
1694             node = TY_(DiscardElement)( doc, node );
1695
1696         if (node == NULL)
1697             return NULL;
1698         
1699         if (node->type == SectionTag)
1700         {
1701             if (TY_(tmbstrncmp)(lexer->lexbuf + node->start, "if", 2) == 0)
1702             {
1703                 node = PruneSection( doc, node );
1704                 continue;
1705             }
1706
1707             if (TY_(tmbstrncmp)(lexer->lexbuf + node->start, "endif", 5) == 0)
1708             {
1709                 node = TY_(DiscardElement)( doc, node );
1710                 break;
1711             }
1712         }
1713     }
1714
1715     return node;
1716 }
1717
1718 void TY_(DropSections)( TidyDocImpl* doc, Node* node )
1719 {
1720     Lexer* lexer = doc->lexer;
1721     while (node)
1722     {
1723         if (node->type == SectionTag)
1724         {
1725             /* prune up to matching endif */
1726             if ((TY_(tmbstrncmp)(lexer->lexbuf + node->start, "if", 2) == 0) &&
1727                 (TY_(tmbstrncmp)(lexer->lexbuf + node->start, "if !vml", 7) != 0)) /* #444394 - fix 13 Sep 01 */
1728             {
1729                 node = PruneSection( doc, node );
1730                 continue;
1731             }
1732
1733             /* discard others as well */
1734             node = TY_(DiscardElement)( doc, node );
1735             continue;
1736         }
1737
1738         if (node->content)
1739             TY_(DropSections)( doc, node->content );
1740
1741         node = node->next;
1742     }
1743 }
1744
1745 static void PurgeWord2000Attributes( TidyDocImpl* doc, Node* node )
1746 {
1747     AttVal *attr, *next, *prev = NULL;
1748
1749     for ( attr = node->attributes; attr; attr = next )
1750     {
1751         next = attr->next;
1752
1753         /* special check for class="Code" denoting pre text */
1754         /* Pass thru user defined styles as HTML class names */
1755         if (attrIsCLASS(attr))
1756         {
1757             if (AttrValueIs(attr, "Code") ||
1758                  TY_(tmbstrncmp)(attr->value, "Mso", 3) != 0 )
1759             {
1760                 prev = attr;
1761                 continue;
1762             }
1763         }
1764
1765         if (attrIsCLASS(attr) ||
1766             attrIsSTYLE(attr) ||
1767             attrIsLANG(attr)  ||
1768              ( (attrIsHEIGHT(attr) || attrIsWIDTH(attr)) &&
1769                (nodeIsTD(node) || nodeIsTR(node) || nodeIsTH(node)) ) ||
1770              (attr->attribute && TY_(tmbstrncmp)(attr->attribute, "x:", 2) == 0) )
1771         {
1772             if (prev)
1773                 prev->next = next;
1774             else
1775                 node->attributes = next;
1776
1777             TY_(FreeAttribute)( doc, attr );
1778         }
1779         else
1780             prev = attr;
1781     }
1782 }
1783
1784 /* Word2000 uses span excessively, so we strip span out */
1785 static Node* StripSpan( TidyDocImpl* doc, Node* span )
1786 {
1787     Node *node, *prev = NULL, *content;
1788
1789     /*
1790      deal with span elements that have content
1791      by splicing the content in place of the span
1792      after having processed it
1793     */
1794
1795     TY_(CleanWord2000)( doc, span->content );
1796     content = span->content;
1797
1798     if (span->prev)
1799         prev = span->prev;
1800     else if (content)
1801     {
1802         node = content;
1803         content = content->next;
1804         TY_(RemoveNode)(node);
1805         TY_(InsertNodeBeforeElement)(span, node);
1806         prev = node;
1807     }
1808
1809     while (content)
1810     {
1811         node = content;
1812         content = content->next;
1813         TY_(RemoveNode)(node);
1814         TY_(InsertNodeAfterElement)(prev, node);
1815         prev = node;
1816     }
1817
1818     if (span->next == NULL)
1819         span->parent->last = prev;
1820
1821     node = span->next;
1822     span->content = NULL;
1823     TY_(DiscardElement)( doc, span );
1824     return node;
1825 }
1826
1827 /* map non-breaking spaces to regular spaces */
1828 void TY_(NormalizeSpaces)(Lexer *lexer, Node *node)
1829 {
1830     while ( node )
1831     {
1832         if ( node->content )
1833             TY_(NormalizeSpaces)( lexer, node->content );
1834
1835         if (TY_(nodeIsText)(node))
1836         {
1837             uint i, c;
1838             tmbstr p = lexer->lexbuf + node->start;
1839
1840             for (i = node->start; i < node->end; ++i)
1841             {
1842                 c = (byte) lexer->lexbuf[i];
1843
1844                 /* look for UTF-8 multibyte character */
1845                 if ( c > 0x7F )
1846                     i += TY_(GetUTF8)( lexer->lexbuf + i, &c );
1847
1848                 if ( c == 160 )
1849                     c = ' ';
1850
1851                 p = TY_(PutUTF8)(p, c);
1852             }
1853             node->end = p - lexer->lexbuf;
1854         }
1855
1856         node = node->next;
1857     }
1858 }
1859
1860 /* used to hunt for hidden preformatted sections */
1861 static Bool NoMargins(Node *node)
1862 {
1863     AttVal *attval = TY_(AttrGetById)(node, TidyAttr_STYLE);
1864
1865     if ( !AttrHasValue(attval) )
1866         return no;
1867
1868     /* search for substring "margin-top: 0" */
1869     if (!TY_(tmbsubstr)(attval->value, "margin-top: 0"))
1870         return no;
1871
1872     /* search for substring "margin-bottom: 0" */
1873     if (!TY_(tmbsubstr)(attval->value, "margin-bottom: 0"))
1874         return no;
1875
1876     return yes;
1877 }
1878
1879 /* does element have a single space as its content? */
1880 static Bool SingleSpace( Lexer* lexer, Node* node )
1881 {
1882     if ( node->content )
1883     {
1884         node = node->content;
1885
1886         if ( node->next != NULL )
1887             return no;
1888
1889         if ( node->type != TextNode )
1890             return no;
1891
1892         if ( (node->end - node->start) == 1 &&
1893              lexer->lexbuf[node->start] == ' ' )
1894             return yes;
1895
1896         if ( (node->end - node->start) == 2 )
1897         {
1898             uint c = 0;
1899             TY_(GetUTF8)( lexer->lexbuf + node->start, &c );
1900             if ( c == 160 )
1901                 return yes;
1902         }
1903     }
1904
1905     return no;
1906 }
1907
1908 /*
1909  This is a major clean up to strip out all the extra stuff you get
1910  when you save as web page from Word 2000. It doesn't yet know what
1911  to do with VML tags, but these will appear as errors unless you
1912  declare them as new tags, such as o:p which needs to be declared
1913  as inline.
1914 */
1915 void TY_(CleanWord2000)( TidyDocImpl* doc, Node *node)
1916 {
1917     /* used to a list from a sequence of bulletted p's */
1918     Lexer* lexer = doc->lexer;
1919     Node* list = NULL;
1920
1921     while ( node )
1922     {
1923         /* get rid of Word's xmlns attributes */
1924         if ( nodeIsHTML(node) )
1925         {
1926             /* check that it's a Word 2000 document */
1927             if ( !TY_(GetAttrByName)(node, "xmlns:o") &&
1928                  !cfgBool(doc, TidyMakeBare) )
1929                 return;
1930
1931             TY_(FreeAttrs)( doc, node );
1932         }
1933
1934         /* fix up preformatted sections by looking for a
1935         ** sequence of paragraphs with zero top/bottom margin
1936         */
1937         if ( nodeIsP(node) )
1938         {
1939             if (NoMargins(node))
1940             {
1941                 Node *pre, *next;
1942                 TY_(CoerceNode)(doc, node, TidyTag_PRE, no, yes);
1943
1944                 PurgeWord2000Attributes( doc, node );
1945
1946                 if (node->content)
1947                     TY_(CleanWord2000)( doc, node->content );
1948
1949                 pre = node;
1950                 node = node->next;
1951
1952                 /* continue to strip p's */
1953
1954                 while ( nodeIsP(node) && NoMargins(node) )
1955                 {
1956                     next = node->next;
1957                     TY_(RemoveNode)(node);
1958                     TY_(InsertNodeAtEnd)(pre, TY_(NewLineNode)(lexer));
1959                     TY_(InsertNodeAtEnd)(pre, node);
1960                     StripSpan( doc, node );
1961                     node = next;
1962                 }
1963
1964                 if (node == NULL)
1965                     break;
1966             }
1967         }
1968
1969         if (node->tag && (node->tag->model & CM_BLOCK)
1970             && SingleSpace(lexer, node))
1971         {
1972             node = StripSpan( doc, node );
1973             continue;
1974         }
1975         /* discard Word's style verbiage */
1976         if ( nodeIsSTYLE(node) || nodeIsMETA(node) ||
1977              node->type == CommentTag )
1978         {
1979             node = TY_(DiscardElement)( doc, node );
1980             continue;
1981         }
1982
1983         /* strip out all span and font tags Word scatters so liberally! */
1984         if ( nodeIsSPAN(node) || nodeIsFONT(node) )
1985         {
1986             node = StripSpan( doc, node );
1987             continue;
1988         }
1989
1990         if ( nodeIsLINK(node) )
1991         {
1992             AttVal *attr = TY_(AttrGetById)(node, TidyAttr_REL);
1993
1994             if (AttrValueIs(attr, "File-List"))
1995             {
1996                 node = TY_(DiscardElement)( doc, node );
1997                 continue;
1998             }
1999         }
2000
2001         /* discards <o:p> which encodes the paragraph mark */
2002         if ( node->tag && TY_(tmbstrcmp)(node->tag->name,"o:p")==0)
2003         {
2004             Node* next;
2005             DiscardContainer( doc, node, &next );
2006             node = next;
2007             continue;
2008         }
2009
2010         /* discard empty paragraphs */
2011
2012         if ( node->content == NULL && nodeIsP(node) )
2013         {
2014             /*  Use the existing function to ensure consistency */
2015             Node *next = TY_(TrimEmptyElement)( doc, node );
2016             node = next;
2017             continue;
2018         }
2019
2020         if ( nodeIsP(node) )
2021         {
2022             AttVal *attr, *atrStyle;
2023             
2024             attr = TY_(AttrGetById)(node, TidyAttr_CLASS);
2025             atrStyle = TY_(AttrGetById)(node, TidyAttr_STYLE);
2026             /*
2027                (JES) Sometimes Word marks a list item with the following hokie syntax
2028                <p class="MsoNormal" style="...;mso-list:l1 level1 lfo1;
2029                 translate these into <li>
2030             */
2031             /* map sequence of <p class="MsoListBullet"> to <ul>...</ul> */
2032             /* map <p class="MsoListNumber"> to <ol>...</ol> */
2033             if ( AttrValueIs(attr, "MsoListBullet") ||
2034                  AttrValueIs(attr, "MsoListNumber") ||
2035                  AttrContains(atrStyle, "mso-list:") )
2036             {
2037                 TidyTagId listType = TidyTag_UL;
2038                 if (AttrValueIs(attr, "MsoListNumber"))
2039                     listType = TidyTag_OL;
2040
2041                 TY_(CoerceNode)(doc, node, TidyTag_LI, no, yes);
2042
2043                 if ( !list || TagId(list) != listType )
2044                 {
2045                     const Dict* tag = TY_(LookupTagDef)( listType );
2046                     list = TY_(InferredTag)(doc, tag->id);
2047                     TY_(InsertNodeBeforeElement)(node, list);
2048                 }
2049
2050                 PurgeWord2000Attributes( doc, node );
2051
2052                 if ( node->content )
2053                     TY_(CleanWord2000)( doc, node->content );
2054
2055                 /* remove node and append to contents of list */
2056                 TY_(RemoveNode)(node);
2057                 TY_(InsertNodeAtEnd)(list, node);
2058                 node = list;
2059             }
2060             /* map sequence of <p class="Code"> to <pre>...</pre> */
2061             else if (AttrValueIs(attr, "Code"))
2062             {
2063                 Node *br = TY_(NewLineNode)(lexer);
2064                 TY_(NormalizeSpaces)(lexer, node->content);
2065
2066                 if ( !list || TagId(list) != TidyTag_PRE )
2067                 {
2068                     list = TY_(InferredTag)(doc, TidyTag_PRE);
2069                     TY_(InsertNodeBeforeElement)(node, list);
2070                 }
2071
2072                 /* remove node and append to contents of list */
2073                 TY_(RemoveNode)(node);
2074                 TY_(InsertNodeAtEnd)(list, node);
2075                 StripSpan( doc, node );
2076                 TY_(InsertNodeAtEnd)(list, br);
2077                 node = list->next;
2078             }
2079             else
2080                 list = NULL;
2081         }
2082         else
2083             list = NULL;
2084
2085         if (!node)
2086             return;
2087
2088         /* strip out style and class attributes */
2089         if (TY_(nodeIsElement)(node))
2090             PurgeWord2000Attributes( doc, node );
2091
2092         if (node->content)
2093             TY_(CleanWord2000)( doc, node->content );
2094
2095         node = node->next;
2096     }
2097 }
2098
2099 Bool TY_(IsWord2000)( TidyDocImpl* doc )
2100 {
2101     AttVal *attval;
2102     Node *node, *head;
2103     Node *html = TY_(FindHTML)( doc );
2104
2105     if (html && TY_(GetAttrByName)(html, "xmlns:o"))
2106         return yes;
2107     
2108     /* search for <meta name="GENERATOR" content="Microsoft ..."> */
2109     head = TY_(FindHEAD)( doc );
2110
2111     if (head)
2112     {
2113         for (node = head->content; node; node = node->next)
2114         {
2115             if ( !nodeIsMETA(node) )
2116                 continue;
2117
2118             attval = TY_(AttrGetById)( node, TidyAttr_NAME );
2119
2120             if ( !AttrValueIs(attval, "generator") )
2121                 continue;
2122
2123             attval =  TY_(AttrGetById)( node, TidyAttr_CONTENT );
2124
2125             if ( AttrContains(attval, "Microsoft") )
2126                 return yes;
2127         }
2128     }
2129
2130     return no;
2131 }
2132
2133 /* where appropriate move object elements from head to body */
2134 void TY_(BumpObject)( TidyDocImpl* doc, Node *html )
2135 {
2136     Node *node, *next, *head = NULL, *body = NULL;
2137
2138     if (!html)
2139         return;
2140
2141     for ( node = html->content; node != NULL; node = node->next )
2142     {
2143         if ( nodeIsHEAD(node) )
2144             head = node;
2145
2146         if ( nodeIsBODY(node) )
2147             body = node;
2148     }
2149
2150     if ( head != NULL && body != NULL )
2151     {
2152         for (node = head->content; node != NULL; node = next)
2153         {
2154             next = node->next;
2155
2156             if ( nodeIsOBJECT(node) )
2157             {
2158                 Node *child;
2159                 Bool bump = no;
2160
2161                 for (child = node->content; child != NULL; child = child->next)
2162                 {
2163                     /* bump to body unless content is param */
2164                     if ( (TY_(nodeIsText)(child) && !TY_(IsBlank)(doc->lexer, node))
2165                          || !nodeIsPARAM(child) )
2166                     {
2167                             bump = yes;
2168                             break;
2169                     }
2170                 }
2171
2172                 if ( bump )
2173                 {
2174                     TY_(RemoveNode)( node );
2175                     TY_(InsertNodeAtStart)( body, node );
2176                 }
2177             }
2178         }
2179     }
2180 }
2181
2182 /* This is disabled due to http://tidy.sf.net/bug/681116 */
2183 #if 0
2184 void FixBrakes( TidyDocImpl* pDoc, Node *pParent )
2185 {
2186     Node *pNode;
2187     Bool bBRDeleted = no;
2188
2189     if (NULL == pParent)
2190         return;
2191
2192     /*  First, check the status of All My Children  */
2193     pNode = pParent->content;
2194     while (NULL != pNode )
2195     {
2196         /* The node may get trimmed, so save the next pointer, if any */
2197         Node *pNext = pNode->next;
2198         FixBrakes( pDoc, pNode );
2199         pNode = pNext;
2200     }
2201
2202
2203     /*  As long as my last child is a <br />, move it to my last peer  */
2204     if ( nodeCMIsBlock( pParent ))
2205     { 
2206         for ( pNode = pParent->last; 
2207               NULL != pNode && nodeIsBR( pNode ); 
2208               pNode = pParent->last ) 
2209         {
2210             if ( NULL == pNode->attributes && no == bBRDeleted )
2211             {
2212                 TY_(DiscardElement)( pDoc, pNode );
2213                 bBRDeleted = yes;
2214             }
2215             else
2216             {
2217                 TY_(RemoveNode)( pNode );
2218                 TY_(InsertNodeAfterElement)( pParent, pNode );
2219             }
2220         }
2221         TY_(TrimEmptyElement)( pDoc, pParent );
2222     }
2223 }
2224 #endif
2225
2226 void TY_(VerifyHTTPEquiv)(TidyDocImpl* doc, Node *head)
2227 {
2228     Node *pNode;
2229     StyleProp *pFirstProp = NULL, *pLastProp = NULL, *prop = NULL;
2230     tmbstr s, pszBegin, pszEnd;
2231     ctmbstr enc = TY_(GetEncodingNameFromTidyId)(cfg(doc, TidyOutCharEncoding));
2232
2233     if (!enc)
2234         return;
2235
2236     if (!nodeIsHEAD(head))
2237         head = TY_(FindHEAD)(doc);
2238
2239     if (!head)
2240         return;
2241
2242     /* Find any <meta http-equiv='Content-Type' content='...' /> */
2243     for (pNode = head->content; NULL != pNode; pNode = pNode->next)
2244     {
2245         AttVal* httpEquiv = TY_(AttrGetById)(pNode, TidyAttr_HTTP_EQUIV);
2246         AttVal* metaContent = TY_(AttrGetById)(pNode, TidyAttr_CONTENT);
2247
2248         if ( !nodeIsMETA(pNode) || !metaContent ||
2249              !AttrValueIs(httpEquiv, "Content-Type") )
2250             continue;
2251
2252         pszBegin = s = TY_(tmbstrdup)( doc->allocator, metaContent->value );
2253         while (pszBegin && *pszBegin)
2254         {
2255             while (isspace( *pszBegin ))
2256                 pszBegin++;
2257             pszEnd = pszBegin;
2258             while ('\0' != *pszEnd && ';' != *pszEnd)
2259                 pszEnd++;
2260             if (';' == *pszEnd )
2261                 *(pszEnd++) = '\0';
2262             if (pszEnd > pszBegin)
2263             {
2264                 prop = (StyleProp *)TidyDocAlloc(doc, sizeof(StyleProp));
2265                 prop->name = TY_(tmbstrdup)( doc->allocator, pszBegin );
2266                 prop->value = NULL;
2267                 prop->next = NULL;
2268
2269                 if (NULL != pLastProp)
2270                     pLastProp->next = prop;
2271                 else
2272                     pFirstProp = prop;
2273
2274                 pLastProp = prop;
2275                 pszBegin = pszEnd;
2276             }
2277         }
2278         TidyDocFree( doc, s );
2279
2280         /*  find the charset property */
2281         for (prop = pFirstProp; NULL != prop; prop = prop->next)
2282         {
2283             if (0 != TY_(tmbstrncasecmp)( prop->name, "charset", 7 ))
2284                 continue;
2285
2286             TidyDocFree( doc, prop->name );
2287             prop->name = (tmbstr)TidyDocAlloc( doc, 8 + TY_(tmbstrlen)(enc) + 1 );
2288             TY_(tmbstrcpy)(prop->name, "charset=");
2289             TY_(tmbstrcpy)(prop->name+8, enc);
2290             s = CreatePropString( doc, pFirstProp );
2291             TidyDocFree( doc, metaContent->value );
2292             metaContent->value = s;
2293             break;
2294         }
2295         /* #718127, prevent memory leakage */
2296         FreeStyleProps(doc, pFirstProp);
2297         pFirstProp = NULL;
2298         pLastProp = NULL;
2299     }
2300 }
2301
2302 void TY_(DropComments)(TidyDocImpl* doc, Node* node)
2303 {
2304     Node* next;
2305
2306     while (node)
2307     {
2308         next = node->next;
2309
2310         if (node->type == CommentTag)
2311         {
2312             TY_(RemoveNode)(node);
2313             TY_(FreeNode)(doc, node);
2314             node = next;
2315             continue;
2316         }
2317
2318         if (node->content)
2319             TY_(DropComments)(doc, node->content);
2320
2321         node = next;
2322     }
2323 }
2324
2325 void TY_(DropFontElements)(TidyDocImpl* doc, Node* node, Node **ARG_UNUSED(pnode))
2326 {
2327     Node* next;
2328
2329     while (node)
2330     {
2331         next = node->next;
2332
2333         if (nodeIsFONT(node))
2334         {
2335             DiscardContainer(doc, node, &next);
2336             node = next;
2337             continue;
2338         }
2339
2340         if (node->content)
2341             TY_(DropFontElements)(doc, node->content, &next);
2342
2343         node = next;
2344     }
2345 }
2346
2347 void TY_(WbrToSpace)(TidyDocImpl* doc, Node* node)
2348 {
2349     Node* next;
2350
2351     while (node)
2352     {
2353         next = node->next;
2354
2355         if (nodeIsWBR(node))
2356         {
2357             Node* text;
2358             text = TY_(NewLiteralTextNode)(doc->lexer, " ");
2359             TY_(InsertNodeAfterElement)(node, text);
2360             TY_(RemoveNode)(node);
2361             TY_(FreeNode)(doc, node);
2362             node = next;
2363             continue;
2364         }
2365
2366         if (node->content)
2367             TY_(WbrToSpace)(doc, node->content);
2368
2369         node = next;
2370    }
2371 }
2372
2373 /*
2374   Filters from Word and PowerPoint often use smart
2375   quotes resulting in character codes between 128
2376   and 159. Unfortunately, the corresponding HTML 4.0
2377   entities for these are not widely supported. The
2378   following converts dashes and quotation marks to
2379   the nearest ASCII equivalent. My thanks to
2380   Andrzej Novosiolov for his help with this code.
2381
2382   Note: The old code in the pretty printer applied
2383   this to all node types and attribute values while
2384   this routine applies it only to text nodes. First,
2385   Microsoft Office products rarely put the relevant
2386   characters into these tokens, second support for
2387   them is much better now and last but not least, it
2388   can be harmful to replace these characters since
2389   US-ASCII quote marks are often used as syntax
2390   characters, a simple
2391
2392     <a onmouseover="alert('&#x2018;')">...</a>
2393
2394   would be broken if the U+2018 is replaced by "'".
2395   The old code would neither take care whether the
2396   quote mark is already used as delimiter,
2397
2398     <p title='&#x2018;'>...</p>
2399
2400   got
2401   
2402     <p title='''>...</p>
2403
2404   Since browser support is much better nowadays and
2405   high-quality typography is better than ASCII it'd
2406   be probably a good idea to drop the feature...
2407 */
2408 void TY_(DowngradeTypography)(TidyDocImpl* doc, Node* node)
2409 {
2410     Node* next;
2411     Lexer* lexer = doc->lexer;
2412
2413     while (node)
2414     {
2415         next = node->next;
2416
2417         if (TY_(nodeIsText)(node))
2418         {
2419             uint i, c;
2420             tmbstr p = lexer->lexbuf + node->start;
2421
2422             for (i = node->start; i < node->end; ++i)
2423             {
2424                 c = (unsigned char) lexer->lexbuf[i];
2425
2426                 if (c > 0x7F)
2427                     i += TY_(GetUTF8)(lexer->lexbuf + i, &c);
2428
2429                 if (c >= 0x2013 && c <= 0x201E)
2430                 {
2431                     switch (c)
2432                     {
2433                     case 0x2013: /* en dash */
2434                     case 0x2014: /* em dash */
2435                         c = '-';
2436                         break;
2437                     case 0x2018: /* left single  quotation mark */
2438                     case 0x2019: /* right single quotation mark */
2439                     case 0x201A: /* single low-9 quotation mark */
2440                         c = '\'';
2441                         break;
2442                     case 0x201C: /* left double  quotation mark */
2443                     case 0x201D: /* right double quotation mark */
2444                     case 0x201E: /* double low-9 quotation mark */
2445                         c = '"';
2446                         break;
2447                     }
2448                 }
2449
2450                 p = TY_(PutUTF8)(p, c);
2451             }
2452
2453             node->end = p - lexer->lexbuf;
2454         }
2455
2456         if (node->content)
2457             TY_(DowngradeTypography)(doc, node->content);
2458
2459         node = next;
2460     }
2461 }
2462
2463 void TY_(ReplacePreformattedSpaces)(TidyDocImpl* doc, Node* node)
2464 {
2465     Node* next;
2466
2467     while (node)
2468     {
2469         next = node->next;
2470
2471         if (node->tag && node->tag->parser == TY_(ParsePre))
2472         {
2473             TY_(NormalizeSpaces)(doc->lexer, node->content);
2474             node = next;
2475             continue;
2476         }
2477
2478         if (node->content)
2479             TY_(ReplacePreformattedSpaces)(doc, node->content);
2480
2481         node = next;
2482     }
2483 }
2484
2485 void TY_(ConvertCDATANodes)(TidyDocImpl* doc, Node* node)
2486 {
2487     Node* next;
2488
2489     while (node)
2490     {
2491         next = node->next;
2492
2493         if (node->type == CDATATag)
2494             node->type = TextNode;
2495
2496         if (node->content)
2497             TY_(ConvertCDATANodes)(doc, node->content);
2498
2499         node = next;
2500     }
2501 }
2502
2503 /*
2504   FixLanguageInformation ensures that the document contains (only)
2505   the attributes for language information desired by the output
2506   document type. For example, for XHTML 1.0 documents both
2507   'xml:lang' and 'lang' are desired, for XHTML 1.1 only 'xml:lang'
2508   is desired and for HTML 4.01 only 'lang' is desired.
2509 */
2510 void TY_(FixLanguageInformation)(TidyDocImpl* doc, Node* node, Bool wantXmlLang, Bool wantLang)
2511 {
2512     Node* next;
2513
2514     while (node)
2515     {
2516         next = node->next;
2517
2518         /* todo: report modifications made here to the report system */
2519
2520         if (TY_(nodeIsElement)(node))
2521         {
2522             AttVal* lang = TY_(AttrGetById)(node, TidyAttr_LANG);
2523             AttVal* xmlLang = TY_(AttrGetById)(node, TidyAttr_XML_LANG);
2524
2525             if (lang && xmlLang)
2526             {
2527                 /*
2528                   todo: check whether both attributes are in sync,
2529                   here or elsewhere, where elsewhere is probably
2530                   preferable.
2531                   AD - March 2005: not mandatory according the standards.
2532                 */
2533             }
2534             else if (lang && wantXmlLang)
2535             {
2536                 if (TY_(NodeAttributeVersions)( node, TidyAttr_XML_LANG )
2537                     & doc->lexer->versionEmitted)
2538                     TY_(RepairAttrValue)(doc, node, "xml:lang", lang->value);
2539             }
2540             else if (xmlLang && wantLang)
2541             {
2542                 if (TY_(NodeAttributeVersions)( node, TidyAttr_LANG )
2543                     & doc->lexer->versionEmitted)
2544                     TY_(RepairAttrValue)(doc, node, "lang", xmlLang->value);
2545             }
2546
2547             if (lang && !wantLang)
2548                 TY_(RemoveAttribute)(doc, node, lang);
2549             
2550             if (xmlLang && !wantXmlLang)
2551                 TY_(RemoveAttribute)(doc, node, xmlLang);
2552         }
2553
2554         if (node->content)
2555             TY_(FixLanguageInformation)(doc, node->content, wantXmlLang, wantLang);
2556
2557         node = next;
2558     }
2559 }
2560
2561 /*
2562   Set/fix/remove <html xmlns='...'>
2563 */
2564 void TY_(FixXhtmlNamespace)(TidyDocImpl* doc, Bool wantXmlns)
2565 {
2566     Node* html = TY_(FindHTML)(doc);
2567     AttVal* xmlns;
2568
2569     if (!html)
2570         return;
2571
2572     xmlns = TY_(AttrGetById)(html, TidyAttr_XMLNS);
2573
2574     if (wantXmlns)
2575     {
2576         if (!AttrValueIs(xmlns, XHTML_NAMESPACE))
2577             TY_(RepairAttrValue)(doc, html, "xmlns", XHTML_NAMESPACE);
2578     }
2579     else if (xmlns)
2580     {
2581         TY_(RemoveAttribute)(doc, html, xmlns);
2582     }
2583 }
2584
2585 /*
2586   ...
2587 */
2588 void TY_(FixAnchors)(TidyDocImpl* doc, Node *node, Bool wantName, Bool wantId)
2589 {
2590     Node* next;
2591
2592     while (node)
2593     {
2594         next = node->next;
2595
2596         if (TY_(IsAnchorElement)(doc, node))
2597         {
2598             AttVal *name = TY_(AttrGetById)(node, TidyAttr_NAME);
2599             AttVal *id = TY_(AttrGetById)(node, TidyAttr_ID);
2600             Bool hadName = name!=NULL;
2601             Bool hadId = id!=NULL;
2602             Bool IdEmitted = no;
2603             Bool NameEmitted = no;
2604
2605             /* todo: how are empty name/id attributes handled? */
2606
2607             if (name && id)
2608             {
2609                 Bool NameHasValue = AttrHasValue(name);
2610                 Bool IdHasValue = AttrHasValue(id);
2611                 if ( (NameHasValue != IdHasValue) ||
2612                      (NameHasValue && IdHasValue &&
2613                      TY_(tmbstrcmp)(name->value, id->value) != 0 ) )
2614                     TY_(ReportAttrError)( doc, node, name, ID_NAME_MISMATCH);
2615             }
2616             else if (name && wantId)
2617             {
2618                 if (TY_(NodeAttributeVersions)( node, TidyAttr_ID )
2619                     & doc->lexer->versionEmitted)
2620                 {
2621                     if (TY_(IsValidHTMLID)(name->value))
2622                     {
2623                         TY_(RepairAttrValue)(doc, node, "id", name->value);
2624                         IdEmitted = yes;
2625                     }
2626                     else
2627                         TY_(ReportAttrError)(doc, node, name, INVALID_XML_ID);
2628                  }
2629             }
2630             else if (id && wantName)
2631             {
2632                 if (TY_(NodeAttributeVersions)( node, TidyAttr_NAME )
2633                     & doc->lexer->versionEmitted)
2634                 {
2635                     /* todo: do not assume id is valid */
2636                     TY_(RepairAttrValue)(doc, node, "name", id->value);
2637                     NameEmitted = yes;
2638                 }
2639             }
2640
2641             if (id && !wantId
2642                 /* make sure that Name has been emitted if requested */
2643                 && (hadName || !wantName || NameEmitted) ) {
2644                 if (!wantId && !wantName)
2645                     TY_(RemoveAnchorByNode)(doc, id->value, node);
2646                 TY_(RemoveAttribute)(doc, node, id);
2647             }
2648
2649             if (name && !wantName
2650                 /* make sure that Id has been emitted if requested */
2651                 && (hadId || !wantId || IdEmitted) ) {
2652                 if (!wantId && !wantName)
2653                     TY_(RemoveAnchorByNode)(doc, name->value, node);
2654                 TY_(RemoveAttribute)(doc, node, name);
2655             }
2656         }
2657
2658         if (node->content)
2659             TY_(FixAnchors)(doc, node->content, wantName, wantId);
2660
2661         node = next;
2662     }
2663 }
2664
2665 /*
2666  * local variables:
2667  * mode: c
2668  * indent-tabs-mode: nil
2669  * c-basic-offset: 4
2670  * eval: (c-set-offset 'substatement-open 0)
2671  * end:
2672  */