| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "CSSSheet.h" |
| #include "CSS.h" |
| #include "CSSProperties.h" |
| #include "CSSParser.h" |
| #include "CSSSelector.h" |
| #include "CSSLength.h" |
| #include "DFString.h" |
| #include "DFHashTable.h" |
| #include "DFBuffer.h" |
| #include "DFCommon.h" |
| #include "DFPlatform.h" |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // CSSSheet // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| struct CSSSheet { |
| size_t retainCount; |
| DFHashTable *_styles; // const char * -> CSSStyle |
| DFHashTable *_defaultStyles; // int(StyleFamily) -> CSSStyle |
| }; |
| |
| CSSSheet *CSSSheetNew(void) |
| { |
| CSSSheet *sheet = (CSSSheet *)xcalloc(1,sizeof(CSSSheet)); |
| sheet->retainCount = 1; |
| sheet->_styles = DFHashTableNew((DFCopyFunction)CSSStyleRetain,(DFFreeFunction)CSSStyleRelease); |
| sheet->_defaultStyles = DFHashTableNew((DFCopyFunction)CSSStyleRetain,(DFFreeFunction)CSSStyleRelease); |
| return sheet; |
| } |
| |
| CSSSheet *CSSSheetRetain(CSSSheet *sheet) |
| { |
| if (sheet != NULL) |
| sheet->retainCount++; |
| return sheet; |
| } |
| |
| void CSSSheetRelease(CSSSheet *sheet) |
| { |
| if ((sheet == NULL) || (--sheet->retainCount > 0)) |
| return; |
| |
| DFHashTableRelease(sheet->_styles); |
| DFHashTableRelease(sheet->_defaultStyles); |
| free(sheet); |
| } |
| |
| const char **CSSSheetCopySelectors(CSSSheet *sheet) |
| { |
| return DFHashTableCopyKeys(sheet->_styles); |
| } |
| |
| void CSSSheetAddStyle(CSSSheet *sheet, CSSStyle *style) |
| { |
| assert(style->selector != NULL); |
| DFHashTableAdd(sheet->_styles,style->selector,style); |
| } |
| |
| void CSSSheetRemoveStyle(CSSSheet *sheet, CSSStyle *style) |
| { |
| DFHashTableRemove(sheet->_styles,style->selector); |
| } |
| |
| CSSStyle *CSSSheetLookupSelector(CSSSheet *sheet, const char *selector, int add, int latent) |
| { |
| CSSStyle *style = DFHashTableLookup(sheet->_styles,selector); |
| if ((style == NULL) && add) { |
| style = CSSStyleNew(selector); |
| style->latent = latent; |
| CSSSheetAddStyle(sheet,style); |
| CSSStyleRelease(style); // The hash table keeps a reference, so this is safe |
| } |
| if ((style != NULL) && add && style->latent && !latent) |
| style->latent = 0; |
| return style; |
| } |
| |
| CSSStyle *CSSSheetLookupElement(CSSSheet *sheet, const char *elementName, const char *className, int add, int latent) |
| { |
| char *selector = CSSMakeSelector(elementName,className); |
| CSSStyle *result = CSSSheetLookupSelector(sheet,selector,add,latent); |
| free(selector); |
| return result; |
| } |
| |
| CSSStyle *CSSSheetFlattenedStyle(CSSSheet *sheet, CSSStyle *orig) |
| { |
| // FIXME: Need tests for parent cycles |
| CSSStyle *ancestor = orig; |
| CSSStyle *result = CSSStyleNew("temp"); |
| DFHashTable *visited = DFHashTableNew((DFCopyFunction)xstrdup,(DFFreeFunction)free); |
| const char **allSuffixes = NULL; |
| while (1) { |
| free(allSuffixes); |
| allSuffixes = CSSStyleCopySuffixes(ancestor); |
| for (int suffixIndex = 0; allSuffixes[suffixIndex]; suffixIndex++) { |
| const char *suffix = allSuffixes[suffixIndex]; |
| CSSProperties *origProperties = CSSStyleRuleForSuffix(ancestor,suffix); |
| CSSProperties *collapsedProperties = CSSStyleRuleForSuffix(result,suffix); |
| |
| const char **allNames = CSSPropertiesCopyNames(origProperties); |
| for (int nameIndex = 0; allNames[nameIndex]; nameIndex++) { |
| const char *name = allNames[nameIndex]; |
| if (!strcmp(name,"-uxwrite-default") && (ancestor != orig)) |
| continue; |
| if (CSSGet(collapsedProperties,name) == NULL) |
| CSSPut(collapsedProperties,name,CSSGet(origProperties,name)); |
| } |
| free(allNames); |
| } |
| DFHashTableAdd(visited,ancestor->selector,""); |
| ancestor = CSSSheetGetStyleParent(sheet,ancestor); |
| if ((ancestor == NULL) || (DFHashTableLookup(visited,ancestor->selector) != NULL)) |
| break; |
| } |
| free(allSuffixes); |
| DFHashTableRelease(visited); |
| return result; |
| } |
| |
| DFHashTable *CSSSheetRules(CSSSheet *sheet) |
| { |
| DFHashTable *result = DFHashTableNew((DFCopyFunction)DFHashTableRetain,(DFFreeFunction)DFHashTableRelease); |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| for (int selIndex = 0; allSelectors[selIndex]; selIndex++) { |
| const char *selector = allSelectors[selIndex]; |
| CSSStyle *origStyle = CSSSheetLookupSelector(sheet,selector,0,0); |
| if ((origStyle == NULL) || origStyle->latent) |
| continue; |
| |
| char *elementName = CSSSelectorCopyElementName(selector); |
| char *className = CSSSelectorCopyClassName(selector); |
| |
| char *baseSelector; |
| if (className != NULL) { |
| char *escapedClassName = CSSEscapeIdent(className); |
| baseSelector = DFFormatString("%s.%s",elementName,escapedClassName); |
| free(escapedClassName); |
| } |
| else { |
| baseSelector = xstrdup(elementName); |
| } |
| |
| CSSStyle *flattenedStyle = CSSSheetFlattenedStyle(sheet,origStyle); |
| const char **allSuffixes = CSSStyleCopySuffixes(flattenedStyle); |
| for (int suffixIndex = 0; allSuffixes[suffixIndex]; suffixIndex++) { |
| const char *suffix = allSuffixes[suffixIndex]; |
| char *fullSelector = DFFormatString("%s%s",baseSelector,suffix); |
| CSSProperties *properties = CSSStyleRuleForSuffix(flattenedStyle,suffix); |
| DFHashTable *collapsed = CSSCollapseProperties(properties); |
| if (!((DFHashTableCount(collapsed) == 0) && ((strlen(suffix) > 0) || CSSIsBuiltinSelector(selector)))) |
| DFHashTableAdd(result,fullSelector,collapsed); |
| free(fullSelector); |
| DFHashTableRelease(collapsed); |
| } |
| free(allSuffixes); |
| |
| CSSStyleRelease(flattenedStyle); |
| free(elementName); |
| free(className); |
| free(baseSelector); |
| } |
| free(allSelectors); |
| return result; |
| } |
| |
| char *CSSSheetCopyCSSText(CSSSheet *sheet) |
| { |
| DFHashTable *hashRules = CSSSheetRules(sheet); |
| char *cssText = CSSCopyStylesheetTextFromRules(hashRules); |
| DFHashTableRelease(hashRules); |
| return cssText; |
| } |
| |
| char *CSSSheetCopyText(CSSSheet *sheet) |
| { |
| DFBuffer *result = DFBufferNew(); |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| DFSortStringsCaseInsensitive(allSelectors); |
| for (int selIndex = 0; allSelectors[selIndex]; selIndex++) { |
| CSSStyle *style = CSSSheetLookupSelector(sheet,allSelectors[selIndex],0,0); |
| DFBufferFormat(result,"%s\n",style->selector); |
| |
| const char **sortedSuffixes = CSSStyleCopySuffixes(style); |
| DFSortStringsCaseInsensitive(sortedSuffixes); |
| for (int suffixIndex = 0; sortedSuffixes[suffixIndex]; suffixIndex++) { |
| const char *suffix = sortedSuffixes[suffixIndex]; |
| char *quotedSuffix = DFQuote(suffix); |
| DFBufferFormat(result," %s\n",quotedSuffix); |
| free(quotedSuffix); |
| CSSProperties *properties = CSSStyleRuleForSuffix(style,suffix); |
| |
| const char **sortedNames = CSSPropertiesCopyNames(properties); |
| DFSortStringsCaseInsensitive(sortedNames); |
| for (int nameIndex = 0; sortedNames[nameIndex]; nameIndex++) { |
| const char *name = sortedNames[nameIndex]; |
| const char *value = CSSGet(properties,name); |
| DFBufferFormat(result," %s = %s\n",name,value); |
| } |
| free(sortedNames); |
| } |
| free(sortedSuffixes); |
| } |
| free(allSelectors); |
| char *str = xstrdup(result->data); |
| DFBufferRelease(result); |
| return str; |
| } |
| |
| static void breakCycles(CSSSheet *sheet) |
| { |
| // FIXME: Not covered by tests |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| const char *selector = allSelectors[i]; |
| CSSStyle *style = CSSSheetLookupSelector(sheet,selector,0,0); |
| DFHashTable *visited = DFHashTableNew((DFCopyFunction)xstrdup,(DFFreeFunction)free); |
| int depth = 0; |
| |
| while (style != NULL) { |
| if (DFHashTableLookup(visited,style->selector) != NULL) { |
| CSSStyleSetParent(style,NULL); |
| break; |
| } |
| DFHashTableAdd(visited,style->selector,""); |
| style = CSSSheetGetStyleParent(sheet,style); |
| depth++; |
| } |
| DFHashTableRelease(visited); |
| } |
| free(allSelectors); |
| } |
| |
| static const char **reverseTopologicalSortedSelectors(CSSSheet *sheet) |
| { |
| // FIXME: Not covered by tests |
| DFArray *selectorsByDepth = DFArrayNew((DFCopyFunction)DFArrayRetain,(DFFreeFunction)DFArrayRelease); |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| const char *selector = allSelectors[i]; |
| CSSStyle *style = CSSSheetLookupSelector(sheet,selector,0,0); |
| unsigned int depth = 0; |
| |
| while ((style = CSSSheetGetStyleParent(sheet,style)) != NULL) |
| depth++; |
| |
| while (DFArrayCount(selectorsByDepth) < depth+1) { |
| DFArray *array = DFArrayNew((DFCopyFunction)xstrdup,(DFFreeFunction)free); |
| DFArrayAppend(selectorsByDepth,array); |
| DFArrayRelease(array); |
| } |
| |
| DFArray *selectorsAtCurrentDepth = DFArrayItemAt(selectorsByDepth,depth); |
| DFArrayAppend(selectorsAtCurrentDepth,(char*)selector); |
| } |
| free(allSelectors); |
| |
| DFArray *sortedSelectors = DFArrayNew((DFCopyFunction)xstrdup,(DFFreeFunction)free); |
| for (size_t i = DFArrayCount(selectorsByDepth); i > 0; i--) { |
| DFArray *atDepth = DFArrayItemAt(selectorsByDepth,i-1); |
| for (size_t j = 0; j < DFArrayCount(atDepth); j++) |
| DFArrayAppend(sortedSelectors,DFArrayItemAt(atDepth,j)); |
| } |
| const char **result = DFStringArrayFlatten(sortedSelectors); |
| DFArrayRelease(selectorsByDepth); |
| DFArrayRelease(sortedSelectors); |
| return result; |
| } |
| |
| static void removeRedundantProperties(CSSSheet *sheet) |
| { |
| // Remove any properties set on a style that have the same value as the corresponding property |
| // on the parent style. This is necessary because CSS doesn't support style inheritance (in |
| // the sense of Word & ODF's styles), so when we save out a HTML file, every style has all |
| // properties of its ancestors. After reading in a HTML file for the purposes of updating the |
| // original Word or ODF style, we don't want these extra property settings to remain, so that |
| // we can avoid adding spurious extra redundant property settings to the original file. |
| |
| breakCycles(sheet); |
| const char **sortedSelectors = reverseTopologicalSortedSelectors(sheet); |
| |
| for (size_t selIndex = 0; sortedSelectors[selIndex]; selIndex++) { |
| const char *selector = sortedSelectors[selIndex]; |
| CSSStyle *child = CSSSheetLookupSelector(sheet,selector,0,0); |
| CSSStyle *parent = CSSSheetGetStyleParent(sheet,child); |
| if (parent == NULL) |
| continue; |
| const char **allSuffixes = CSSStyleCopySuffixes(child); |
| for (int suffixIndex = 0; allSuffixes[suffixIndex]; suffixIndex++) { |
| const char *suffix = allSuffixes[suffixIndex]; |
| int isCell = !strcmp(suffix," > * > tr > td"); |
| CSSProperties *childProperties = CSSStyleRuleForSuffix(child,suffix); |
| CSSProperties *parentProperties = CSSStyleRuleForSuffix(parent,suffix); |
| |
| const char **allNames = CSSPropertiesCopyNames(childProperties); |
| for (int nameIndex = 0; allNames[nameIndex]; nameIndex++) { |
| const char *name = allNames[nameIndex]; |
| |
| // In docx's styles.xml, the tblCellMar values in table styles are not inherited |
| // (this seems like a bug in word, as isn't inconsistent with all other properties) |
| // So keep these ones. |
| if (isCell && DFStringHasPrefix(name,"padding-")) |
| continue; |
| |
| const char *childVal = CSSGet(childProperties,name); |
| const char *parentVal = CSSGet(parentProperties,name); |
| if ((childVal != NULL) && (parentVal != NULL) && DFStringEquals(childVal,parentVal)) |
| CSSPut(childProperties,name,NULL); |
| } |
| free(allNames); |
| } |
| free(allSuffixes); |
| } |
| free(sortedSelectors); |
| } |
| |
| static void updateFromRawCSSRules(CSSSheet *sheet, DFHashTable *rules) |
| { |
| // FIXME: Handle class names containing escape sequences |
| DFHashTableRelease(sheet->_styles); |
| sheet->_styles = DFHashTableNew((DFCopyFunction)CSSStyleRetain,(DFFreeFunction)CSSStyleRelease); |
| |
| const char **sortedSelectors = DFHashTableCopyKeys(rules); |
| DFSortStringsCaseInsensitive(sortedSelectors); |
| for (int selIndex = 0; sortedSelectors[selIndex]; selIndex++) { |
| const char *constSelector = sortedSelectors[selIndex]; |
| |
| // Treat any selectors specifying the class name only as paragraph styles |
| char *selector; |
| if (!strncmp(constSelector,".",1)) |
| selector = DFFormatString("p%s",constSelector); // FIXME: Not covered by tests |
| else |
| selector = xstrdup(constSelector); |
| |
| DFHashTable *raw = DFHashTableLookup(rules,constSelector); |
| char *baseId = NULL; |
| char *suffix = NULL; |
| CSSParseSelector(selector,&baseId,&suffix); |
| |
| CSSStyle *style = CSSSheetLookupSelector(sheet,baseId,0,0); |
| if (style == NULL) { |
| style = CSSStyleNew(baseId); |
| CSSSheetAddStyle(sheet,style); |
| CSSStyleRelease(style); |
| } |
| |
| CSSProperties *properties = CSSStyleRuleForSuffix(style,suffix); |
| CSSProperties *expanded = CSSPropertiesNewWithRaw(raw); |
| |
| const char **allNames = CSSPropertiesCopyNames(expanded); |
| for (int nameIndex = 0; allNames[nameIndex]; nameIndex++) { |
| const char *name = allNames[nameIndex]; |
| CSSPut(properties,name,CSSGet(expanded,name)); |
| } |
| free(allNames); |
| |
| if (!strcmp(suffix,"")) { |
| const char *defaultVal = CSSGet(properties,"-uxwrite-default"); |
| if ((defaultVal != NULL) && DFStringEqualsCI(defaultVal,"true")) |
| CSSSheetSetDefaultStyle(sheet,style,StyleFamilyFromHTMLTag(style->tag)); |
| } |
| CSSPropertiesRelease(expanded); |
| free(baseId); |
| free(suffix); |
| free(selector); |
| } |
| free(sortedSelectors); |
| removeRedundantProperties(sheet); |
| } |
| |
| void CSSSheetUpdateFromCSSText(CSSSheet *sheet, const char *cssText) |
| { |
| DFHashTable *rules = DFHashTableNew((DFCopyFunction)DFHashTableRetain,(DFFreeFunction)DFHashTableRelease); |
| |
| CSSParser *parser = CSSParserNew(cssText); |
| DFHashTable *top = CSSParserRules(parser); |
| CSSParserFree(parser); |
| |
| const char **allSelectorsText = DFHashTableCopyKeys(top); |
| for (int i = 0; allSelectorsText[i]; i++) { |
| const char *selectorsText = allSelectorsText[i]; |
| const char *propertiesText = DFHashTableLookup(top,selectorsText); |
| |
| parser = CSSParserNew(selectorsText); |
| DFArray *selectors = CSSParserSelectors(parser); |
| CSSParserFree(parser); |
| if (selectors == NULL) |
| continue; |
| |
| parser = CSSParserNew(propertiesText); |
| DFHashTable *properties = CSSParserProperties(parser); |
| CSSParserFree(parser); |
| if (properties == NULL) { |
| DFArrayRelease(selectors); |
| continue; |
| } |
| |
| for (size_t selIndex = 0; selIndex < DFArrayCount(selectors); selIndex++) { |
| const char *selector = DFArrayItemAt(selectors,selIndex); |
| DFHashTableAdd(rules,selector,properties); |
| } |
| |
| DFHashTableRelease(properties); |
| DFArrayRelease(selectors); |
| } |
| |
| updateFromRawCSSRules(sheet,rules); |
| free(allSelectorsText); |
| DFHashTableRelease(top); |
| DFHashTableRelease(rules); |
| } |
| |
| CSSProperties *CSSSheetPageProperties(CSSSheet *sheet) |
| { |
| return CSSStyleRule(CSSSheetLookupElement(sheet,"@page",NULL,1,0)); |
| } |
| |
| CSSProperties *CSSSheetBodyProperties(CSSSheet *sheet) |
| { |
| return CSSStyleRule(CSSSheetLookupElement(sheet,"body",NULL,1,0)); |
| } |
| |
| CSSStyle *CSSSheetDefaultStyleForFamily(CSSSheet *sheet, StyleFamily family) |
| { |
| return DFHashTableLookupInt(sheet->_defaultStyles,(int)family); |
| } |
| |
| void CSSSheetSetDefaultStyle(CSSSheet *sheet, CSSStyle *style, StyleFamily family) |
| { |
| if (style == NULL) |
| DFHashTableRemoveInt(sheet->_defaultStyles,(int)family); |
| else |
| DFHashTableAddInt(sheet->_defaultStyles,(int)family,style); |
| } |
| |
| int CSSSheetHeadingNumbering(CSSSheet *sheet) |
| { |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| CSSStyle *style = CSSSheetLookupSelector(sheet,allSelectors[i],0,0); |
| if (style->headingLevel == 0) |
| continue; |
| if (CSSGet(CSSStyleBefore(style),"content") == NULL) |
| continue; |
| DFArray *contentParts = CSSParseContent(CSSGet(CSSStyleBefore(style),"content")); |
| for (size_t partIndex = 0; partIndex < DFArrayCount(contentParts); partIndex++) { |
| ContentPart *part = DFArrayItemAt(contentParts,partIndex); |
| if (part->type == ContentPartCounter) { |
| free(allSelectors); |
| DFArrayRelease(contentParts); |
| return 1; |
| } |
| } |
| DFArrayRelease(contentParts); |
| } |
| free(allSelectors); |
| return 0; |
| } |
| |
| typedef struct StyleList StyleList; |
| |
| struct StyleList { |
| CSSStyle *style; |
| StyleList *next; |
| }; |
| |
| static DFHashTable *getStylesByHeadingLevel(CSSSheet *sheet) |
| { |
| DFHashTable *stylesByHeadingLevel = DFHashTableNew(NULL,NULL); |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| CSSStyle *style = CSSSheetLookupSelector(sheet,allSelectors[i],0,0); |
| if (style->headingLevel > 0) { |
| int headingLevel = style->headingLevel; |
| StyleList *item = (StyleList *)xcalloc(1,sizeof(StyleList)); |
| item->style = CSSStyleRetain(style); |
| item->next = DFHashTableLookupInt(stylesByHeadingLevel,headingLevel); |
| DFHashTableAddInt(stylesByHeadingLevel,headingLevel,item); |
| } |
| } |
| free(allSelectors); |
| return stylesByHeadingLevel; |
| } |
| |
| static void enableNumberingForStyle(CSSStyle *style) |
| { |
| int level = style->headingLevel; |
| |
| DFBuffer *reset = DFBufferNew(); |
| for (int after = level+1; after <= 6; after++) { |
| if (reset->len == 0) |
| DFBufferFormat(reset,"h%d",after); |
| else |
| DFBufferFormat(reset," h%d",after); |
| } |
| |
| DFBuffer *content = DFBufferNew(); |
| for (int upto = 1; upto <= level; upto++) { |
| if (content->len == 0) |
| DFBufferFormat(content,"counter(h%d)",upto); |
| else |
| DFBufferFormat(content," \".\" counter(h%d)",upto); |
| } |
| DFBufferFormat(content," \" \""); |
| |
| CSSProperties *rule = CSSStyleRule(style); |
| CSSPut(rule,"counter-increment",style->elementName); |
| if (reset->len > 0) |
| CSSPut(rule,"counter-reset",reset->data); |
| CSSPut(CSSStyleBefore(style),"content",content->data); |
| style->latent = 0; |
| |
| DFBufferRelease(reset); |
| DFBufferRelease(content); |
| } |
| |
| static void disableNumberingForStyle(CSSStyle *style, int explicitly) |
| { |
| CSSProperties *rule = CSSStyleRule(style); |
| CSSProperties *before = CSSStyleBefore(style); |
| if (explicitly) { |
| // FIXME: Not covered by tests |
| char *increment = DFFormatString("h%d 0",style->headingLevel); |
| CSSPut(rule,"counter-increment",increment); |
| CSSPut(rule,"counter-reset","null"); |
| CSSPut(before,"content","\"\""); |
| style->latent = 0; |
| free(increment); |
| } |
| else { |
| CSSPut(rule,"counter-increment",NULL); |
| CSSPut(rule,"counter-reset",NULL); |
| CSSPut(before,"content",NULL); |
| } |
| } |
| |
| // FIXME: This won't work now for Word documents, where styles always have class names |
| // FIXME: Not covered by tests |
| void CSSSheetSetHeadingNumbering(CSSSheet *sheet, int enabled) |
| { |
| CSSStyle *defaultStyle = CSSSheetDefaultStyleForFamily(sheet,StyleFamilyParagraph); |
| |
| if (enabled) { |
| DFHashTable *stylesByHeadingLevel = getStylesByHeadingLevel(sheet); |
| for (int level = 1; level <= 6; level++) { |
| |
| StyleList *styles = DFHashTableLookupInt(stylesByHeadingLevel,level); |
| int haveClassless = 0; |
| |
| for (StyleList *item = styles; item != NULL; item = item->next) { |
| if (item->style->className == NULL) |
| haveClassless = 1; |
| } |
| |
| // If there exists a style at level n which has no class name, then that is the parent of all other |
| // heading styles. Set the numbering on that. Alternatively, if there no styles exist at level n, then |
| // create a new one with no class name, and set the numbering information on that. |
| if ((styles == NULL) || haveClassless) { |
| char *elementName = DFFormatString("h%d",level); |
| CSSStyle *style = CSSSheetLookupElement(sheet,elementName,NULL,1,0); |
| enableNumberingForStyle(style); |
| free(elementName); |
| } |
| else { |
| // There are multiple heading styles at level n, all of which have a class name associated with them. |
| // Set the numbering information on those which either have no parentId, or whose parentId is the |
| // default paragraph style. This caters for situations like the "HeadingXUnnumbered" styles we used |
| // to have, which were based on the corresponding "HeadingX" style. |
| for (StyleList *item = styles; item != NULL; item = item->next) { |
| char *parentSelector = CSSStyleCopyParent(item->style); |
| if ((parentSelector == NULL) || !strcmp(parentSelector,defaultStyle->selector)) { |
| enableNumberingForStyle(item->style); |
| } |
| else { |
| disableNumberingForStyle(item->style,1); |
| } |
| free(parentSelector); |
| } |
| } |
| |
| StyleList *next; |
| for (; styles != NULL; styles = next) { |
| next = styles->next; |
| CSSStyleRelease(styles->style); |
| free(styles); |
| } |
| } |
| DFHashTableRelease(stylesByHeadingLevel); |
| } |
| else { |
| // Clear all numbering information on all heading styles |
| const char **allSelectors = CSSSheetCopySelectors(sheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| CSSStyle *style = CSSSheetLookupSelector(sheet,allSelectors[i],0,0); |
| if (style->headingLevel > 0) { |
| CSSPut(CSSStyleRule(style),"counter-increment",NULL); |
| CSSPut(CSSStyleRule(style),"counter-reset",NULL); |
| CSSPut(CSSStyleBefore(style),"content",NULL); |
| } |
| } |
| free(allSelectors); |
| } |
| } |
| |
| // FIXME: This won't work now for Word documents, where styles always have class names |
| // FIXME: Not covered by tests |
| int CSSSheetIsNumberingUsed(CSSSheet *sheet) |
| { |
| CSSStyle *style = CSSSheetLookupElement(sheet,"body",NULL,0,0); |
| return (CSSGet(CSSStyleRule(style),"counter-reset") != NULL); |
| } |
| |
| int CSSStyleIsNumbered(CSSStyle *style) |
| { |
| const char *beforeContent = CSSGet(CSSStyleBefore(style),"content"); |
| return ((beforeContent != NULL) && !DFStringEquals(beforeContent,"\"\"")); |
| } |
| |
| CSSStyle *CSSSheetGetStyleParent(CSSSheet *sheet, CSSStyle *style) |
| { |
| char *parentSelector = CSSStyleCopyParent(style); |
| if (parentSelector == NULL) |
| return NULL;; |
| |
| CSSStyle *parent = CSSSheetLookupSelector(sheet,parentSelector,0,0); |
| free(parentSelector); |
| return parent; |
| } |
| |
| CSSStyle *CSSSheetParentOfStyle(CSSSheet *sheet, CSSStyle *style) |
| { |
| // FIXME: Not covered by tests |
| if (style == NULL) |
| return NULL;; |
| |
| CSSStyle *parent = CSSSheetGetStyleParent(sheet,style); |
| if (parent != NULL) |
| return parent; |
| |
| if (CSSSelectorHasClassName(style->selector)) { |
| char *elementName = CSSSelectorCopyElementName(style->selector); |
| parent = CSSSheetLookupSelector(sheet,elementName,0,0); |
| free(elementName); |
| if (parent != NULL) |
| return parent; |
| } |
| |
| return NULL; |
| } |
| |
| static int CSSIsBuiltinSelector2(Tag tag, const char *elementName, const char *className) |
| { |
| switch (tag) { |
| case HTML_P: |
| case HTML_H1: |
| case HTML_H2: |
| case HTML_H3: |
| case HTML_H4: |
| case HTML_H5: |
| case HTML_H6: |
| case HTML_BLOCKQUOTE: |
| case HTML_PRE: |
| case HTML_BODY: |
| case HTML_FIGURE: |
| case HTML_FIGCAPTION: |
| case HTML_TABLE: |
| case HTML_CAPTION: |
| return (className == NULL); |
| case HTML_NAV: |
| if ((className != NULL) && |
| (!strcmp(className,"tableofcontents") || |
| !strcmp(className,"listoffigures") || |
| !strcmp(className,"listoftables"))) |
| return 1; |
| break; |
| default: |
| break; |
| } |
| |
| if ((elementName != NULL) && !strcmp(elementName,"@page")) |
| return 1; |
| |
| return 0; |
| } |
| |
| int CSSIsBuiltinSelector(const char *selector) |
| { |
| Tag tag = CSSSelectorGetTag(selector); |
| char *elementName = CSSSelectorCopyElementName(selector); |
| char *className = CSSSelectorCopyClassName(selector); |
| int result = CSSIsBuiltinSelector2(tag,elementName,className); |
| free(elementName); |
| free(className); |
| return result; |
| } |