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; }
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 *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; }
CSSSheet *WordParseStyles(WordConverter *converter) { CSSSheet *styleSheet = CSSSheetNew(); CSSStyle *bodyStyle = CSSSheetLookupElement(styleSheet,"body",NULL,1,0); CSSPut(CSSStyleRule(bodyStyle),"counter-reset","h1 h2 h3 h4 h5 h6 figure table"); parseBody(converter,styleSheet); if (converter->package->styles == NULL) return styleSheet;; DFNode *root = converter->package->styles->root; if (root == NULL) return styleSheet; if (root->tag != WORD_STYLES) return styleSheet;; WordSheet *sheet = converter->styles; const char **allIdents = WordSheetCopyIdents(sheet); for (int i = 0; allIdents[i]; i++) { WordStyle *wordStyle = WordSheetStyleForIdent(sheet,allIdents[i]); if ((wordStyle->selector == NULL) || WordStyleIsProtected(wordStyle)) continue; CSSStyle *style = CSSSheetLookupSelector(styleSheet,wordStyle->selector,1,0); styleParse(wordStyle,converter,style); const char *defaultVal = DFGetAttribute(wordStyle->element,WORD_DEFAULT); if ((defaultVal != NULL) && Word_parseOnOff(defaultVal)) { StyleFamily family = WordStyleFamilyForSelector(style->selector); CSSSheetSetDefaultStyle(styleSheet,style,family); CSSSetDefault(CSSStyleRule(style),1); if (family == StyleFamilyParagraph) fixParagraphSpacing(style,wordStyle->element); } } free(allIdents); DFNode *docDefaults = DFChildWithTag(root,WORD_DOCDEFAULTS); DFNode *pPrDefault = DFChildWithTag(docDefaults,WORD_PPRDEFAULT); DFNode *pPr = DFChildWithTag(pPrDefault,WORD_PPR); if (pPr != NULL) { CSSStyle *body = CSSSheetLookupElement(styleSheet,"body",NULL,1,0); const char *styleId = NULL; WordGetPPr(pPr,CSSStyleRule(body),&styleId,converter->mainSection); } // Special case for figure style: set left and right margin to auto, if not already set CSSStyle *figure = CSSSheetLookupElement(styleSheet,"figure",NULL,0,0); if (figure != NULL) { if (CSSGet(CSSStyleRule(figure),"margin-left") == NULL) CSSPut(CSSStyleRule(figure),"margin-left","auto"); if (CSSGet(CSSStyleRule(figure),"margin-right") == NULL) CSSPut(CSSStyleRule(figure),"margin-right","auto"); } return styleSheet; }
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); }
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 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); }
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; }
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; }
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; }
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 CellPadding getPadding(CSSSheet *styleSheet, WordStyle *wordStyle, CSSProperties *cellProperties) { const char *paddingLeftStr = NULL; const char *paddingRightStr = NULL; CSSStyle *style; if ((wordStyle != NULL) && (wordStyle->selector != NULL)) { style = CSSSheetLookupSelector(styleSheet,wordStyle->selector,0,0); } else { style = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyTable); } if (style != NULL) { paddingLeftStr = CSSGet(CSSStyleCell(style),"padding-left"); paddingRightStr = CSSGet(CSSStyleCell(style),"padding-right"); } if (CSSGet(cellProperties,"padding-left") != NULL) paddingLeftStr = CSSGet(cellProperties,"padding-left"); if (CSSGet(cellProperties,"padding-right") != NULL) paddingRightStr = CSSGet(cellProperties,"padding-right");; CellPadding padding; padding.leftPts = 0; padding.rightPts = 0; CSSLength paddingLeftLength = CSSLengthFromString(paddingLeftStr); if (CSSLengthIsValid(paddingLeftLength) && (paddingLeftLength.units == UnitsPt)) padding.leftPts = paddingLeftLength.value;; CSSLength paddingRightLength = CSSLengthFromString(paddingRightStr); if (CSSLengthIsValid(paddingRightLength) && (paddingRightLength.units == UnitsPt)) padding.rightPts = paddingRightLength.value;; return padding; }
void WordUpdateStyles(WordConverter *converter, CSSSheet *styleSheet) { CSSStyle *paraDefault = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyParagraph); if (CSSGet(CSSStyleRule(paraDefault),"margin-top") == NULL) CSSPut(CSSStyleRule(paraDefault),"margin-top","-word-auto"); if (CSSGet(CSSStyleRule(paraDefault),"margin-bottom") == NULL) CSSPut(CSSStyleRule(paraDefault),"margin-bottom","-word-auto"); if (converter->package->styles == NULL) // FIXME: create this document return;; DFNode *root = converter->package->styles->root; if ((root == NULL) || (root->tag != WORD_STYLES)) return;; DFHashTable *remainingSelectors = DFHashTableNew(NULL,NULL); // Used as a set const char **allSelectors = CSSSheetCopySelectors(styleSheet); for (int i = 0; allSelectors[i]; i++) { const char *selector = allSelectors[i]; DFHashTableAdd(remainingSelectors,selector,""); } free(allSelectors); WordSheet *sheet = converter->styles; DFHashTable *oldConcreteNumIds = WordSheetFindUsedConcreteNumIds(sheet); updateNumbering(converter,styleSheet); // Update or remove existing styles const char **allIdents = WordSheetCopyIdents(sheet); for (int i = 0; allIdents[i]; i++) { WordStyle *wordStyle = WordSheetStyleForIdent(sheet,allIdents[i]); DFNode *element = wordStyle->element; if (WordStyleIsProtected(wordStyle)) { DFHashTableRemove(remainingSelectors,wordStyle->selector); continue; } if (!DFStringEquals(wordStyle->type,"paragraph") && !DFStringEquals(wordStyle->type,"character") && !DFStringEquals(wordStyle->type,"table")) continue; CSSStyle *cssStyle = CSSSheetLookupSelector(styleSheet,wordStyle->selector,0,0); if (cssStyle == NULL) { // Remove style WordSheetRemoveStyle(sheet,wordStyle); continue; } // Update style WordPutStyle(element,cssStyle,converter); updateDefault(cssStyle,element,styleSheet,converter); DFHashTableRemove(remainingSelectors,wordStyle->selector); } free(allIdents); // Sort the list of new styles, so that test output is deterministic const char **sortedSelectors = DFHashTableCopyKeys(remainingSelectors); DFSortStringsCaseInsensitive(sortedSelectors); // Add new styles. We do this in two stages - first creating the styles, and then setting their properties. // This is because the second stage depends on referenced styles (e.g. based on and next) to be already // present. for (int selIndex = 0; sortedSelectors[selIndex]; selIndex++) { const char *selector = sortedSelectors[selIndex]; CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,0,0); const char *familyStr = NULL; StyleFamily family = WordStyleFamilyForSelector(selector); if (family == StyleFamilyParagraph) familyStr = "paragraph"; else if (family == StyleFamilyCharacter) familyStr = "character"; else if (family == StyleFamilyTable) familyStr = "table"; else continue; char *styleId = WordStyleIdForStyle(style); char *name = WordStyleNameForStyle(style); if (name == NULL) name = xstrdup(styleId);; WordStyle *wordStyle = WordSheetAddStyle(sheet,familyStr,styleId,name,selector); DFCreateChildElement(wordStyle->element,WORD_QFORMAT); free(styleId); free(name); } for (int selIndex = 0; sortedSelectors[selIndex]; selIndex++) { const char *selector = sortedSelectors[selIndex]; StyleFamily family = WordStyleFamilyForSelector(selector); if ((family != StyleFamilyParagraph) && (family != StyleFamilyCharacter) && (family != StyleFamilyTable)) continue; CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,0,0); WordStyle *wordStyle = WordSheetStyleForSelector(converter->styles,selector); assert(wordStyle != NULL); CSSStyleAddDefaultHTMLProperties(style); // FIXME: language // FIXME: not covered by tests if ((style->headingLevel >= 1) && (style->headingLevel <= 6)) CSSStyleSetNext(style,"p.Normal"); WordPutStyle(wordStyle->element,style,converter); updateDefault(style,wordStyle->element,styleSheet,converter); } free(sortedSelectors); // Update body style (document defaults) updateDefaults(converter,styleSheet); updateBody(converter,styleSheet); DFHashTable *newConcreteNumIds = WordSheetFindUsedConcreteNumIds(sheet); const char **oldKeys = DFHashTableCopyKeys(oldConcreteNumIds); for (int oldIndex = 0; oldKeys[oldIndex]; oldIndex++) { const char *numId = oldKeys[oldIndex]; if (DFHashTableLookup(newConcreteNumIds,numId) == NULL) { WordConcreteNum *concreteNum = WordNumberingConcreteWithId(converter->numbering,numId); if (concreteNum != NULL) WordNumberingRemoveConcrete(converter->numbering,concreteNum); } } free(oldKeys); DFHashTableRelease(remainingSelectors); DFHashTableRelease(oldConcreteNumIds); DFHashTableRelease(newConcreteNumIds); }
// 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); } }