1/*
2  config.c -- read config file and manage config properties
3
4  (c) 1998-2006 (W3C) MIT, ERCIM, Keio University
5  See tidy.h for the copyright notice.
6
7  CVS Info :
8
9    $Author: iccir $
10    $Date: 2007/02/20 23:59:55 $
11    $Revision: 1.8 $
12
13*/
14
15/*
16  config files associate a property name with a value.
17
18  // comments can start at the beginning of a line
19  # comments can start at the beginning of a line
20  name: short values fit onto one line
21  name: a really long value that
22   continues on the next line
23
24  property names are case insensitive and should be less than
25  60 characters in length and must start at the begining of
26  the line, as whitespace at the start of a line signifies a
27  line continuation.
28*/
29
30#include "config.h"
31#include "tidy-int.h"
32#include "message.h"
33#include "tmbstr.h"
34#include "tags.h"
35
36#ifdef WINDOWS_OS
37#include <io.h>
38#else
39#ifdef DMALLOC
40/*
41   macro for valloc() in dmalloc.h may conflict with declaration for valloc() in unistd.h -
42   we don't need (debugging for) valloc() here. dmalloc.h should come last but it doesn't.
43*/
44#ifdef valloc
45#undef valloc
46#endif
47#endif
48#include <unistd.h>
49#endif
50
51#ifdef TIDY_WIN32_MLANG_SUPPORT
52#include "win32tc.h"
53#endif
54
55void TY_(InitConfig)( TidyDocImpl* doc )
56{
57    ClearMemory( &doc->config, sizeof(TidyConfigImpl) );
58    TY_(ResetConfigToDefault)( doc );
59}
60
61void TY_(FreeConfig)( TidyDocImpl* doc )
62{
63    TY_(ResetConfigToDefault)( doc );
64    TY_(TakeConfigSnapshot)( doc );
65}
66
67
68/* Arrange so index can be cast to enum
69*/
70static const ctmbstr boolPicks[] =
71{
72  "no",
73  "yes",
74  NULL
75};
76
77static const ctmbstr autoBoolPicks[] =
78{
79  "no",
80  "yes",
81  "auto",
82  NULL
83};
84
85static const ctmbstr repeatAttrPicks[] =
86{
87  "keep-first",
88  "keep-last",
89  NULL
90};
91
92static const ctmbstr accessPicks[] =
93{
94  "0 (Tidy Classic)",
95  "1 (Priority 1 Checks)",
96  "2 (Priority 2 Checks)",
97  "3 (Priority 3 Checks)",
98  NULL
99};
100
101static const ctmbstr charEncPicks[] =
102{
103  "raw",
104  "ascii",
105  "latin0",
106  "latin1",
107  "utf8",
108#ifndef NO_NATIVE_ISO2022_SUPPORT
109  "iso2022",
110#endif
111  "mac",
112  "win1252",
113  "ibm858",
114
115#if SUPPORT_UTF16_ENCODINGS
116  "utf16le",
117  "utf16be",
118  "utf16",
119#endif
120
121#if SUPPORT_ASIAN_ENCODINGS
122  "big5",
123  "shiftjis",
124#endif
125
126  NULL
127};
128
129static const ctmbstr newlinePicks[] =
130{
131  "LF",
132  "CRLF",
133  "CR",
134  NULL
135};
136
137static const ctmbstr doctypePicks[] =
138{
139  "omit",
140  "auto",
141  "strict",
142  "transitional",
143  "user",
144  NULL
145};
146
147#define MU TidyMarkup
148#define DG TidyDiagnostics
149#define PP TidyPrettyPrint
150#define CE TidyEncoding
151#define MS TidyMiscellaneous
152
153#define IN TidyInteger
154#define BL TidyBoolean
155#define ST TidyString
156
157#define XX (TidyConfigCategory)-1
158#define XY (TidyOptionType)-1
159
160#define DLF DEFAULT_NL_CONFIG
161
162/* If Accessibility checks not supported, make config setting read-only */
163#if SUPPORT_ACCESSIBILITY_CHECKS
164#define ParseAcc ParseInt
165#else
166#define ParseAcc NULL
167#endif
168
169static void AdjustConfig( TidyDocImpl* doc );
170
171/* parser for integer values */
172static ParseProperty ParseInt;
173
174/* parser for 't'/'f', 'true'/'false', 'y'/'n', 'yes'/'no' or '1'/'0' */
175static ParseProperty ParseBool;
176
177/* parser for 't'/'f', 'true'/'false', 'y'/'n', 'yes'/'no', '1'/'0'
178   or 'auto' */
179static ParseProperty ParseAutoBool;
180
181/* a string excluding whitespace */
182static ParseProperty ParseName;
183
184/* a CSS1 selector - CSS class naming for -clean option */
185static ParseProperty ParseCSS1Selector;
186
187/* a string including whitespace */
188static ParseProperty ParseString;
189
190/* a space or comma separated list of tag names */
191static ParseProperty ParseTagNames;
192
193/* RAW, ASCII, LATIN0, LATIN1, UTF8, ISO2022, MACROMAN,
194   WIN1252, IBM858, UTF16LE, UTF16BE, UTF16, BIG5, SHIFTJIS
195*/
196static ParseProperty ParseCharEnc;
197static ParseProperty ParseNewline;
198
199/* omit | auto | strict | loose | <fpi> */
200static ParseProperty ParseDocType;
201
202/* keep-first or keep-last? */
203static ParseProperty ParseRepeatAttr;
204
205
206static const TidyOptionImpl option_defs[] =
207{
208  { TidyUnknownOption,           MS, "unknown!",                    IN, 0,               NULL,              NULL            },
209  { TidyIndentSpaces,            PP, "indent-spaces",               IN, 2,               ParseInt,          NULL            },
210  { TidyWrapLen,                 PP, "wrap",                        IN, 68,              ParseInt,          NULL            },
211  { TidyTabSize,                 PP, "tab-size",                    IN, 8,               ParseInt,          NULL            },
212  { TidyCharEncoding,            CE, "char-encoding",               IN, ASCII,           ParseCharEnc,      charEncPicks    },
213  { TidyInCharEncoding,          CE, "input-encoding",              IN, LATIN1,          ParseCharEnc,      charEncPicks    },
214  { TidyOutCharEncoding,         CE, "output-encoding",             IN, ASCII,           ParseCharEnc,      charEncPicks    },
215  { TidyNewline,                 CE, "newline",                     IN, DLF,             ParseNewline,      newlinePicks    },
216  { TidyDoctypeMode,             MU, "doctype-mode",                IN, TidyDoctypeAuto, NULL,              doctypePicks    },
217  { TidyDoctype,                 MU, "doctype",                     ST, 0,               ParseDocType,      doctypePicks    },
218  { TidyDuplicateAttrs,          MU, "repeated-attributes",         IN, TidyKeepLast,    ParseRepeatAttr,   repeatAttrPicks },
219  { TidyAltText,                 MU, "alt-text",                    ST, 0,               ParseString,       NULL            },
220
221  /* obsolete */
222  { TidySlideStyle,              MS, "slide-style",                 ST, 0,               ParseName,         NULL            },
223
224  { TidyErrFile,                 MS, "error-file",                  ST, 0,               ParseString,       NULL            },
225  { TidyOutFile,                 MS, "output-file",                 ST, 0,               ParseString,       NULL            },
226  { TidyWriteBack,               MS, "write-back",                  BL, no,              ParseBool,         boolPicks       },
227  { TidyShowMarkup,              PP, "markup",                      BL, yes,             ParseBool,         boolPicks       },
228  { TidyShowWarnings,            DG, "show-warnings",               BL, yes,             ParseBool,         boolPicks       },
229  { TidyQuiet,                   MS, "quiet",                       BL, no,              ParseBool,         boolPicks       },
230  { TidyIndentContent,           PP, "indent",                      IN, TidyNoState,     ParseAutoBool,     autoBoolPicks   },
231  { TidyHideEndTags,             MU, "hide-endtags",                BL, no,              ParseBool,         boolPicks       },
232  { TidyXmlTags,                 MU, "input-xml",                   BL, no,              ParseBool,         boolPicks       },
233  { TidyXmlOut,                  MU, "output-xml",                  BL, no,              ParseBool,         boolPicks       },
234  { TidyXhtmlOut,                MU, "output-xhtml",                BL, no,              ParseBool,         boolPicks       },
235  { TidyHtmlOut,                 MU, "output-html",                 BL, no,              ParseBool,         boolPicks       },
236  { TidyXmlDecl,                 MU, "add-xml-decl",                BL, no,              ParseBool,         boolPicks       },
237  { TidyUpperCaseTags,           MU, "uppercase-tags",              BL, no,              ParseBool,         boolPicks       },
238  { TidyUpperCaseAttrs,          MU, "uppercase-attributes",        BL, no,              ParseBool,         boolPicks       },
239  { TidyMakeBare,                MU, "bare",                        BL, no,              ParseBool,         boolPicks       },
240  { TidyMakeClean,               MU, "clean",                       BL, no,              ParseBool,         boolPicks       },
241  { TidyLogicalEmphasis,         MU, "logical-emphasis",            BL, no,              ParseBool,         boolPicks       },
242  { TidyDropPropAttrs,           MU, "drop-proprietary-attributes", BL, no,              ParseBool,         boolPicks       },
243  { TidyDropFontTags,            MU, "drop-font-tags",              BL, no,              ParseBool,         boolPicks       },
244  { TidyDropEmptyParas,          MU, "drop-empty-paras",            BL, yes,             ParseBool,         boolPicks       },
245  { TidyFixComments,             MU, "fix-bad-comments",            BL, yes,             ParseBool,         boolPicks       },
246  { TidyBreakBeforeBR,           PP, "break-before-br",             BL, no,              ParseBool,         boolPicks       },
247
248  /* obsolete */
249  { TidyBurstSlides,             PP, "split",                       BL, no,              ParseBool,         boolPicks       },
250
251  { TidyNumEntities,             MU, "numeric-entities",            BL, no,              ParseBool,         boolPicks       },
252  { TidyQuoteMarks,              MU, "quote-marks",                 BL, no,              ParseBool,         boolPicks       },
253  { TidyQuoteNbsp,               MU, "quote-nbsp",                  BL, yes,             ParseBool,         boolPicks       },
254  { TidyQuoteAmpersand,          MU, "quote-ampersand",             BL, yes,             ParseBool,         boolPicks       },
255  { TidyWrapAttVals,             PP, "wrap-attributes",             BL, no,              ParseBool,         boolPicks       },
256  { TidyWrapScriptlets,          PP, "wrap-script-literals",        BL, no,              ParseBool,         boolPicks       },
257  { TidyWrapSection,             PP, "wrap-sections",               BL, yes,             ParseBool,         boolPicks       },
258  { TidyWrapAsp,                 PP, "wrap-asp",                    BL, yes,             ParseBool,         boolPicks       },
259  { TidyWrapJste,                PP, "wrap-jste",                   BL, yes,             ParseBool,         boolPicks       },
260  { TidyWrapPhp,                 PP, "wrap-php",                    BL, yes,             ParseBool,         boolPicks       },
261  { TidyFixBackslash,            MU, "fix-backslash",               BL, yes,             ParseBool,         boolPicks       },
262  { TidyIndentAttributes,        PP, "indent-attributes",           BL, no,              ParseBool,         boolPicks       },
263  { TidyXmlPIs,                  MU, "assume-xml-procins",          BL, no,              ParseBool,         boolPicks       },
264  { TidyXmlSpace,                MU, "add-xml-space",               BL, no,              ParseBool,         boolPicks       },
265  { TidyEncloseBodyText,         MU, "enclose-text",                BL, no,              ParseBool,         boolPicks       },
266  { TidyEncloseBlockText,        MU, "enclose-block-text",          BL, no,              ParseBool,         boolPicks       },
267  { TidyKeepFileTimes,           MS, "keep-time",                   BL, no,              ParseBool,         boolPicks       },
268  { TidyWord2000,                MU, "word-2000",                   BL, no,              ParseBool,         boolPicks       },
269  { TidyMark,                    MS, "tidy-mark",                   BL, yes,             ParseBool,         boolPicks       },
270  { TidyEmacs,                   MS, "gnu-emacs",                   BL, no,              ParseBool,         boolPicks       },
271  { TidyEmacsFile,               MS, "gnu-emacs-file",              ST, 0,               ParseString,       NULL            },
272  { TidyLiteralAttribs,          MU, "literal-attributes",          BL, no,              ParseBool,         boolPicks       },
273  { TidyBodyOnly,                MU, "show-body-only",              BL, no,              ParseBool,         boolPicks       },
274  { TidyFixUri,                  MU, "fix-uri",                     BL, yes,             ParseBool,         boolPicks       },
275  { TidyLowerLiterals,           MU, "lower-literals",              BL, yes,             ParseBool,         boolPicks       },
276  { TidyHideComments,            MU, "hide-comments",               BL, no,              ParseBool,         boolPicks       },
277  { TidyIndentCdata,             MU, "indent-cdata",                BL, no,              ParseBool,         boolPicks       },
278  { TidyForceOutput,             MS, "force-output",                BL, no,              ParseBool,         boolPicks       },
279  { TidyShowErrors,              DG, "show-errors",                 IN, 6,               ParseInt,          NULL            },
280  { TidyAsciiChars,              CE, "ascii-chars",                 BL, no,              ParseBool,         boolPicks       },
281  { TidyJoinClasses,             MU, "join-classes",                BL, no,              ParseBool,         boolPicks       },
282  { TidyJoinStyles,              MU, "join-styles",                 BL, yes,             ParseBool,         boolPicks       },
283  { TidyEscapeCdata,             MU, "escape-cdata",                BL, no,              ParseBool,         boolPicks       },
284#if SUPPORT_ASIAN_ENCODINGS
285  { TidyLanguage,                CE, "language",                    ST, 0,               ParseName,         NULL            },
286  { TidyNCR,                     MU, "ncr",                         BL, yes,             ParseBool,         boolPicks       },
287#endif
288#if SUPPORT_UTF16_ENCODINGS
289  { TidyOutputBOM,               CE, "output-bom",                  IN, TidyAutoState,   ParseAutoBool,     autoBoolPicks   },
290#endif
291  { TidyReplaceColor,            MU, "replace-color",               BL, no,              ParseBool,         boolPicks       },
292  { TidyCSSPrefix,               MU, "css-prefix",                  ST, 0,               ParseCSS1Selector, NULL            },
293  { TidyInlineTags,              MU, "new-inline-tags",             ST, 0,               ParseTagNames,     NULL            },
294  { TidyBlockTags,               MU, "new-blocklevel-tags",         ST, 0,               ParseTagNames,     NULL            },
295  { TidyEmptyTags,               MU, "new-empty-tags",              ST, 0,               ParseTagNames,     NULL            },
296  { TidyPreTags,                 MU, "new-pre-tags",                ST, 0,               ParseTagNames,     NULL            },
297  { TidyAccessibilityCheckLevel, DG, "accessibility-check",         IN, 0,               ParseAcc,          accessPicks     },
298  { TidyVertSpace,               PP, "vertical-space",              BL, no,              ParseBool,         boolPicks       },
299#if SUPPORT_ASIAN_ENCODINGS
300  { TidyPunctWrap,               PP, "punctuation-wrap",            BL, no,              ParseBool,         boolPicks       },
301#endif
302  { TidyMergeDivs,               MU, "merge-divs",                  IN, TidyAutoState,   ParseAutoBool,     autoBoolPicks   },
303  { TidyDecorateInferredUL,      MU, "decorate-inferred-ul",        BL, no,              ParseBool,         boolPicks       },
304#if TIDY_APPLE_CHANGES
305  { TidyRelativePathBaseUri,     MU, "_relative-path-base-uri",     ST, 0,               ParseString,       NULL            },
306  { TidyAbsolutePathBaseUri,     MU, "_absolute-path-base-uri",     ST, 0,               ParseString,       NULL            },
307  { TidyDropClassesWithPrefix,   MU, "_drop-classes-with-prefix",   ST, 0,               ParseString,       NULL            },
308  { TidyDropIdsWithPrefix,       MU, "_drop-ids-with-prefix",       ST, 0,               ParseString,       NULL            },
309  { TidySanitizeAgainstXSS,      MU, "_sanitize-against-xss",       BL, no,              ParseBool,         boolPicks       },
310#endif
311  { N_TIDY_OPTIONS,              XX, NULL,                          XY, 0,               NULL,              NULL            }
312};
313
314/* Should only be called by options set by name
315** thus, it is cheaper to do a few scans than set
316** up every option in a hash table.
317*/
318const TidyOptionImpl* TY_(lookupOption)( ctmbstr s )
319{
320    const TidyOptionImpl* np = option_defs;
321    for ( /**/; np < option_defs + N_TIDY_OPTIONS; ++np )
322    {
323        if ( TY_(tmbstrcasecmp)(s, np->name) == 0 )
324            return np;
325    }
326    return NULL;
327}
328
329const TidyOptionImpl* TY_(getOption)( TidyOptionId optId )
330{
331  if ( optId < N_TIDY_OPTIONS )
332      return option_defs + optId;
333  return NULL;
334}
335
336
337static void FreeOptionValue( const TidyOptionImpl* option, TidyOptionValue* value )
338{
339    if ( option->type == TidyString && value->p && value->p != option->pdflt )
340    {
341        MemFree( value->p );
342    }
343}
344
345static void CopyOptionValue( const TidyOptionImpl* option,
346                             TidyOptionValue* oldval, const TidyOptionValue* newval )
347{
348    assert( oldval != NULL );
349    FreeOptionValue( option, oldval );
350
351    if ( option->type == TidyString )
352    {
353        if ( newval->p && newval->p != option->pdflt )
354            oldval->p = TY_(tmbstrdup)( newval->p );
355        else
356            oldval->p = newval->p;
357    }
358    else
359        oldval->v = newval->v;
360}
361
362
363static Bool SetOptionValue( TidyDocImpl* doc, TidyOptionId optId, ctmbstr val )
364{
365   const TidyOptionImpl* option = &option_defs[ optId ];
366   Bool status = ( optId < N_TIDY_OPTIONS );
367   if ( status )
368   {
369      assert( option->id == optId && option->type == TidyString );
370      FreeOptionValue( option, &doc->config.value[ optId ] );
371      doc->config.value[ optId ].p = TY_(tmbstrdup)( val );
372   }
373   return status;
374}
375
376Bool TY_(SetOptionInt)( TidyDocImpl* doc, TidyOptionId optId, ulong val )
377{
378   Bool status = ( optId < N_TIDY_OPTIONS );
379   if ( status )
380   {
381       assert( option_defs[ optId ].type == TidyInteger );
382       doc->config.value[ optId ].v = val;
383   }
384   return status;
385}
386
387Bool TY_(SetOptionBool)( TidyDocImpl* doc, TidyOptionId optId, Bool val )
388{
389   Bool status = ( optId < N_TIDY_OPTIONS );
390   if ( status )
391   {
392       assert( option_defs[ optId ].type == TidyBoolean );
393       doc->config.value[ optId ].v = val;
394   }
395   return status;
396}
397
398static void GetOptionDefault( const TidyOptionImpl* option,
399                              TidyOptionValue* dflt )
400{
401    if ( option->type == TidyString )
402        dflt->p = (char*)option->pdflt;
403    else
404        dflt->v = option->dflt;
405}
406
407static Bool OptionValueEqDefault( const TidyOptionImpl* option,
408                                  const TidyOptionValue* val )
409{
410    return ( option->type == TidyString ) ?
411        val->p == option->pdflt :
412        val->v == option->dflt;
413}
414
415Bool TY_(ResetOptionToDefault)( TidyDocImpl* doc, TidyOptionId optId )
416{
417    Bool status = ( optId > 0 && optId < N_TIDY_OPTIONS );
418    if ( status )
419    {
420        TidyOptionValue dflt;
421        const TidyOptionImpl* option = option_defs + optId;
422        TidyOptionValue* value = &doc->config.value[ optId ];
423        assert( optId == option->id );
424        GetOptionDefault( option, &dflt );
425        CopyOptionValue( option, value, &dflt );
426    }
427    return status;
428}
429
430static void ReparseTagType( TidyDocImpl* doc, TidyOptionId optId )
431{
432    ctmbstr tagdecl = cfgStr( doc, optId );
433    tmbstr dupdecl = TY_(tmbstrdup)( tagdecl );
434    TY_(ParseConfigValue)( doc, optId, dupdecl );
435    MemFree( dupdecl );
436}
437
438/* Not efficient, but effective */
439static void ReparseTagDecls( TidyDocImpl* doc )
440{
441    TY_(FreeDeclaredTags)( doc, tagtype_null );
442    if ( cfg(doc, TidyInlineTags) )
443        ReparseTagType( doc, TidyInlineTags );
444    if ( cfg(doc, TidyBlockTags) )
445        ReparseTagType( doc, TidyBlockTags );
446    if ( cfg(doc, TidyEmptyTags) )
447        ReparseTagType( doc, TidyEmptyTags );
448    if ( cfg(doc, TidyPreTags) )
449        ReparseTagType( doc, TidyPreTags );
450}
451
452void TY_(ResetConfigToDefault)( TidyDocImpl* doc )
453{
454    uint ixVal;
455    const TidyOptionImpl* option = option_defs;
456    TidyOptionValue* value = &doc->config.value[ 0 ];
457    for ( ixVal=0; ixVal < N_TIDY_OPTIONS; ++option, ++ixVal )
458    {
459        TidyOptionValue dflt;
460        assert( ixVal == (uint) option->id );
461        GetOptionDefault( option, &dflt );
462        CopyOptionValue( option, &value[ixVal], &dflt );
463    }
464    TY_(FreeDeclaredTags)( doc, tagtype_null );
465}
466
467void TY_(TakeConfigSnapshot)( TidyDocImpl* doc )
468{
469    uint ixVal;
470    const TidyOptionImpl* option = option_defs;
471    const TidyOptionValue* value = &doc->config.value[ 0 ];
472    TidyOptionValue* snap  = &doc->config.snapshot[ 0 ];
473
474    AdjustConfig( doc );  /* Make sure it's consistent */
475    for ( ixVal=0; ixVal < N_TIDY_OPTIONS; ++option, ++ixVal )
476    {
477        assert( ixVal == (uint) option->id );
478        CopyOptionValue( option, &snap[ixVal], &value[ixVal] );
479    }
480}
481
482void TY_(ResetConfigToSnapshot)( TidyDocImpl* doc )
483{
484    uint ixVal;
485    const TidyOptionImpl* option = option_defs;
486    TidyOptionValue* value = &doc->config.value[ 0 ];
487    const TidyOptionValue* snap  = &doc->config.snapshot[ 0 ];
488
489    for ( ixVal=0; ixVal < N_TIDY_OPTIONS; ++option, ++ixVal )
490    {
491        assert( ixVal == (uint) option->id );
492        CopyOptionValue( option, &value[ixVal], &snap[ixVal] );
493    }
494    TY_(FreeDeclaredTags)( doc, tagtype_null );
495    ReparseTagDecls( doc );
496}
497
498void TY_(CopyConfig)( TidyDocImpl* docTo, TidyDocImpl* docFrom )
499{
500    if ( docTo != docFrom )
501    {
502        uint ixVal;
503        const TidyOptionImpl* option = option_defs;
504        const TidyOptionValue* from = &docFrom->config.value[ 0 ];
505        TidyOptionValue* to   = &docTo->config.value[ 0 ];
506
507        TY_(TakeConfigSnapshot)( docTo );
508        for ( ixVal=0; ixVal < N_TIDY_OPTIONS; ++option, ++ixVal )
509        {
510            assert( ixVal == (uint) option->id );
511            CopyOptionValue( option, &to[ixVal], &from[ixVal] );
512        }
513        ReparseTagDecls( docTo );
514        AdjustConfig( docTo );  /* Make sure it's consistent */
515    }
516}
517
518
519#ifdef _DEBUG
520
521/* Debug accessor functions will be type-safe and assert option type match */
522ulong   TY_(_cfgGet)( TidyDocImpl* doc, TidyOptionId optId )
523{
524  assert( optId < N_TIDY_OPTIONS );
525  return doc->config.value[ optId ].v;
526}
527
528Bool    TY_(_cfgGetBool)( TidyDocImpl* doc, TidyOptionId optId )
529{
530  ulong val = TY_(_cfgGet)( doc, optId );
531  const TidyOptionImpl* opt = &option_defs[ optId ];
532  assert( opt && opt->type == TidyBoolean );
533  return (Bool) val;
534}
535
536TidyTriState    TY_(_cfgGetAutoBool)( TidyDocImpl* doc, TidyOptionId optId )
537{
538  ulong val = TY_(_cfgGet)( doc, optId );
539  const TidyOptionImpl* opt = &option_defs[ optId ];
540  assert( opt && opt->type == TidyInteger
541          && opt->parser == ParseAutoBool );
542  return (TidyTriState) val;
543}
544
545ctmbstr TY_(_cfgGetString)( TidyDocImpl* doc, TidyOptionId optId )
546{
547  const TidyOptionImpl* opt;
548
549  assert( optId < N_TIDY_OPTIONS );
550  opt = &option_defs[ optId ];
551  assert( opt && opt->type == TidyString );
552  return doc->config.value[ optId ].p;
553}
554#endif
555
556
557#if 0
558/* for use with Gnu Emacs */
559void SetEmacsFilename( TidyDocImpl* doc, ctmbstr filename )
560{
561    SetOptionValue( doc, TidyEmacsFile, filename );
562}
563#endif
564
565static tchar GetC( TidyConfigImpl* config )
566{
567    if ( config->cfgIn )
568        return TY_(ReadChar)( config->cfgIn );
569    return EndOfStream;
570}
571
572static tchar FirstChar( TidyConfigImpl* config )
573{
574    config->c = GetC( config );
575    return config->c;
576}
577
578static tchar AdvanceChar( TidyConfigImpl* config )
579{
580    if ( config->c != EndOfStream )
581        config->c = GetC( config );
582    return config->c;
583}
584
585static tchar SkipWhite( TidyConfigImpl* config )
586{
587    while ( TY_(IsWhite)(config->c) && !TY_(IsNewline)(config->c) )
588        config->c = GetC( config );
589    return config->c;
590}
591
592/* skip until end of line
593static tchar SkipToEndofLine( TidyConfigImpl* config )
594{
595    while ( config->c != EndOfStream )
596    {
597        config->c = GetC( config );
598        if ( config->c == '\n' || config->c == '\r' )
599            break;
600    }
601    return config->c;
602}
603*/
604
605/*
606 skip over line continuations
607 to start of next property
608*/
609static uint NextProperty( TidyConfigImpl* config )
610{
611    do
612    {
613        /* skip to end of line */
614        while ( config->c != '\n' &&  config->c != '\r' &&  config->c != EndOfStream )
615             config->c = GetC( config );
616
617        /* treat  \r\n   \r  or  \n as line ends */
618        if ( config->c == '\r' )
619             config->c = GetC( config );
620
621        if ( config->c == '\n' )
622            config->c = GetC( config );
623    }
624    while ( TY_(IsWhite)(config->c) );  /* line continuation? */
625
626    return config->c;
627}
628
629/*
630 Todd Lewis contributed this code for expanding
631 ~/foo or ~your/foo according to $HOME and your
632 user name. This will work partially on any system
633 which defines $HOME.  Support for ~user/foo will
634 work on systems that support getpwnam(userid),
635 namely Unix/Linux.
636*/
637static ctmbstr ExpandTilde( ctmbstr filename )
638{
639    char *home_dir = NULL;
640
641    if ( !filename )
642        return NULL;
643
644    if ( filename[0] != '~' )
645        return filename;
646
647    if (filename[1] == '/')
648    {
649        home_dir = getenv("HOME");
650        if ( home_dir )
651            ++filename;
652    }
653#ifdef SUPPORT_GETPWNAM
654    else
655    {
656        struct passwd *passwd = NULL;
657        ctmbstr s = filename + 1;
658        tmbstr t;
659
660        while ( *s && *s != '/' )
661            s++;
662
663        if ( t = MemAlloc(s - filename) )
664        {
665            memcpy(t, filename+1, s-filename-1);
666            t[s-filename-1] = 0;
667
668            passwd = getpwnam(t);
669
670            MemFree(t);
671        }
672
673        if ( passwd )
674        {
675            filename = s;
676            home_dir = passwd->pw_dir;
677        }
678    }
679#endif /* SUPPORT_GETPWNAM */
680
681    if ( home_dir )
682    {
683        uint len = TY_(tmbstrlen)(filename) + TY_(tmbstrlen)(home_dir) + 1;
684        tmbstr p = (tmbstr)MemAlloc( len );
685        TY_(tmbstrcpy)( p, home_dir );
686        TY_(tmbstrcat)( p, filename );
687        return (ctmbstr) p;
688    }
689    return (ctmbstr) filename;
690}
691
692Bool TIDY_CALL tidyFileExists( ctmbstr filename )
693{
694  ctmbstr fname = (tmbstr) ExpandTilde( filename );
695#ifndef NO_ACCESS_SUPPORT
696  Bool exists = ( access(fname, 0) == 0 );
697#else
698  Bool exists;
699  /* at present */
700  FILE* fin = fopen(fname, "r");
701  if (fin != NULL)
702      fclose(fin);
703  exists = ( fin != NULL );
704#endif
705  if ( fname != filename )
706      MemFree( (tmbstr) fname );
707  return exists;
708}
709
710
711#ifndef TIDY_MAX_NAME
712#define TIDY_MAX_NAME 64
713#endif
714
715int TY_(ParseConfigFile)( TidyDocImpl* doc, ctmbstr file )
716{
717    return TY_(ParseConfigFileEnc)( doc, file, "ascii" );
718}
719
720/* open the file and parse its contents
721*/
722int TY_(ParseConfigFileEnc)( TidyDocImpl* doc, ctmbstr file, ctmbstr charenc )
723{
724    uint opterrs = doc->optionErrors;
725    tmbstr fname = (tmbstr) ExpandTilde( file );
726    TidyConfigImpl* cfg = &doc->config;
727    FILE* fin = fopen( fname, "r" );
728    int enc = TY_(CharEncodingId)( charenc );
729
730    if ( fin == NULL || enc < 0 )
731    {
732        TY_(FileError)( doc, fname, TidyConfig );
733        return -1;
734    }
735    else
736    {
737        tchar c;
738        cfg->cfgIn = TY_(FileInput)( doc, fin, enc );
739        c = FirstChar( cfg );
740
741        for ( c = SkipWhite(cfg); c != EndOfStream; c = NextProperty(cfg) )
742        {
743            uint ix = 0;
744            tmbchar name[ TIDY_MAX_NAME ] = {0};
745
746            /* // or # start a comment */
747            if ( c == '/' || c == '#' )
748                continue;
749
750            while ( ix < sizeof(name)-1 && c != '\n' && c != EndOfStream && c != ':' )
751            {
752                name[ ix++ ] = (tmbchar) c;  /* Option names all ASCII */
753                c = AdvanceChar( cfg );
754            }
755
756            if ( c == ':' )
757            {
758                const TidyOptionImpl* option = TY_(lookupOption)( name );
759                c = AdvanceChar( cfg );
760                if ( option )
761                    option->parser( doc, option );
762                else
763                {
764                    if (NULL != doc->pOptCallback)
765                    {
766                        TidyConfigImpl* cfg = &doc->config;
767                        tmbchar buf[8192];
768                        uint i = 0;
769                        tchar delim = 0;
770                        Bool waswhite = yes;
771
772                        tchar c = SkipWhite( cfg );
773
774                        if ( c == '"' || c == '\'' )
775                        {
776                            delim = c;
777                            c = AdvanceChar( cfg );
778                        }
779
780                        while ( i < sizeof(buf)-2 && c != EndOfStream && c != '\r' && c != '\n' )
781                        {
782                            if ( delim && c == delim )
783                                break;
784
785                            if ( TY_(IsWhite)(c) )
786                            {
787                                if ( waswhite )
788                                {
789                                    c = AdvanceChar( cfg );
790                                    continue;
791                                }
792                                c = ' ';
793                            }
794                            else
795                                waswhite = no;
796
797                            buf[i++] = (tmbchar) c;
798                            c = AdvanceChar( cfg );
799                        }
800                        buf[i] = '\0';
801                        if (no == (*doc->pOptCallback)( name, buf ))
802                            TY_(ReportUnknownOption)( doc, name );
803                    }
804                    else
805                        TY_(ReportUnknownOption)( doc, name );
806                }
807            }
808        }
809
810        TY_(freeFileSource)(&cfg->cfgIn->source, yes);
811        TY_(freeStreamIn)( cfg->cfgIn );
812        cfg->cfgIn = NULL;
813    }
814
815    if ( fname != (tmbstr) file )
816        MemFree( fname );
817
818    AdjustConfig( doc );
819
820    /* any new config errors? If so, return warning status. */
821    return (doc->optionErrors > opterrs ? 1 : 0);
822}
823
824/* returns false if unknown option, missing parameter,
825** or option doesn't use parameter
826*/
827Bool TY_(ParseConfigOption)( TidyDocImpl* doc, ctmbstr optnam, ctmbstr optval )
828{
829    const TidyOptionImpl* option = TY_(lookupOption)( optnam );
830    Bool status = ( option != NULL );
831    if ( !status )
832    {
833        /* Not a standard tidy option.  Check to see if the user application
834           recognizes it  */
835        if (NULL != doc->pOptCallback)
836            status = (*doc->pOptCallback)( optnam, optval );
837        if (!status)
838            TY_(ReportUnknownOption)( doc, optnam );
839    }
840    else
841        status = TY_(ParseConfigValue)( doc, option->id, optval );
842    return status;
843}
844
845/* returns false if unknown option, missing parameter,
846** or option doesn't use parameter
847*/
848Bool TY_(ParseConfigValue)( TidyDocImpl* doc, TidyOptionId optId, ctmbstr optval )
849{
850    const TidyOptionImpl* option = option_defs + optId;
851    Bool status = ( optId < N_TIDY_OPTIONS && optval != NULL );
852
853    if ( !status )
854        TY_(ReportBadArgument)( doc, option->name );
855    else
856    {
857        TidyBuffer inbuf = {0};            /* Set up input source */
858        tidyBufAttach( &inbuf, (byte*)optval, TY_(tmbstrlen)(optval)+1 );
859        doc->config.cfgIn = TY_(BufferInput)( doc, &inbuf, ASCII );
860        doc->config.c = GetC( &doc->config );
861
862        status = option->parser( doc, option );
863
864        TY_(freeStreamIn)(doc->config.cfgIn);  /* Release input source */
865        doc->config.cfgIn  = NULL;
866        tidyBufDetach( &inbuf );
867    }
868    return status;
869}
870
871
872/* ensure that char encodings are self consistent */
873Bool  TY_(AdjustCharEncoding)( TidyDocImpl* doc, int encoding )
874{
875    int outenc = -1;
876    int inenc = -1;
877
878    switch( encoding )
879    {
880    case MACROMAN:
881        inenc = MACROMAN;
882        outenc = ASCII;
883        break;
884
885    case WIN1252:
886        inenc = WIN1252;
887        outenc = ASCII;
888        break;
889
890    case IBM858:
891        inenc = IBM858;
892        outenc = ASCII;
893        break;
894
895    case ASCII:
896        inenc = LATIN1;
897        outenc = ASCII;
898        break;
899
900    case LATIN0:
901        inenc = LATIN0;
902        outenc = ASCII;
903        break;
904
905    case RAW:
906    case LATIN1:
907    case UTF8:
908#ifndef NO_NATIVE_ISO2022_SUPPORT
909    case ISO2022:
910#endif
911
912#if SUPPORT_UTF16_ENCODINGS
913    case UTF16LE:
914    case UTF16BE:
915    case UTF16:
916#endif
917#if SUPPORT_ASIAN_ENCODINGS
918    case SHIFTJIS:
919    case BIG5:
920#endif
921        inenc = outenc = encoding;
922        break;
923    }
924
925    if ( inenc >= 0 )
926    {
927        TY_(SetOptionInt)( doc, TidyCharEncoding, encoding );
928        TY_(SetOptionInt)( doc, TidyInCharEncoding, inenc );
929        TY_(SetOptionInt)( doc, TidyOutCharEncoding, outenc );
930        return yes;
931    }
932    return no;
933}
934
935/* ensure that config is self consistent */
936void AdjustConfig( TidyDocImpl* doc )
937{
938    if ( cfgBool(doc, TidyEncloseBlockText) )
939        TY_(SetOptionBool)( doc, TidyEncloseBodyText, yes );
940
941    if ( cfgAutoBool(doc, TidyIndentContent) == TidyNoState )
942        TY_(SetOptionInt)( doc, TidyIndentSpaces, 0 );
943
944    /* disable wrapping */
945    if ( cfg(doc, TidyWrapLen) == 0 )
946        TY_(SetOptionInt)( doc, TidyWrapLen, 0x7FFFFFFF );
947
948    /* Word 2000 needs o:p to be declared as inline */
949    if ( cfgBool(doc, TidyWord2000) )
950    {
951        doc->config.defined_tags |= tagtype_inline;
952        TY_(DefineTag)( doc, tagtype_inline, "o:p" );
953    }
954
955    /* #480701 disable XHTML output flag if both output-xhtml and xml input are set */
956    if ( cfgBool(doc, TidyXmlTags) )
957        TY_(SetOptionBool)( doc, TidyXhtmlOut, no );
958
959    /* XHTML is written in lower case */
960    if ( cfgBool(doc, TidyXhtmlOut) )
961    {
962        TY_(SetOptionBool)( doc, TidyXmlOut, yes );
963        TY_(SetOptionBool)( doc, TidyUpperCaseTags, no );
964        TY_(SetOptionBool)( doc, TidyUpperCaseAttrs, no );
965        /* TY_(SetOptionBool)( doc, TidyXmlPIs, yes ); */
966    }
967
968    /* if XML in, then XML out */
969    if ( cfgBool(doc, TidyXmlTags) )
970    {
971        TY_(SetOptionBool)( doc, TidyXmlOut, yes );
972        TY_(SetOptionBool)( doc, TidyXmlPIs, yes );
973    }
974
975    /* #427837 - fix by Dave Raggett 02 Jun 01
976    ** generate <?xml version="1.0" encoding="iso-8859-1"?>
977    ** if the output character encoding is Latin-1 etc.
978    */
979    if ( cfg(doc, TidyOutCharEncoding) != ASCII &&
980         cfg(doc, TidyOutCharEncoding) != UTF8 &&
981#if SUPPORT_UTF16_ENCODINGS
982         cfg(doc, TidyOutCharEncoding) != UTF16 &&
983         cfg(doc, TidyOutCharEncoding) != UTF16BE &&
984         cfg(doc, TidyOutCharEncoding) != UTF16LE &&
985#endif
986         cfg(doc, TidyOutCharEncoding) != RAW &&
987         cfgBool(doc, TidyXmlOut) )
988    {
989        TY_(SetOptionBool)( doc, TidyXmlDecl, yes );
990    }
991
992    /* XML requires end tags */
993    if ( cfgBool(doc, TidyXmlOut) )
994    {
995#if SUPPORT_UTF16_ENCODINGS
996        /* XML requires a BOM on output if using UTF-16 encoding */
997        ulong enc = cfg( doc, TidyOutCharEncoding );
998        if ( enc == UTF16LE || enc == UTF16BE || enc == UTF16 )
999            TY_(SetOptionInt)( doc, TidyOutputBOM, yes );
1000#endif
1001        TY_(SetOptionBool)( doc, TidyQuoteAmpersand, yes );
1002        TY_(SetOptionBool)( doc, TidyHideEndTags, no );
1003    }
1004}
1005
1006/* unsigned integers */
1007Bool ParseInt( TidyDocImpl* doc, const TidyOptionImpl* entry )
1008{
1009    ulong number = 0;
1010    Bool digits = no;
1011    TidyConfigImpl* cfg = &doc->config;
1012    tchar c = SkipWhite( cfg );
1013
1014    while ( TY_(IsDigit)(c) )
1015    {
1016        number = c - '0' + (10 * number);
1017        digits = yes;
1018        c = AdvanceChar( cfg );
1019    }
1020
1021    if ( !digits )
1022        TY_(ReportBadArgument)( doc, entry->name );
1023    else
1024        TY_(SetOptionInt)( doc, entry->id, number );
1025    return digits;
1026}
1027
1028/* true/false or yes/no or 0/1 or "auto" only looks at 1st char */
1029static Bool ParseTriState( TidyTriState theState, TidyDocImpl* doc,
1030                           const TidyOptionImpl* entry, ulong* flag )
1031{
1032    TidyConfigImpl* cfg = &doc->config;
1033    tchar c = SkipWhite( cfg );
1034
1035    if (c == 't' || c == 'T' || c == 'y' || c == 'Y' || c == '1')
1036        *flag = yes;
1037    else if (c == 'f' || c == 'F' || c == 'n' || c == 'N' || c == '0')
1038        *flag = no;
1039    else if (theState == TidyAutoState && (c == 'a' || c =='A'))
1040        *flag = TidyAutoState;
1041    else
1042    {
1043        TY_(ReportBadArgument)( doc, entry->name );
1044        return no;
1045    }
1046
1047    return yes;
1048}
1049
1050/* cr, lf or crlf */
1051Bool ParseNewline( TidyDocImpl* doc, const TidyOptionImpl* entry )
1052{
1053    int nl = -1;
1054    tmbchar work[ 16 ] = {0};
1055    tmbstr cp = work, end = work + sizeof(work);
1056    TidyConfigImpl* cfg = &doc->config;
1057    tchar c = SkipWhite( cfg );
1058
1059    while ( c!=EndOfStream && cp < end && !TY_(IsWhite)(c) && c != '\r' && c != '\n' )
1060    {
1061        *cp++ = (tmbchar) c;
1062        c = AdvanceChar( cfg );
1063    }
1064    *cp = 0;
1065
1066    if ( TY_(tmbstrcasecmp)(work, "lf") == 0 )
1067        nl = TidyLF;
1068    else if ( TY_(tmbstrcasecmp)(work, "crlf") == 0 )
1069        nl = TidyCRLF;
1070    else if ( TY_(tmbstrcasecmp)(work, "cr") == 0 )
1071        nl = TidyCR;
1072
1073    if ( nl < TidyLF || nl > TidyCR )
1074        TY_(ReportBadArgument)( doc, entry->name );
1075    else
1076        TY_(SetOptionInt)( doc, entry->id, nl );
1077    return ( nl >= TidyLF && nl <= TidyCR );
1078}
1079
1080Bool ParseBool( TidyDocImpl* doc, const TidyOptionImpl* entry )
1081{
1082    ulong flag = 0;
1083    Bool status = ParseTriState( TidyNoState, doc, entry, &flag );
1084    if ( status )
1085        TY_(SetOptionBool)( doc, entry->id, flag != 0 );
1086    return status;
1087}
1088
1089Bool ParseAutoBool( TidyDocImpl* doc, const TidyOptionImpl* entry )
1090{
1091    ulong flag = 0;
1092    Bool status = ParseTriState( TidyAutoState, doc, entry, &flag );
1093    if ( status )
1094        TY_(SetOptionInt)( doc, entry->id, flag );
1095    return status;
1096}
1097
1098/* a string excluding whitespace */
1099Bool ParseName( TidyDocImpl* doc, const TidyOptionImpl* option )
1100{
1101    tmbchar buf[ 1024 ] = {0};
1102    uint i = 0;
1103    uint c = SkipWhite( &doc->config );
1104
1105    while ( i < sizeof(buf)-2 && c != EndOfStream && !TY_(IsWhite)(c) )
1106    {
1107        buf[i++] = (tmbchar) c;
1108        c = AdvanceChar( &doc->config );
1109    }
1110    buf[i] = 0;
1111
1112    if ( i == 0 )
1113        TY_(ReportBadArgument)( doc, option->name );
1114    else
1115        SetOptionValue( doc, option->id, buf );
1116    return ( i > 0 );
1117}
1118
1119/* #508936 - CSS class naming for -clean option */
1120Bool ParseCSS1Selector( TidyDocImpl* doc, const TidyOptionImpl* option )
1121{
1122    char buf[256] = {0};
1123    uint i = 0;
1124    uint c = SkipWhite( &doc->config );
1125
1126    while ( i < sizeof(buf)-2 && c != EndOfStream && !TY_(IsWhite)(c) )
1127    {
1128        buf[i++] = (tmbchar) c;
1129        c = AdvanceChar( &doc->config );
1130    }
1131    buf[i] = '\0';
1132
1133    if ( i == 0 || !TY_(IsCSS1Selector)(buf) ) {
1134        TY_(ReportBadArgument)( doc, option->name );
1135        return no;
1136    }
1137
1138    buf[i++] = '-';  /* Make sure any escaped Unicode is terminated */
1139    buf[i] = 0;      /* so valid class names are generated after */
1140                     /* Tidy appends last digits. */
1141
1142    SetOptionValue( doc, option->id, buf );
1143    return yes;
1144}
1145
1146/* Coordinates Config update and Tags data */
1147static void DeclareUserTag( TidyDocImpl* doc, TidyOptionId optId,
1148                            UserTagType tagType, ctmbstr name )
1149{
1150  ctmbstr prvval = cfgStr( doc, optId );
1151  tmbstr catval = NULL;
1152  ctmbstr theval = name;
1153  if ( prvval )
1154  {
1155    uint len = TY_(tmbstrlen)(name) + TY_(tmbstrlen)(prvval) + 3;
1156    catval = TY_(tmbstrndup)( prvval, len );
1157    TY_(tmbstrcat)( catval, ", " );
1158    TY_(tmbstrcat)( catval, name );
1159    theval = catval;
1160  }
1161  TY_(DefineTag)( doc, tagType, name );
1162  SetOptionValue( doc, optId, theval );
1163  if ( catval )
1164    MemFree( catval );
1165}
1166
1167/* a space or comma separated list of tag names */
1168Bool ParseTagNames( TidyDocImpl* doc, const TidyOptionImpl* option )
1169{
1170    TidyConfigImpl* cfg = &doc->config;
1171    tmbchar buf[1024];
1172    uint i = 0, nTags = 0;
1173    uint c = SkipWhite( cfg );
1174    UserTagType ttyp = tagtype_null;
1175
1176    switch ( option->id )
1177    {
1178    case TidyInlineTags:  ttyp = tagtype_inline;    break;
1179    case TidyBlockTags:   ttyp = tagtype_block;     break;
1180    case TidyEmptyTags:   ttyp = tagtype_empty;     break;
1181    case TidyPreTags:     ttyp = tagtype_pre;       break;
1182    default:
1183       TY_(ReportUnknownOption)( doc, option->name );
1184       return no;
1185    }
1186
1187    SetOptionValue( doc, option->id, NULL );
1188    TY_(FreeDeclaredTags)( doc, ttyp );
1189    cfg->defined_tags |= ttyp;
1190
1191    do
1192    {
1193        if (c == ' ' || c == '\t' || c == ',')
1194        {
1195            c = AdvanceChar( cfg );
1196            continue;
1197        }
1198
1199        if ( c == '\r' || c == '\n' )
1200        {
1201            uint c2 = AdvanceChar( cfg );
1202            if ( c == '\r' && c2 == '\n' )
1203                c = AdvanceChar( cfg );
1204            else
1205                c = c2;
1206
1207            if ( !TY_(IsWhite)(c) )
1208            {
1209                buf[i] = 0;
1210                TY_(UngetChar)( c, cfg->cfgIn );
1211                TY_(UngetChar)( '\n', cfg->cfgIn );
1212                break;
1213            }
1214        }
1215
1216        /*
1217        if ( c == '\n' )
1218        {
1219            c = AdvanceChar( cfg );
1220            if ( !TY_(IsWhite)(c) )
1221            {
1222                buf[i] = 0;
1223                TY_(UngetChar)( c, cfg->cfgIn );
1224                TY_(UngetChar)( '\n', cfg->cfgIn );
1225                break;
1226            }
1227        }
1228        */
1229
1230        while ( i < sizeof(buf)-2 && c != EndOfStream && !TY_(IsWhite)(c) && c != ',' )
1231        {
1232            buf[i++] = (tmbchar) c;
1233            c = AdvanceChar( cfg );
1234        }
1235
1236        buf[i] = '\0';
1237        if (i == 0)          /* Skip empty tag definition.  Possible when */
1238            continue;        /* there is a trailing space on the line. */
1239
1240        /* add tag to dictionary */
1241        DeclareUserTag( doc, option->id, ttyp, buf );
1242        i = 0;
1243        ++nTags;
1244    }
1245    while ( c != EndOfStream );
1246
1247    if ( i > 0 )
1248      DeclareUserTag( doc, option->id, ttyp, buf );
1249    return ( nTags > 0 );
1250}
1251
1252/* a string including whitespace */
1253/* munges whitespace sequences */
1254
1255Bool ParseString( TidyDocImpl* doc, const TidyOptionImpl* option )
1256{
1257    TidyConfigImpl* cfg = &doc->config;
1258    tmbchar buf[8192];
1259    uint i = 0;
1260    tchar delim = 0;
1261    Bool waswhite = yes;
1262
1263    tchar c = SkipWhite( cfg );
1264
1265    if ( c == '"' || c == '\'' )
1266    {
1267        delim = c;
1268        c = AdvanceChar( cfg );
1269    }
1270
1271    while ( i < sizeof(buf)-2 && c != EndOfStream && c != '\r' && c != '\n' )
1272    {
1273        if ( delim && c == delim )
1274            break;
1275
1276        if ( TY_(IsWhite)(c) )
1277        {
1278            if ( waswhite )
1279            {
1280                c = AdvanceChar( cfg );
1281                continue;
1282            }
1283            c = ' ';
1284        }
1285        else
1286            waswhite = no;
1287
1288        buf[i++] = (tmbchar) c;
1289        c = AdvanceChar( cfg );
1290    }
1291    buf[i] = '\0';
1292
1293    SetOptionValue( doc, option->id, buf );
1294    return yes;
1295}
1296
1297Bool ParseCharEnc( TidyDocImpl* doc, const TidyOptionImpl* option )
1298{
1299    tmbchar buf[64] = {0};
1300    uint i = 0;
1301    int enc = ASCII;
1302    Bool validEncoding = yes;
1303    tchar c = SkipWhite( &doc->config );
1304
1305    while ( i < sizeof(buf)-2 && c != EndOfStream && !TY_(IsWhite)(c) )
1306    {
1307        buf[i++] = (tmbchar) TY_(ToLower)( c );
1308        c = AdvanceChar( &doc->config );
1309    }
1310    buf[i] = 0;
1311
1312    enc = TY_(CharEncodingId)( buf );
1313
1314#ifdef TIDY_WIN32_MLANG_SUPPORT
1315    /* limit support to --input-encoding */
1316    if (option->id != TidyInCharEncoding && enc > WIN32MLANG)
1317        enc = -1;
1318#endif
1319
1320    if ( enc < 0 )
1321    {
1322        validEncoding = no;
1323        TY_(ReportBadArgument)( doc, option->name );
1324    }
1325    else
1326        TY_(SetOptionInt)( doc, option->id, enc );
1327
1328    if ( validEncoding && option->id == TidyCharEncoding )
1329        TY_(AdjustCharEncoding)( doc, enc );
1330    return validEncoding;
1331}
1332
1333
1334int TY_(CharEncodingId)( ctmbstr charenc )
1335{
1336    int enc = TY_(GetCharEncodingFromOptName)( charenc );
1337
1338#ifdef TIDY_WIN32_MLANG_SUPPORT
1339    if (enc == -1)
1340    {
1341        uint wincp = TY_(Win32MLangGetCPFromName)(charenc);
1342        if (wincp)
1343            enc = wincp;
1344    }
1345#endif
1346
1347    return enc;
1348}
1349
1350ctmbstr TY_(CharEncodingName)( int encoding )
1351{
1352    ctmbstr encodingName = TY_(GetEncodingNameFromTidyId)(encoding);
1353
1354    if (!encodingName)
1355        encodingName = "unknown";
1356
1357    return encodingName;
1358}
1359
1360ctmbstr TY_(CharEncodingOptName)( int encoding )
1361{
1362    ctmbstr encodingName = TY_(GetEncodingOptNameFromTidyId)(encoding);
1363
1364    if (!encodingName)
1365        encodingName = "unknown";
1366
1367    return encodingName;
1368}
1369
1370/*
1371   doctype: omit | auto | strict | loose | <fpi>
1372
1373   where the fpi is a string similar to
1374
1375      "-//ACME//DTD HTML 3.14159//EN"
1376*/
1377Bool ParseDocType( TidyDocImpl* doc, const TidyOptionImpl* option )
1378{
1379    tmbchar buf[ 32 ] = {0};
1380    uint i = 0;
1381    Bool status = yes;
1382    TidyDoctypeModes dtmode = TidyDoctypeAuto;
1383
1384    TidyConfigImpl* cfg = &doc->config;
1385    tchar c = SkipWhite( cfg );
1386
1387    /* "-//ACME//DTD HTML 3.14159//EN" or similar */
1388
1389    if ( c == '"' || c == '\'' )
1390    {
1391        status = ParseString(doc, option);
1392        if (status)
1393            TY_(SetOptionInt)( doc, TidyDoctypeMode, TidyDoctypeUser );
1394
1395        return status;
1396    }
1397
1398    /* read first word */
1399    while ( i < sizeof(buf)-1 && c != EndOfStream && !TY_(IsWhite)(c) )
1400    {
1401        buf[i++] = (tmbchar) c;
1402        c = AdvanceChar( cfg );
1403    }
1404    buf[i] = '\0';
1405
1406    if ( TY_(tmbstrcasecmp)(buf, "auto") == 0 )
1407        dtmode = TidyDoctypeAuto;
1408    else if ( TY_(tmbstrcasecmp)(buf, "omit") == 0 )
1409        dtmode = TidyDoctypeOmit;
1410    else if ( TY_(tmbstrcasecmp)(buf, "strict") == 0 )
1411        dtmode = TidyDoctypeStrict;
1412    else if ( TY_(tmbstrcasecmp)(buf, "loose") == 0 ||
1413              TY_(tmbstrcasecmp)(buf, "transitional") == 0 )
1414        dtmode = TidyDoctypeLoose;
1415    else
1416    {
1417        TY_(ReportBadArgument)( doc, option->name );
1418        status = no;
1419    }
1420
1421    if ( status )
1422        TY_(SetOptionInt)( doc, TidyDoctypeMode, dtmode );
1423    return status;
1424}
1425
1426Bool ParseRepeatAttr( TidyDocImpl* doc, const TidyOptionImpl* option )
1427{
1428    Bool status = yes;
1429    tmbchar buf[64] = {0};
1430    uint i = 0;
1431
1432    TidyConfigImpl* cfg = &doc->config;
1433    tchar c = SkipWhite( cfg );
1434
1435    while (i < sizeof(buf)-1 && c != EndOfStream && !TY_(IsWhite)(c))
1436    {
1437        buf[i++] = (tmbchar) c;
1438        c = AdvanceChar( cfg );
1439    }
1440    buf[i] = '\0';
1441
1442    if ( TY_(tmbstrcasecmp)(buf, "keep-first") == 0 )
1443        cfg->value[ TidyDuplicateAttrs ].v = TidyKeepFirst;
1444    else if ( TY_(tmbstrcasecmp)(buf, "keep-last") == 0 )
1445        cfg->value[ TidyDuplicateAttrs ].v = TidyKeepLast;
1446    else
1447    {
1448        TY_(ReportBadArgument)( doc, option->name );
1449        status = no;
1450    }
1451    return status;
1452}
1453
1454/* Use TidyOptionId as iterator.
1455** Send index of 1st option after TidyOptionUnknown as start of list.
1456*/
1457TidyIterator TY_(getOptionList)( TidyDocImpl* ARG_UNUSED(doc) )
1458{
1459  return (TidyIterator) 1;
1460}
1461
1462/* Check if this item is last valid option.
1463** If so, zero out iterator.
1464*/
1465const TidyOptionImpl*  TY_(getNextOption)( TidyDocImpl* ARG_UNUSED(doc),
1466                                           TidyIterator* iter )
1467{
1468  const TidyOptionImpl* option = NULL;
1469  ulong optId;
1470  assert( iter != NULL );
1471  optId = (ulong) *iter;
1472  if ( optId > TidyUnknownOption && optId < N_TIDY_OPTIONS )
1473  {
1474    option = &option_defs[ optId ];
1475    optId++;
1476  }
1477  *iter = (TidyIterator) ( optId < N_TIDY_OPTIONS ? optId : 0 );
1478  return option;
1479}
1480
1481/* Use a 1-based array index as iterator: 0 == end-of-list
1482*/
1483TidyIterator TY_(getOptionPickList)( const TidyOptionImpl* option )
1484{
1485    ulong ix = 0;
1486    if ( option && option->pickList )
1487        ix = 1;
1488    return (TidyIterator) ix;
1489}
1490
1491ctmbstr      TY_(getNextOptionPick)( const TidyOptionImpl* option,
1492                                     TidyIterator* iter )
1493{
1494    ulong ix;
1495    ctmbstr val = NULL;
1496    assert( option!=NULL && iter != NULL );
1497
1498    ix = (ulong) *iter;
1499    if ( ix > 0 && ix < 16 && option->pickList )
1500        val = option->pickList[ ix-1 ];
1501    *iter = (TidyIterator) ( val && option->pickList[ix] ? ix + 1 : 0 );
1502    return val;
1503}
1504
1505static int  WriteOptionString( const TidyOptionImpl* option,
1506                               ctmbstr sval, StreamOut* out )
1507{
1508  ctmbstr cp = option->name;
1509  while ( *cp )
1510      TY_(WriteChar)( *cp++, out );
1511  TY_(WriteChar)( ':', out );
1512  TY_(WriteChar)( ' ', out );
1513  cp = sval;
1514  while ( *cp )
1515      TY_(WriteChar)( *cp++, out );
1516  TY_(WriteChar)( '\n', out );
1517  return 0;
1518}
1519
1520static int  WriteOptionInt( const TidyOptionImpl* option, uint ival, StreamOut* out )
1521{
1522  tmbchar sval[ 32 ] = {0};
1523  TY_(tmbsnprintf)(sval, sizeof(sval), "%u", ival );
1524  return WriteOptionString( option, sval, out );
1525}
1526
1527static int  WriteOptionBool( const TidyOptionImpl* option, Bool bval, StreamOut* out )
1528{
1529  ctmbstr sval = bval ? "yes" : "no";
1530  return WriteOptionString( option, sval, out );
1531}
1532
1533static int  WriteOptionPick( const TidyOptionImpl* option, uint ival, StreamOut* out )
1534{
1535    uint ix;
1536    const ctmbstr* val = option->pickList;
1537    for ( ix=0; val[ix] && ix<ival; ++ix )
1538        /**/;
1539    if ( ix==ival && val[ix] )
1540        return WriteOptionString( option, val[ix], out );
1541    return -1;
1542}
1543
1544Bool  TY_(ConfigDiffThanSnapshot)( TidyDocImpl* doc )
1545{
1546  int diff = memcmp( &doc->config.value, &doc->config.snapshot,
1547                     N_TIDY_OPTIONS * sizeof(uint) );
1548  return ( diff != 0 );
1549}
1550
1551Bool  TY_(ConfigDiffThanDefault)( TidyDocImpl* doc )
1552{
1553  Bool diff = no;
1554  const TidyOptionImpl* option = option_defs + 1;
1555  const TidyOptionValue* val = doc->config.value;
1556  for ( /**/; !diff && option && option->name; ++option, ++val )
1557  {
1558      diff = !OptionValueEqDefault( option, val );
1559  }
1560  return diff;
1561}
1562
1563
1564static int  SaveConfigToStream( TidyDocImpl* doc, StreamOut* out )
1565{
1566    int rc = 0;
1567    const TidyOptionImpl* option;
1568    for ( option=option_defs+1; 0==rc && option && option->name; ++option )
1569    {
1570        const TidyOptionValue* val = &doc->config.value[ option->id ];
1571        if ( option->parser == NULL )
1572            continue;
1573        if ( OptionValueEqDefault( option, val ) && option->id != TidyDoctype)
1574            continue;
1575
1576        if ( option->id == TidyDoctype )  /* Special case */
1577        {
1578          ulong dtmode = cfg( doc, TidyDoctypeMode );
1579          if ( dtmode == TidyDoctypeUser )
1580          {
1581            tmbstr t;
1582
1583            /* add 2 double quotes */
1584            if (( t = (tmbstr)MemAlloc( TY_(tmbstrlen)( val->p ) + 2 ) ))
1585            {
1586              t[0] = '\"'; t[1] = 0;
1587
1588              TY_(tmbstrcat)( t, val->p );
1589              TY_(tmbstrcat)( t, "\"" );
1590              rc = WriteOptionString( option, t, out );
1591
1592              MemFree( t );
1593            }
1594          }
1595          else if ( dtmode == option_defs[TidyDoctypeMode].dflt )
1596            continue;
1597          else
1598            rc = WriteOptionPick( option, dtmode, out );
1599        }
1600        else if ( option->pickList )
1601          rc = WriteOptionPick( option, val->v, out );
1602        else
1603        {
1604          switch ( option->type )
1605          {
1606          case TidyString:
1607            rc = WriteOptionString( option, val->p, out );
1608            break;
1609          case TidyInteger:
1610            rc = WriteOptionInt( option, val->v, out );
1611            break;
1612          case TidyBoolean:
1613            rc = WriteOptionBool( option, val->v ? yes : no, out );
1614            break;
1615          }
1616        }
1617    }
1618    return rc;
1619}
1620
1621int  TY_(SaveConfigFile)( TidyDocImpl* doc, ctmbstr cfgfil )
1622{
1623    int status = -1;
1624    StreamOut* out = NULL;
1625    uint outenc = cfg( doc, TidyOutCharEncoding );
1626    uint nl = cfg( doc, TidyNewline );
1627    FILE* fout = fopen( cfgfil, "wb" );
1628    if ( fout )
1629    {
1630        out = TY_(FileOutput)( fout, outenc, nl );
1631        status = SaveConfigToStream( doc, out );
1632        fclose( fout );
1633        MemFree( out );
1634    }
1635    return status;
1636}
1637
1638int  TY_(SaveConfigSink)( TidyDocImpl* doc, TidyOutputSink* sink )
1639{
1640    uint outenc = cfg( doc, TidyOutCharEncoding );
1641    uint nl = cfg( doc, TidyNewline );
1642    StreamOut* out = TY_(UserOutput)( sink, outenc, nl );
1643    int status = SaveConfigToStream( doc, out );
1644    MemFree( out );
1645    return status;
1646}
1647
1648/*
1649 * local variables:
1650 * mode: c
1651 * indent-tabs-mode: nil
1652 * c-basic-offset: 4
1653 * eval: (c-set-offset 'substatement-open 0)
1654 * end:
1655 */
1656