| // 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 "CSSClassNames.h" |
| #include "CSS.h" |
| #include "CSSProperties.h" |
| #include "CSSStyle.h" |
| #include "CSSSheet.h" |
| #include "DFDOM.h" |
| #include "DFXML.h" |
| #include "DFNameMap.h" |
| #include "DFString.h" |
| #include "DFHashTable.h" |
| #include "DFHTML.h" |
| #include "WordStyles.h" |
| #include "Word.h" |
| #include "DFCommon.h" |
| #include "DFPlatform.h" |
| #include <stdlib.h> |
| #include <string.h> |
| |
| static const char *classPrefixForElementName(const char *elementName); |
| |
| static void ensureStylesReferencedFromNode(DFNode *node, CSSSheet *styleSheet) |
| { |
| switch (node->tag) { |
| case HTML_H1: |
| case HTML_H2: |
| case HTML_H3: |
| case HTML_H4: |
| case HTML_H5: |
| case HTML_H6: |
| case HTML_P: |
| case HTML_FIGURE: { |
| char *selector = CSSMakeNodeSelector(node); |
| CSSSheetLookupSelector(styleSheet,selector,1,0); |
| free(selector); |
| break; |
| } |
| case HTML_A: { |
| if (HTML_nodeIsHyperlink(node)) { |
| CSSStyle *style = CSSSheetLookupElement(styleSheet,"span","Hyperlink",0,0); |
| if (style == NULL) { |
| style = CSSSheetLookupElement(styleSheet,"span","Hyperlink",1,0); |
| CSSPut(CSSStyleRule(style),"color","#0000FF"); |
| CSSSetUnderline(CSSStyleRule(style),1); |
| |
| CSSStyle *parent = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyCharacter); |
| if (parent != NULL) |
| CSSStyleSetParent(style,parent->selector); |
| } |
| } |
| break; |
| } |
| case HTML_TABLE: { |
| const char *className = DFGetAttribute(node,HTML_CLASS); |
| if (className == NULL) { |
| CSSStyle *dflt = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyTable); |
| int changed = 0; |
| CSSStyle *grid = WordSetupTableGridStyle(styleSheet,&changed); |
| DFSetAttribute(node,HTML_CLASS,grid->className); |
| |
| // Copy over cell padding rules. These are not automatically inherited in Word, and must be |
| // explicitly duplicated for each descendant of the TableNormal style. |
| |
| // Under normal circumstances, when the user has added a table in the editor, the TableGrid |
| // style will already be defined, with the appropriate padding rules set in the document's |
| // stylesheet, and StyleSheet.removeRedundantProperties will make sure that each table style |
| // keeps the padding properties. However if we are creating a HTML file that was not |
| // oringally created with the intention of being converted to a word document, the TableGrid |
| // style will not exist, and must be created. In this case (i.e. where changed is true) we |
| // have to explicitly copy these properties over. |
| |
| if (changed) { |
| CSSPut(CSSStyleCell(grid),"padding-left",CSSGet(CSSStyleCell(dflt),"padding-left")); |
| CSSPut(CSSStyleCell(grid),"padding-right",CSSGet(CSSStyleCell(dflt),"padding-right")); |
| CSSPut(CSSStyleCell(grid),"padding-top",CSSGet(CSSStyleCell(dflt),"padding-top")); |
| CSSPut(CSSStyleCell(grid),"padding-bottom",CSSGet(CSSStyleCell(dflt),"padding-bottom")); |
| } |
| } |
| break; |
| } |
| } |
| for (DFNode *child = node->first; child != NULL; child = child->next) |
| ensureStylesReferencedFromNode(child,styleSheet); |
| } |
| |
| void CSSEnsureReferencedStylesPresent(DFDocument *htmlDoc, CSSSheet *styleSheet) |
| { |
| CSSSheetLookupSelector(styleSheet,"p",1,0); |
| ensureStylesReferencedFromNode(htmlDoc->root,styleSheet); |
| } |
| |
| static void addHTMLDefaults(CSSStyle *style, CSSStyle *parent) |
| { |
| if (style->headingLevel > 0) { |
| const char *fontWeight = NULL; |
| const char *fontSize = NULL; |
| |
| switch (style->tag) { |
| case HTML_H1: |
| fontWeight = "bold"; |
| fontSize = "24pt"; |
| break; |
| case HTML_H2: |
| fontWeight = "bold"; |
| fontSize = "18pt"; |
| break; |
| case HTML_H3: |
| fontWeight = "bold"; |
| fontSize = "14pt"; |
| break; |
| case HTML_H4: |
| fontWeight = "bold"; |
| fontSize = "12pt"; |
| break; |
| case HTML_H5: |
| fontWeight = "bold"; |
| fontSize = "10pt"; |
| break; |
| case HTML_H6: |
| fontWeight = "bold"; |
| fontSize = "8pt"; |
| break; |
| } |
| |
| CSSProperties *properties = CSSStyleRule(style); |
| if ((CSSGet(properties,"font-weight") == NULL) && ((parent == NULL) || (CSSGet(CSSStyleRule(parent),"font-weight") == NULL))) |
| CSSPut(properties,"font-weight",fontWeight); |
| if ((CSSGet(properties,"font-size") == NULL) && ((parent == NULL) || (CSSGet(CSSStyleRule(parent),"font-size") == NULL))) |
| CSSPut(properties,"font-size",fontSize); |
| // FIXME: This likely differs between languages |
| // FIXME: Not covered by tests |
| char *styleNext = CSSStyleCopyNext(style); |
| char *parentNext = (parent != NULL) ? CSSStyleCopyNext(parent) : NULL; |
| if ((styleNext == NULL) && ((parent == NULL) || (parentNext == NULL))) |
| CSSStyleSetNext(style,"p.Normal"); |
| free(styleNext); |
| free(parentNext); |
| } |
| } |
| |
| void CSSSetHTMLDefaults(CSSSheet *styleSheet) |
| { |
| // For any heading elements that have an unnamed style declaration, set the default HTML properties on that. |
| // All named style declarations inherit from these. |
| |
| // For any heading elements that have only named style declarations, set the default HTML properties on each |
| // of them individually. |
| |
| for (int i = 1; i <= 6; i++) { |
| char *elementName = DFFormatString("h%d",i); |
| CSSStyle *style = CSSSheetLookupElement(styleSheet,elementName,NULL,0,0); |
| if (style != NULL) |
| addHTMLDefaults(style,NULL); |
| free(elementName); |
| } |
| |
| const char **allSelectors = CSSSheetCopySelectors(styleSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| const char *selector = allSelectors[i]; |
| |
| if (CSSSelectorIsHeading(selector) && (CSSSelectorHasClassName(selector))) { |
| char *elementName = CSSSelectorCopyElementName(selector); |
| |
| CSSStyle *namedStyle = CSSSheetLookupSelector(styleSheet,selector,0,0); |
| CSSStyle *unnamedStyle = CSSSheetLookupSelector(styleSheet,elementName,0,0); |
| CSSStyle *flattened = NULL; |
| CSSStyle *parent = CSSSheetGetStyleParent(styleSheet,namedStyle); |
| if (parent != NULL) |
| flattened = CSSSheetFlattenedStyle(styleSheet,parent); |
| |
| if (unnamedStyle == NULL) |
| addHTMLDefaults(namedStyle,flattened); |
| |
| CSSStyleRelease(flattened); |
| free(elementName); |
| } |
| } |
| free(allSelectors); |
| } |
| |
| static void setMissingDisplayNames(CSSSheet *styleSheet) |
| { |
| const char **allSelectors = CSSSheetCopySelectors(styleSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| CSSStyle *style = CSSSheetLookupSelector(styleSheet,allSelectors[i],0,0); |
| char *className = NULL; |
| char *displayName = CSSStyleCopyDisplayName(style); |
| if ((style->headingLevel > 0) && (displayName == NULL)) { |
| const char *defaultClass = classPrefixForElementName(style->elementName); |
| |
| if ((style->className != NULL) && (defaultClass != NULL) && DFStringHasPrefix(style->className,defaultClass)) |
| className = DFSubstring(style->className,strlen(defaultClass),strlen(style->className)); |
| else |
| className = DFStrDup(style->className); |
| |
| if ((className != NULL) && strlen(className) > 0) |
| displayName = DFFormatString("Heading %d (%s)",style->headingLevel,className); |
| else |
| displayName = DFFormatString("heading %d",style->headingLevel); |
| CSSStyleSetDisplayName(style,displayName); |
| } |
| free(displayName); |
| free(className); |
| } |
| free(allSelectors); |
| } |
| |
| static void setParentForHeadings(CSSSheet *styleSheet, DFHashTable *repls) |
| { |
| const char **allSelectors = CSSSheetCopySelectors(styleSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| const char *selector = allSelectors[i]; |
| if (!CSSSelectorIsHeading(selector)) |
| continue; |
| |
| CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,0,0); |
| const char *defaultClass = classPrefixForElementName(style->elementName); |
| if (defaultClass == NULL) |
| continue; |
| |
| CSSStyle *parentStyle = CSSSheetLookupElement(styleSheet,style->elementName,defaultClass,0,0); |
| if ((parentStyle != NULL) && (parentStyle != style)) |
| CSSStyleSetParent(style,parentStyle->selector); |
| } |
| free(allSelectors); |
| } |
| |
| static const char *classPrefixForElementName(const char *elementName) |
| { |
| if (!strcmp(elementName,"h1")) |
| return "heading_1"; |
| else if (!strcmp(elementName,"h2")) |
| return "heading_2"; |
| else if (!strcmp(elementName,"h3")) |
| return "heading_3"; |
| else if (!strcmp(elementName,"h4")) |
| return "heading_4"; |
| else if (!strcmp(elementName,"h5")) |
| return "heading_5"; |
| else if (!strcmp(elementName,"h6")) |
| return "heading_6"; |
| else |
| return elementName; |
| } |
| |
| static void determineReplacements(CSSSheet *styleSheet, DFHashTable *repls) |
| { |
| // char * -> linked list of SelectorList nodes |
| DFHashTable *selectorsByClassName = DFHashTableNew(NULL,NULL); |
| |
| const char **allSelectors = CSSSheetCopySelectors(styleSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| const char *selector = allSelectors[i]; |
| char *className = CSSSelectorCopyClassName(selector); |
| if (className == NULL) |
| className = xstrdup("");; |
| |
| SelectorList *item = (SelectorList *)xcalloc(1,sizeof(SelectorList)); |
| item->selector = xstrdup(selector); |
| item->next = DFHashTableLookup(selectorsByClassName,className); |
| DFHashTableAdd(selectorsByClassName,className,item); |
| free(className); |
| } |
| free(allSelectors); |
| |
| const char **allClasses = DFHashTableCopyKeys(selectorsByClassName); |
| for (int i = 0; allClasses[i]; i++) { |
| const char *className = allClasses[i]; |
| SelectorList *list = DFHashTableLookup(selectorsByClassName,className); |
| SelectorList *start = list; |
| SelectorList *next; |
| for (; list != NULL; list = next) { |
| next = list->next; |
| const char *oldSelector = list->selector; |
| char *elementName = CSSSelectorCopyElementName(oldSelector); |
| int haveUnnamed = (CSSSheetLookupSelector(styleSheet,elementName,0,0) != NULL); |
| if (((start != NULL) && (start->next != NULL)) || !strcmp(className,"") || haveUnnamed) { |
| if (CSSSelectorIsHeading(oldSelector)) { |
| const char *prefix = classPrefixForElementName(elementName); |
| char *newSelector = DFFormatString("%s.%s%s",elementName,prefix,className); |
| DFHashTableAdd(repls,oldSelector,newSelector); |
| free(newSelector); |
| } |
| } |
| free(elementName); |
| } |
| |
| // Free selector list |
| for (list = start; list != NULL; list = next) { |
| next = list->next; |
| free(list->selector); |
| free(list); |
| } |
| } |
| free(allClasses); |
| DFHashTableRelease(selectorsByClassName); |
| |
| // Remove any "double replacements", where the new selector is itself a key in the hash table, e.g.: |
| // h1 -> h1.Heading1 -> h1.Heading1Heading1 |
| // |
| // This avoids a problem where a style is renamed twice, but the relevant nodes in the DOM tree are only updated |
| // to refer to the first replacement, not the second, causing an assertion failure in WordParagraphPut, which |
| // expects there to be a style for every selector it encounters during traversal. |
| const char **allOldSelectors = DFHashTableCopyKeys(repls); |
| for (int i = 0; allOldSelectors[i]; i++) { |
| const char *newSelector = DFHashTableLookup(repls,allOldSelectors[i]); |
| if (newSelector != NULL) // may have already been removed |
| DFHashTableRemove(repls,newSelector); |
| } |
| free(allOldSelectors); |
| } |
| |
| static void replaceSelectorsInNode(DFHashTable *repls, DFNode *node) |
| { |
| switch (node->tag) { |
| case HTML_H1: |
| case HTML_H2: |
| case HTML_H3: |
| case HTML_H4: |
| case HTML_H5: |
| case HTML_H6: { |
| const char *className = DFGetAttribute(node,HTML_CLASS); |
| char *oldSelector = CSSMakeTagSelector(node->tag,className); |
| const char *newSelector = DFHashTableLookup(repls,oldSelector); |
| |
| if (newSelector != NULL) { |
| char *className = CSSSelectorCopyClassName(newSelector); |
| DFSetAttribute(node,HTML_CLASS,className); |
| free(className); |
| } |
| |
| free(oldSelector); |
| break; |
| } |
| } |
| for (DFNode *child = node->first; child != NULL; child = child->next) |
| replaceSelectorsInNode(repls,child); |
| } |
| |
| static void replaceSelectorsInSheet(DFHashTable *repls, CSSSheet *styleSheet) |
| { |
| const char **allSelectors = CSSSheetCopySelectors(styleSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| const char *oldSelector = allSelectors[i]; |
| const char *newSelector = DFHashTableLookup(repls,oldSelector); |
| if ((newSelector == NULL) || !strcmp(newSelector,oldSelector)) |
| continue; |
| |
| CSSStyle *style = CSSSheetLookupSelector(styleSheet,oldSelector,0,0); |
| CSSStyle *existing = CSSSheetLookupSelector(styleSheet,newSelector,0,0); |
| |
| if (existing != NULL) { |
| CSSSheetRemoveStyle(styleSheet,style); |
| } |
| else { |
| // We must retain a reference to the style here, otherwise it will be deleted when removed from the sheet |
| CSSStyleRetain(style); |
| CSSSheetRemoveStyle(styleSheet,style); |
| CSSStyleSetSelector(style,newSelector); |
| CSSSheetAddStyle(styleSheet,style); |
| CSSStyleRelease(style); |
| } |
| } |
| free(allSelectors); |
| |
| // FIXME: Not covered by tests |
| allSelectors = CSSSheetCopySelectors(styleSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| CSSStyle *style = CSSSheetLookupSelector(styleSheet,allSelectors[i],0,0); |
| |
| char *parentSelector = CSSStyleCopyParent(style); |
| if ((parentSelector != NULL) && (DFHashTableLookup(repls,parentSelector) != NULL)) |
| CSSStyleSetParent(style,DFHashTableLookup(repls,parentSelector)); |
| free(parentSelector); |
| |
| char *nextSelector = CSSStyleCopyNext(style); |
| if ((nextSelector != NULL) && (DFHashTableLookup(repls,nextSelector) != NULL)) |
| CSSStyleSetNext(style,DFHashTableLookup(repls,nextSelector)); |
| free(nextSelector); |
| } |
| free(allSelectors); |
| } |
| |
| void CSSEnsureUnique(CSSSheet *styleSheet, DFDocument *htmlDoc, int creating) |
| { |
| DFHashTable *repls = DFHashTableNew((DFCopyFunction)xstrdup,free); |
| determineReplacements(styleSheet,repls); |
| replaceSelectorsInSheet(repls,styleSheet); |
| replaceSelectorsInNode(repls,htmlDoc->root); |
| setMissingDisplayNames(styleSheet); |
| setParentForHeadings(styleSheet,repls); |
| DFHashTableRelease(repls); |
| } |