diff --git a/include/tidyenum.h b/include/tidyenum.h index 2a261ed..6f4ff19 100755 --- a/include/tidyenum.h +++ b/include/tidyenum.h @@ -608,6 +608,7 @@ typedef enum TidyPPrintTabs, /**< Indent using tabs istead of spaces */ TidyPreserveEntities, /**< Preserve entities */ TidyPreTags, /**< Declared pre tags */ + TidyPriorityAttributes, /**< Attributes to place first in an element */ #if SUPPORT_ASIAN_ENCODINGS TidyPunctWrap, /**< consider punctuation and breaking spaces for wrapping */ #else diff --git a/src/attrs.c b/src/attrs.c index a4cb379..0286f55 100644 --- a/src/attrs.c +++ b/src/attrs.c @@ -929,6 +929,42 @@ AttVal* TY_(RepairAttrValue)(TidyDocImpl* doc, Node* node, ctmbstr name, ctmbstr return TY_(AddAttribute)(doc, node, name, value); } + +void TY_(DefinePriorityAttribute)(TidyDocImpl* doc, ctmbstr name) +{ + enum { capacity = 10 }; + PriorityAttribs *priorities = &(doc->attribs.priorityAttribs); + + /* @TODO: Don't forget to free this stuff */ + if ( !priorities->list ) + { + priorities->list = malloc( sizeof(ctmbstr) * capacity ); + priorities->list[0] = NULL; + priorities->capacity = capacity; + priorities->count = 0; + } + + if ( priorities->count >= priorities->capacity ) + { + priorities->capacity = priorities->capacity * 2; + priorities->list = realloc( priorities->list, sizeof(tmbstr) * priorities->capacity + 1 ); + } + + priorities->list[priorities->count] = TY_(tmbstrdup)( doc->allocator, name); + priorities->count++; + priorities->list[priorities->count] = NULL; + + uint i = 0; + while ( priorities->list[i] != NULL ) + { + printf("Array contains %s.\n", priorities->list[i]); + i++; + } + + printf("Adding %s to list.\n", name); +} + + static Bool CheckAttrType( TidyDocImpl* doc, ctmbstr attrname, AttrCheck type ) { @@ -2219,25 +2255,85 @@ void TY_(SortAttributes)(Node* node, TidyAttrSortStrategy strat) * SOFTWARE. */ -typedef int(*ptAttValComparator)(AttVal *one, AttVal *two); +typedef int(*ptAttValComparator)(AttVal *one, AttVal *two, ctmbstr *list); -/* Comparison function for TidySortAttrAlpha */ +/* Returns the index of the item in the array, or -1 if not in the array */ static -int AlphaComparator(AttVal *one, AttVal *two) +int indexof( ctmbstr item, ctmbstr *list ) { + uint i = 0; + while ( list[i] != NULL ) { + if ( TY_(tmbstrcasecmp)(item, list[i]) == 0 ) + return i; + i++; + } + + return -1; +} + +/* Comparison function for TidySortAttrAlpha. Will also consider items in + the passed in list as higher-priority, and will group them first. + */ +static +int AlphaComparator(AttVal *one, AttVal *two, ctmbstr *list) +{ + int oneIndex = indexof( one->attribute, list ); + int twoIndex = indexof( two->attribute, list ); + + /* If both on the list, the lower index has priority. */ + if ( oneIndex >= 0 && twoIndex >= 0 ) + return oneIndex < twoIndex ? -1 : 1; + + /* If A on the list but B not on the list, then A has priority. */ + if ( oneIndex >= 0 && twoIndex == -1 ) + return -1; + + /* If A not on the list but B is on the list, then B has priority. */ + if ( oneIndex == -1 && twoIndex >= 0 ) + return 1; + + /* Otherwise nothing is on the list, so just compare strings. */ return TY_(tmbstrcmp)(one->attribute, two->attribute); } +/* Comparison function for prioritizing list items. It doesn't otherwise + sort. + */ +static +int PriorityComparator(AttVal *one, AttVal *two, ctmbstr *list) +{ + int oneIndex = indexof( one->attribute, list ); + int twoIndex = indexof( two->attribute, list ); + + /* If both on the list, the lower index has priority. */ + if ( oneIndex >= 0 && twoIndex >= 0 ) + return oneIndex < twoIndex ? -1 : 1; + + /* If A on the list but B not on the list, then A has priority. */ + if ( oneIndex >= 0 && twoIndex == -1 ) + return -1; + + /* If A not on the list but B is on the list, then B has priority. */ + if ( oneIndex == -1 && twoIndex >= 0 ) + return 1; + + /* Otherwise nothing is on the list, so just mark them as the same. */ + return 0; +} + + /* The "factory method" that returns a pointer to the comparator function */ static -ptAttValComparator GetAttValComparator(TidyAttrSortStrategy strat) +ptAttValComparator GetAttValComparator(TidyAttrSortStrategy strat, ctmbstr *list) { switch (strat) { case TidySortAttrAlpha: return AlphaComparator; case TidySortAttrNone: + if ( list[0] ) + return PriorityComparator; break; } return 0; @@ -2247,7 +2343,13 @@ ptAttValComparator GetAttValComparator(TidyAttrSortStrategy strat) static AttVal *SortAttVal( AttVal *list, TidyAttrSortStrategy strat) { - ptAttValComparator ptComparator = GetAttValComparator(strat); + /* Get the list from the pass-in tidyDoc, which is a to-do. + We'll use this static list temporarily. + */ +// ctmbstr doc[] = { NULL }; + ctmbstr temp_list[] = { "id", "name", "class", NULL }; /* temp until option */ + + ptAttValComparator ptComparator = GetAttValComparator(strat, temp_list); AttVal *p, *q, *e, *tail; int insize, nmerges, psize, qsize, i; @@ -2258,6 +2360,10 @@ AttVal *SortAttVal( AttVal *list, TidyAttrSortStrategy strat) if (!list) return NULL; + /* If no comparator, return the list as is */ + if (ptComparator == 0) + return list; + insize = 1; while (1) { @@ -2291,7 +2397,7 @@ AttVal *SortAttVal( AttVal *list, TidyAttrSortStrategy strat) } else if (qsize == 0 || !q) { /* q is empty; e must come from p. */ e = p; p = p->next; psize--; - } else if (ptComparator(p,q) <= 0) { + } else if (ptComparator(p,q, temp_list) <= 0) { /* First element of p is lower (or same); * e must come from p. */ e = p; p = p->next; psize--; diff --git a/src/attrs.h b/src/attrs.h index 0192efc..36f3a63 100644 --- a/src/attrs.h +++ b/src/attrs.h @@ -60,6 +60,12 @@ enum ANCHOR_HASH_SIZE=1021u }; +typedef struct _priorityAttribs { + ctmbstr* list; + uint count; + uint capacity; +} PriorityAttribs; + struct _TidyAttribImpl { /* anchor/node lookup */ @@ -68,6 +74,9 @@ struct _TidyAttribImpl /* Declared literal attributes */ Attribute* declared_attr_list; + /* Prioritized list of attributes to write */ + PriorityAttribs priorityAttribs; + #if ATTRIBUTE_HASH_LOOKUP AttrHash* hashtab[ATTRIBUTE_HASH_SIZE]; #endif @@ -93,6 +102,9 @@ AttVal* TY_(AddAttribute)( TidyDocImpl* doc, AttVal* TY_(RepairAttrValue)(TidyDocImpl* doc, Node* node, ctmbstr name, ctmbstr value); +/* Add an item to the list of priority attributes to write first. */ +void TY_(DefinePriorityAttribute)(TidyDocImpl* doc, ctmbstr name); + Bool TY_(IsUrl)( TidyDocImpl* doc, ctmbstr attrname ); /* Bool IsBool( TidyDocImpl* doc, ctmbstr attrname ); */ diff --git a/src/config.c b/src/config.c index 64003ea..736473b 100644 --- a/src/config.c +++ b/src/config.c @@ -172,6 +172,9 @@ static PickListItems attributeCasePicks = { static void AdjustConfig( TidyDocImpl* doc ); +/* a space or comma separated list of attribute names */ +static ParseProperty ParseAttribNames; + /* parser for integer values */ static ParseProperty ParseInt; @@ -277,6 +280,7 @@ static const TidyOptionImpl option_defs[] = { TidyPPrintTabs, PP, "indent-with-tabs", BL, no, ParseTabs, &boolPicks }, /* 20150515 - Issue #108 */ { TidyPreserveEntities, MU, "preserve-entities", BL, no, ParsePickList, &boolPicks }, { TidyPreTags, MU, "new-pre-tags", ST, 0, ParseTagNames, NULL }, + { TidyPriorityAttributes, MU, "priority-attributes", ST, 0, ParseAttribNames, NULL }, #if SUPPORT_ASIAN_ENCODINGS { TidyPunctWrap, PP, "punctuation-wrap", BL, no, ParsePickList, &boolPicks }, #endif @@ -1087,6 +1091,88 @@ void AdjustConfig( TidyDocImpl* doc ) } } + +/* Coordinates Config update and Attributes data */ +void TY_(DeclarePriorityAttrib)( TidyDocImpl* doc, TidyOptionId optId, ctmbstr name ) +{ + ctmbstr prvval = cfgStr( doc, optId ); + tmbstr catval = NULL; + ctmbstr theval = name; + if ( prvval ) + { + uint len = TY_(tmbstrlen)(name) + TY_(tmbstrlen)(prvval) + 3; + catval = TY_(tmbstrndup)( doc->allocator, prvval, len ); + TY_(tmbstrcat)( catval, ", " ); + TY_(tmbstrcat)( catval, name ); + theval = catval; + } + + TY_(DefinePriorityAttribute)( doc, name ); + SetOptionValue( doc, optId, theval ); + if ( catval ) + TidyDocFree( doc, catval ); + } + +/* a space or comma separated list of attribute names */ +Bool ParseAttribNames( TidyDocImpl* doc, const TidyOptionImpl* option ) +{ + TidyConfigImpl* cfg = &doc->config; + tmbchar buf[1024]; + uint i = 0, nAttribs = 0; + uint c = SkipWhite( cfg ); + + + SetOptionValue( doc, option->id, NULL ); + + do + { + if (c == ' ' || c == '\t' || c == ',') + { + c = AdvanceChar( cfg ); + continue; + } + + if ( c == '\r' || c == '\n' ) + { + uint c2 = AdvanceChar( cfg ); + if ( c == '\r' && c2 == '\n' ) + c = AdvanceChar( cfg ); + else + c = c2; + + if ( !TY_(IsWhite)(c) ) + { + buf[i] = 0; + TY_(UngetChar)( c, cfg->cfgIn ); + TY_(UngetChar)( '\n', cfg->cfgIn ); + break; + } + } + + while ( i < sizeof(buf)-2 && c != EndOfStream && !TY_(IsWhite)(c) && c != ',' ) + { + buf[i++] = (tmbchar) c; + c = AdvanceChar( cfg ); + } + + buf[i] = '\0'; + if (i == 0) /* Skip empty attribute definition. Possible when */ + continue; /* there is a trailing space on the line. */ + + /* add attribute to array */ + TY_(DeclarePriorityAttrib)( doc, option->id, buf ); + i = 0; + ++nAttribs; + } + while ( c != EndOfStream ); + + if ( i > 0 ) + TY_(DeclarePriorityAttrib)( doc, option->id, buf ); + + return ( nAttribs > 0 ); +} + + /* unsigned integers */ Bool ParseInt( TidyDocImpl* doc, const TidyOptionImpl* entry ) { @@ -1351,9 +1437,9 @@ Bool ParseTagNames( TidyDocImpl* doc, const TidyOptionImpl* option ) return ( nTags > 0 ); } + /* a string including whitespace */ /* munges whitespace sequences */ - Bool ParseString( TidyDocImpl* doc, const TidyOptionImpl* option ) { TidyConfigImpl* cfg = &doc->config; diff --git a/src/language_en.h b/src/language_en.h index a41e220..82a9d99 100755 --- a/src/language_en.h +++ b/src/language_en.h @@ -1005,6 +1005,22 @@ static languageDefinition language_en = { whichPluralForm_en, { "
" "This option is ignored in XML mode. " }, + {/* Important notes for translators: + - Use only , , , , and +
. + - Entities, tags, attributes, etc., should be enclosed in . + - Option values should be enclosed in . + - It's very important that
be self-closing! + - The strings "Tidy" and "HTML Tidy" are the program name and must not + be translated. */ + TidyPriorityAttributes, 0, + "This option allows prioritizing the writing of attributes in tidied " + "documents, allowing them to written before the other attributes of an " + "element. For example, you might specify that id and " + "name are written before every other attribute. " + "
" + "This option takes a space or comma separated list of attribute names. " + }, #if SUPPORT_ASIAN_ENCODINGS {/* Important notes for translators: - Use only , , , , and diff --git a/src/tidylib.c b/src/tidylib.c index 162fe5d..96e2afb 100755 --- a/src/tidylib.c +++ b/src/tidylib.c @@ -2215,8 +2215,7 @@ int tidyDocSaveStream( TidyDocImpl* doc, StreamOut* out ) else TY_(ReplacePreformattedSpaces)(doc, &doc->root); - if ( sortAttrStrat != TidySortAttrNone ) - TY_(SortAttributes)(&doc->root, sortAttrStrat); + TY_(SortAttributes)(&doc->root, sortAttrStrat); if ( showMarkup && (doc->errors == 0 || forceOutput) ) {