static void WordPutNumPr(DFNode *concrete, CSSProperties *newp) { DFNode *children[PREDEFINED_TAG_COUNT]; childrenToArray(concrete,children); CSSProperties *oldp = CSSPropertiesNew(); WordGetNumPr(concrete,oldp); if (!DFStringEquals(CSSGet(oldp,"-word-numId"),CSSGet(newp,"-word-numId")) || !DFStringEquals(CSSGet(oldp,"-word-ilvl"),CSSGet(newp,"-word-ilvl"))) { if (CSSGet(newp,"-word-numId") != NULL) { children[WORD_NUMID] = DFCreateElement(concrete->doc,WORD_NUMID); DFSetAttribute(children[WORD_NUMID],WORD_VAL,CSSGet(newp,"-word-numId")); if (CSSGet(newp,"-word-ilvl") != NULL) { children[WORD_ILVL] = DFCreateElement(concrete->doc,WORD_ILVL); DFSetAttribute(children[WORD_ILVL],WORD_VAL,CSSGet(newp,"-word-ilvl")); } else { children[WORD_ILVL] = NULL; } } else { children[WORD_NUMID] = NULL; children[WORD_ILVL] = NULL; } } replaceChildrenFromArray(concrete,children,WordNumPr_Children); CSSPropertiesRelease(oldp); }
int identicalAttributesExcept(DFNode *first, DFNode *second, Tag except) { // FIXME: except paremeter is ignored // This is O(n^2), but it's not really a problem as we generally only have a very small // number of attributes for (unsigned int i = 0; i < first->attrsCount; i++) { Tag tag = first->attrs[i].tag; if (tag == HTML_ID) continue; const char *firstValue = first->attrs[i].value; const char *secondValue = DFGetAttribute(second,tag); if (!DFStringEquals(firstValue,secondValue)) return 0; } for (unsigned int i = 0; i < second->attrsCount; i++) { Tag tag = second->attrs[i].tag; if (tag == HTML_ID) continue; const char *secondValue = second->attrs[i].value; const char *firstValue = DFGetAttribute(first,tag); if (!DFStringEquals(secondValue,firstValue)) return 0; } return 1; }
static DFNode *WordParagraphContentCreate(WordPutData *put, DFNode *abstract) { switch (abstract->tag) { case HTML_SPAN: { const char *spanClass = DFGetAttribute(abstract,HTML_CLASS); if (DFStringEquals(spanClass,DFFieldClass)) { DFNode *concrete = DFCreateElement(put->contentDoc,WORD_FLDSIMPLE); char *nodeText = DFNodeTextToString(abstract); DFSetAttribute(concrete,WORD_INSTR,nodeText); free(nodeText); put->conv->haveFields = 1; return concrete; } else if (DFStringEquals(spanClass,DFBookmarkClass)) { const char *bookmarkId = DFGetAttribute(abstract,WORD_ID); const char *bookmarkName = DFGetAttribute(abstract,WORD_NAME); if ((bookmarkId == NULL) || (bookmarkName == NULL)) return NULL;; WordBookmark *bookmark = WordObjectsBookmarkWithName(put->conv->objects,bookmarkName); if (bookmark == NULL) return NULL;; DFNode *concrete = DFCreateElement(put->contentDoc,WORD_BOOKMARK); DFSetAttribute(concrete,WORD_ID,bookmarkId); DFSetAttribute(concrete,WORD_NAME,bookmarkName); bookmark->element = concrete; WordParagraphContentPut(put,abstract,concrete); return concrete; } else { DFNode *concrete = DFCreateElement(put->contentDoc,WORD_R); WordParagraphContentPut(put,abstract,concrete); return concrete; } } case HTML_INS: case HTML_DEL: return WordChangeLens.create(put,abstract); case HTML_A: { const char *href = DFGetAttribute(abstract,HTML_HREF); if (href != NULL) { if (HTML_nodeIsHyperlink(abstract)) return WordHyperlinkLens.create(put,abstract); else return WordFieldLens.create(put,abstract); } return NULL; } default: return NULL; } }
static void updateListTypes(WordPutData *put) { const char **htmlIds = DFHashTableCopyKeys(put->numIdByHtmlId); for (int i = 0; htmlIds[i]; i++) { const char *htmlId = htmlIds[i]; const char *numId = DFHashTableLookup(put->numIdByHtmlId,htmlId); WordConcreteNum *num = WordNumberingConcreteWithId(put->conv->numbering,numId); if (num == NULL) continue; // FIXME: remove entry from both maps so it is re-created DFNode *listNode = DFNodeForSeqNo(put->conv->html,(unsigned int)atoi(htmlId)); assert(listNode != NULL); const char *htmlType = DFGetAttribute(listNode,CONV_LISTTYPE); const char *htmlIlvl = DFGetAttribute(listNode,CONV_ILVL); WordNumLevel *level = WordConcreteNumGetLevel(num,atoi(htmlIlvl)); if (level == NULL) continue; // FIXME: remove entry from both maps so it is re-created const char *wordType = WordNumLevelToListStyleType(level); if (!DFStringEquals(wordType,htmlType)) { // Make a copy of numId, as it may be freed during the first call to DFHashTableRemove char *numIdCopy = strdup(numId); DFHashTableRemove(put->numIdByHtmlId,htmlId); DFHashTableRemove(put->htmlIdByNumId,numIdCopy); free(numIdCopy); if (num->referenceCount == 1) WordNumberingRemoveConcrete(put->conv->numbering,num); } } free(htmlIds); }
// Finds the type of measurement used for cell widths in a table. If the table is empty, or // the cells use different types of widths, this returns NULL. static const char *cellWidthTypeForTable(DFNode *tbl) { const char *commonType = NULL; for (DFNode *tr = tbl->first; tr != NULL; tr = tr->next) { if (tr->tag != WORD_TR) continue; for (DFNode *tc = tr->first; tc != NULL; tc = tc->next) { if (tc->tag != WORD_TC) continue; DFNode *tcPr = DFChildWithTag(tc,WORD_TCPR); DFNode *tcW = DFChildWithTag(tcPr,WORD_TCW); if (tcW != NULL) { const char *type = DFGetAttribute(tcW,WORD_TYPE); if (type == NULL) type = "dxa"; if (commonType == NULL) // First cell we've encountered commonType = type; else if (!DFStringEquals(commonType,type)) return NULL; } } } return commonType; }
static void removeCaptionBookmarksRecursive(DFNode *node, int inCaption) { if (node->tag == WORD_P) { DFNode *pPr = DFChildWithTag(node,WORD_PPR); const char *styleId = DFGetChildAttribute(pPr,WORD_PSTYLE,WORD_VAL); if (DFStringEquals(styleId,"Caption")) inCaption = 1; } DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; removeCaptionBookmarksRecursive(child,inCaption); } if (inCaption) { switch (node->tag) { case WORD_BOOKMARKSTART: case WORD_BOOKMARKEND: case WORD_BOOKMARK: DFRemoveNodeButKeepChildren(node); break; } } }
static void findTargetAndType(WordBookmark *bookmark, WordSheet *sheet) { if (DFStringEquals(bookmark->bookmarkName,"_GoBack")) { bookmark->type = WordBookmarkCursor; bookmark->target = NULL; return; } // FIXME: Check if the following line is still relevant with the new bookmarks model assert(bookmark->element != NULL); DFNode *p = WordFindContainingParagraph(bookmark->element); if (p == NULL) return; DFNode *pPr = DFChildWithTag(p,WORD_PPR); if (pPr == NULL) return; DFNode *pStyle = DFChildWithTag(pPr,WORD_PSTYLE); if (pStyle == NULL) return; const char *styleId = DFGetAttribute(pStyle,WORD_VAL); if (styleId == NULL) return; WordStyle *style = WordSheetStyleForTypeId(sheet,"paragraph",styleId); if ((style != NULL) && isHeadingOutlineLvl(style->outlineLvl)) { bookmark->type = WordBookmarkHeading; bookmark->target = p; } else if (DFStringEquals(styleId,"Caption")) { DFNode *prev = findPreviousElement(p); if (prev == NULL) return; if (prev->tag == WORD_TBL) { bookmark->type = WordBookmarkTable; bookmark->target = prev; } else if (Word_isFigureParagraph(prev)) { bookmark->type = WordBookmarkFigure; bookmark->target = prev; } else if (Word_isEquationParagraph(prev)) { bookmark->type = WordBookmarkEquation; bookmark->target = prev; } } }
static void simplifyRecursive(WordSimplification *simp, DFNode *node) { switch (node->tag) { case WORD_FLDCHAR: { const char *type = DFGetAttribute(node,WORD_FLDCHARTYPE); if (DFStringEquals(type,"begin")) { if (simp->depth == 0) { DFBufferRelease(simp->instrText); simp->instrText = DFBufferNew(); simp->beginNode = node; simp->endNode = NULL; simp->inSeparate = 0; } simp->depth++; } else if (DFStringEquals(type,"end") && (simp->depth > 0)) { simp->depth--; if (simp->depth == 0) { simp->endNode = node; replaceField(simp); } } else if (DFStringEquals(type,"separate")) { if (simp->depth == 1) simp->inSeparate = 1; } break; } case WORD_INSTRTEXT: { if ((simp->depth == 1) && !simp->inSeparate) { char *value = DFNodeTextToString(node); DFBufferFormat(simp->instrText,"%s",value); free(value); } break; } } DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; simplifyRecursive(simp,child); } }
static DFNode *WordRunContentGet(WordGetData *get, DFNode *concrete) { switch (concrete->tag) { case WORD_T: case WORD_DELTEXT: { DFBuffer *buf = DFBufferNew(); DFNodeTextToBuffer(concrete,buf); DFNode *abstract = DFCreateTextNode(get->conv->html,buf->data); DFBufferRelease(buf); return abstract; } case WORD_DRAWING: case WORD_OBJECT: case WORD_PICT: return WordDrawingGet(get,concrete); case WORD_TAB: { DFNode *span = WordConverterCreateAbstract(get,HTML_SPAN,concrete); DFSetAttribute(span,HTML_CLASS,DFTabClass); return span; } case WORD_BR: { const char *type = DFGetAttribute(concrete,WORD_TYPE); if (DFStringEquals(type,"column")) { DFNode *span = WordConverterCreateAbstract(get,HTML_SPAN,concrete); DFSetAttribute(span,HTML_CLASS,DFPlaceholderClass); DFCreateChildTextNode(span,"[Column break]"); return span; } else if (DFStringEquals(type,"page")) { DFNode *span = WordConverterCreateAbstract(get,HTML_SPAN,concrete); DFSetAttribute(span,HTML_CLASS,DFPlaceholderClass); DFCreateChildTextNode(span,"[Page break]"); return span; } else { return WordConverterCreateAbstract(get,HTML_BR,concrete); } } default: return NULL; } }
static void Word_addContentParts(DFNode *child, const char *content, WordCaption *caption) { if (content == NULL) return; DFNode *nextSibling = child->first; DFArray *parts = CSSParseContent(content); for (int i = 0; i < DFArrayCount(parts); i++) { ContentPart *part = DFArrayItemAt(parts,i); switch (part->type) { case ContentPartString: { DFNode *text = DFCreateTextNode(child->doc,part->value); if (strlen(part->value) > 0) { DFNode *span = DFCreateElement(child->doc,HTML_SPAN); DFAppendChild(span,text); DFInsertBefore(child,span,nextSibling); } break; } case ContentPartCounter: { if (DFStringEquals(part->value,"figure")) { DFNode *span = DFCreateElement(child->doc,HTML_SPAN); DFSetAttribute(span,HTML_CLASS,DFFieldClass); DFCreateChildTextNode(span," SEQ Figure \\* ARABIC "); DFInsertBefore(child,span,nextSibling); caption->number = span; } else if (DFStringEquals(part->value,"table")) { DFNode *span = DFCreateElement(child->doc,HTML_SPAN); DFSetAttribute(span,HTML_CLASS,DFFieldClass); DFCreateChildTextNode(span," SEQ Table \\* ARABIC "); DFInsertBefore(child,span,nextSibling); caption->number = span; } break; default: break; } } } DFArrayRelease(parts); }
static int WordBookmarkIsVisible2(DFNode *concrete) { const char *name = DFGetAttribute(concrete,WORD_NAME); if (name == NULL) return 0; if (DFStringEquals(name,"_GoBack") && (concrete->first == NULL)) return 0; return 1; }
static int isSeqField(DFNode *node) { if (node->tag != HTML_SPAN) return 0; if (!DFStringEquals(DFGetAttribute(node,HTML_CLASS),DFFieldClass)) return 0; char *instr = DFNodeTextToString(node); const char **args = Word_parseField(instr); int result = (args[0] != NULL) && !strcmp(args[0],"SEQ"); free(args); free(instr); return result; }
static const char *bookmarkNameForHtmlId(WordConverter *converter, const char *htmlId, const char *refClass) { DFNode *htmlElem = DFElementForIdAttr(converter->html,htmlId); if (htmlElem == NULL) return NULL; switch (htmlElem->tag) { case HTML_H1: case HTML_H2: case HTML_H3: case HTML_H4: case HTML_H5: case HTML_H6: { DFNode *labelSpan = htmlElem->first; if ((labelSpan == NULL) || (labelSpan->tag != HTML_SPAN)) return NULL;; const char *labelClass = DFGetAttribute(labelSpan,HTML_CLASS); if (!DFStringEquals(labelClass,DFBookmarkClass)) return NULL; return DFGetAttribute(labelSpan,WORD_NAME); } case HTML_FIGURE: case HTML_TABLE: { WordCaption *caption = WordObjectsCaptionForTarget(converter->objects,htmlElem); if (caption == NULL) return NULL; if (DFStringEquals(refClass,DFRefTextClass) && (caption->textBookmark != NULL)) return caption->textBookmark->bookmarkName; else if (DFStringEquals(refClass,DFRefLabelNumClass) && (caption->labelNumBookmark != NULL)) return caption->labelNumBookmark->bookmarkName; else if (DFStringEquals(refClass,DFRefCaptionTextClass) && (caption->captionTextBookmark != NULL)) return caption->captionTextBookmark->bookmarkName; else if (caption->textBookmark != NULL) return caption->textBookmark->bookmarkName; // default is entire caption } default: return NULL; } }
static void populateTableStructure(DFTable *structure, DFNode *concrete) { // Populate table int row = 0; for (DFNode *tblChild = concrete->first; tblChild != NULL; tblChild = tblChild->next) { if (tblChild->tag != WORD_TR) continue; DFTableSetRowElement(structure,tblChild,row); int col = 0; for (DFNode *trChild = tblChild->first; trChild != NULL; trChild = trChild->next) { if (trChild->tag != WORD_TC) continue; const char *gridSpan = NULL; const char *vMerge = NULL; DFNode *tcPr = DFChildWithTag(trChild,WORD_TCPR); if (tcPr != NULL) { DFNode *gridSpanNode = DFChildWithTag(tcPr,WORD_GRIDSPAN); DFNode *vMergeNode = DFChildWithTag(tcPr,WORD_VMERGE); if (gridSpanNode != NULL) gridSpan = DFGetAttribute(gridSpanNode,WORD_VAL); if (vMergeNode != NULL) { vMerge = DFGetAttribute(vMergeNode,WORD_VAL); if (vMerge == NULL) vMerge = "continue"; } } int colSpan = (gridSpan != NULL) ? atoi(gridSpan) : 1; DFCell *cell; if ((vMerge != NULL) && !DFStringEquals(vMerge,"restart") && (row > 0)) { cell = DFCellRetain(DFTableGetCell(structure,row-1,col)); if (cell->rowSpan < row + 1 - cell->row) cell->rowSpan = row + 1 - cell->row; } else { cell = DFCellNew(trChild,row,col); cell->colSpan = colSpan; } for (int i = 0; i < colSpan; i++) DFTableSetCell(structure,row,col+i,cell); col += colSpan; DFCellRelease(cell); } row++; } }
static DFNode *WordRunContentCreate(WordPutData *put, DFNode *abstract) { switch (abstract->tag) { case DOM_TEXT: { DFNode *text = DFCreateTextNode(put->contentDoc,abstract->value); // Text inside a <w:del> element must be stored in a <w:delText> element // Text *not* inside a <w:del> element is stored in a <w:t> element Tag tag = WORD_T; for (DFNode *a = abstract->parent; a != NULL; a = a->parent) { if (a->tag == HTML_DEL) tag = WORD_DELTEXT; } DFNode *t = DFCreateElement(put->contentDoc,tag); DFAppendChild(t,text); char *trimmed = DFStringTrimWhitespace(abstract->value); if (!DFStringEquals(trimmed,abstract->value)) DFSetAttribute(t,XML_SPACE,"preserve"); free(trimmed); return t; } case HTML_IMG: return WordDrawingCreate(put,abstract); case HTML_BR: return DFCreateElement(put->contentDoc,WORD_BR); case HTML_SPAN: { const char *className = DFGetAttribute(abstract,HTML_CLASS); if (DFStringEquals(className,DFTabClass)) return DFCreateElement(put->contentDoc,WORD_TAB); return NULL; } default: return NULL; } }
static void WordFieldPut(WordPutData *put, DFNode *abstract, DFNode *concrete) { switch (abstract->tag) { case HTML_SPAN: { const char *className = DFGetAttribute(abstract,HTML_CLASS); if (!DFStringEquals(className,DFFieldClass)) return; char *text = DFNodeTextToString(abstract); DFSetAttribute(concrete,WORD_INSTR,text); free(text); break; } case HTML_A: { const char *href = DFGetAttribute(abstract,HTML_HREF); if ((href == NULL) || (href[0] != '#')) return;; const char *targetId = &href[1]; const char *className = DFGetAttribute(abstract,HTML_CLASS); if (className == NULL) className = "";; const char *bookmarkName = bookmarkNameForHtmlId(put->conv,targetId,className); if (bookmarkName == NULL) return;; DFNode *htmlElem = DFElementForIdAttr(put->conv->html,targetId); if ((htmlElem != NULL) && ((htmlElem->tag == HTML_TABLE) || (htmlElem->tag == HTML_FIGURE))) { if (!DFStringEquals(className,DFRefTextClass) && !DFStringEquals(className,DFRefLabelNumClass) && !DFStringEquals(className,DFRefCaptionTextClass)) className = DFRefTextClass; } if (DFStringEquals(className,DFRefTextClass) || DFStringEquals(className,DFRefLabelNumClass) || DFStringEquals(className,DFRefCaptionTextClass)) DFFormatAttribute(concrete,WORD_INSTR," REF %s \\h ",bookmarkName); else if (DFStringEquals(className,DFRefDirectionClass)) DFFormatAttribute(concrete,WORD_INSTR," REF %s \\p \\h ",bookmarkName); else DFFormatAttribute(concrete,WORD_INSTR," REF %s \\r \\h ",bookmarkName); break; } } }
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 Word_preProcessHTML(WordConverter *word, DFNode *node) { switch (node->tag) { case HTML_TABLE: case HTML_FIGURE: { DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; if ((child->tag != HTML_CAPTION) && (child->tag != HTML_FIGCAPTION)) continue; WordCaption *caption = WordCaptionNew(child); WordObjectsSetCaption(word->objects,caption,node); caption->contentStart = child->first; WordCaptionRelease(caption); const char *className = DFGetAttribute(child,HTML_CLASS); CSSStyle *style; if (child->tag == HTML_CAPTION) style = CSSSheetLookupElement(word->styleSheet,"caption",className,0,0); else style = CSSSheetLookupElement(word->styleSheet,"figcaption",className,0,0); CSSProperties *before = CSSStyleBefore(style); if (CSSGet(before,"content") != NULL) Word_addContentParts(child,CSSGet(before,"content"),caption); child->tag = HTML_P; DFSetAttribute(child,HTML_CLASS,"Caption"); DFInsertBefore(node->parent,child,node->next); Word_preProcessHTML(word,child); } // The HTML normalization process ensures that apart from the <figcaption> element, // all children of a <figure> are paragraphs or containers. Currently the editor only // lets you create figures that contain a single image, so it's always a single // paragraph. Since the HTML <figure> element gets mapped to a single <w:p> element // by WordParagraphLens, we want to make sure it only contains inline children. for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; if (HTML_isParagraphTag(child->tag)) DFRemoveNodeButKeepChildren(child); } // FIXME: Handle <div>, <pre>, lists, tables etc which could also theoretically // exist inside the <figure> element break; } case HTML_NAV: { const char *className = DFGetAttribute(node,HTML_CLASS); const char *instr = NULL; if (DFStringEquals(className,DFTableOfContentsClass)) instr = " TOC \\o \"1-3\" "; else if (DFStringEquals(className,DFListOfFiguresClass)) instr = " TOC \\c \"Figure\" "; else if (DFStringEquals(className,DFListOfTablesClass)) instr = " TOC \\c \"Table\" "; if (instr != NULL) { DFNode *p = DFCreateElement(word->html,HTML_P); DFNode *field = DFCreateChildElement(p,HTML_SPAN); DFSetAttribute(field,HTML_CLASS,DFFieldClass); DFCreateChildTextNode(field,instr); DFInsertBefore(node->parent,p,node); DFRemoveNode(node); } break; } } DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; Word_preProcessHTML(word,child); } }
static void WordGetStyle(DFNode *concrete, CSSStyle *style, WordConverter *converter) { WordSection *section = converter->mainSection; for (DFNode *child = concrete->first; child != NULL; child = child->next) { const char *styleId = NULL; switch (child->tag) { case WORD_NAME: CSSStyleSetDisplayName(style,DFGetAttribute(child,WORD_VAL)); break; case WORD_NEXT: { // FIXME: do he need to handle style types other than paragraph here? const char *nextName = DFGetAttribute(child,WORD_VAL); if (nextName == NULL) continue; WordStyle *nextStyle = WordSheetStyleForTypeId(converter->styles,"paragraph",nextName); if (nextStyle == NULL) continue; CSSStyleSetNext(style,nextStyle->selector); break; } case WORD_RPR: WordGetRPr(child,CSSStyleRule(style),&styleId,converter->theme); break; case WORD_PPR: { WordGetPPr(child,CSSStyleRule(style),&styleId,section); if (style->headingLevel > 0) { DFNode *numPr = DFChildWithTag(child,WORD_NUMPR); if (numPr != NULL) WordGetNumPrStyle(numPr,style,converter); } break; } case WORD_TBLPR: WordGetTblPr(child,CSSStyleRule(style),CSSStyleCell(style),section,&styleId); break; case WORD_TRPR: // FIXME break; case WORD_TCPR: WordGetTcPr(child,CSSStyleRule(style)); break; case WORD_TBLSTYLEPR: WordGetTblStylePr(child,style,section,converter->theme); break; } } // Special case: The ListParagraph style that word automatically adds specifies an indentation // of 36pt. We don't actually want this, because HTML automatically indents lists anyway. If // we see this, clear it, but keep the old value around for when we update the word document. StyleFamily family = WordStyleFamilyForSelector(style->selector); const char *name = WordSheetStyleIdForSelector(converter->styles,style->selector); if ((family == StyleFamilyParagraph) && DFStringEquals(name,"ListParagraph")) { CSSProperties *properties = CSSStyleRule(style); const char *wordMarginLeft = CSSGet(properties,"margin-left"); CSSPut(properties,"-word-margin-left",wordMarginLeft); CSSPut(properties,"margin-left",NULL); } DFNode *pPr = DFChildWithTag(concrete,WORD_PPR); DFNode *numPr = DFChildWithTag(pPr,WORD_NUMPR); const char *numId = DFGetChildAttribute(numPr,WORD_NUMID,WORD_VAL); if ((numId != NULL) && (atoi(numId) == 0)) { switch (style->tag) { case HTML_H1: case HTML_H2: case HTML_H3: case HTML_H4: case HTML_H5: case HTML_H6: case HTML_FIGURE: case HTML_TABLE: { char *counterIncrement = DFFormatString("%s 0",style->elementName); CSSPut(CSSStyleRule(style),"counter-reset","null"); CSSPut(CSSStyleRule(style),"counter-increment",counterIncrement); CSSPut(CSSStyleBefore(style),"content","none"); free(counterIncrement); } } } }
static void collapseRecursive(DFNode *node, DFHashTable *bookmarksById) { DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; switch (child->tag) { case WORD_BOOKMARKSTART: case WORD_BOOKMARKEND: { DFArray *startElements = DFArrayNew(NULL,NULL); DFArray *endElements = DFArrayNew(NULL,NULL); DFHashTable *startIds = DFHashTableNew((DFCopyFunction)strdup,(DFFreeFunction)free); DFHashTable *endIds = DFHashTableNew((DFCopyFunction)strdup,(DFFreeFunction)free); DFNode *n; for (n = child; (n != NULL) && ((n->tag == WORD_BOOKMARKSTART) || (n->tag == WORD_BOOKMARKEND)); n = n->next) { if (n->tag == WORD_BOOKMARKSTART) { const char *idValue = DFGetAttribute(n,WORD_ID); if (idValue == NULL) idValue = ""; DFHashTableAdd(startIds,idValue,idValue); DFArrayAppend(startElements,n); } else { const char *idValue = DFGetAttribute(n,WORD_ID); if (idValue == NULL) idValue = ""; DFHashTableAdd(endIds,idValue,idValue); DFArrayAppend(endElements,n); } } next = n; DFArraySort(startElements,bookmarksById,compareStartElements); for (size_t endIndex = 0; endIndex < DFArrayCount(endElements); endIndex++) { DFNode *elem = DFArrayItemAt(endElements,endIndex); const char *endId = DFGetAttribute(elem,WORD_ID); int found = 0; DFNode *ancestor; for (ancestor = elem->parent; (ancestor != NULL) && !found; ancestor = ancestor->parent) { if ((ancestor->tag == WORD_BOOKMARK) && DFStringEquals(DFGetAttribute(ancestor,WORD_ID),endId)) { found = 1; break; } } if (found) { DFNode *before = ancestor->next; DFNode *nnext; for (DFNode *n = child; n != NULL; n = nnext) { nnext = n->next; DFInsertBefore(ancestor->parent,n,before); } } } size_t x = 0; while (x < DFArrayCount(startElements)) { DFNode *element = DFArrayItemAt(startElements,x); const char *bookmarkId = DFGetAttribute(element,WORD_ID); if (bookmarkId == NULL) bookmarkId = ""; if (DFHashTableLookup(endIds,bookmarkId) != NULL) { element->tag = WORD_BOOKMARK; DFArrayRemove(startElements,x); } else { x++; } } if (DFArrayCount(startElements) > 0) { for (size_t i = 1; i < DFArrayCount(startElements); i++) { DFNode *tempParent = DFArrayItemAt(startElements,i-1); DFNode *tempChild = DFArrayItemAt(startElements,i); DFAppendChild(tempParent,tempChild); } DFNode *last = DFArrayItemAt(startElements,DFArrayCount(startElements)-1); while (next != NULL) { DFNode *tempChild = next; next = next->next; DFAppendChild(last,tempChild); } } for (size_t eIndex = 0; eIndex < DFArrayCount(startElements); eIndex++) { DFNode *e = DFArrayItemAt(startElements,eIndex); e->tag = WORD_BOOKMARK; } for (size_t eIndex = 0; eIndex < DFArrayCount(endElements); eIndex++) { DFNode *e = DFArrayItemAt(endElements,eIndex); DFRemoveNode(e); } if (DFArrayCount(startElements) > 0) { DFNode *last = DFArrayItemAt(startElements,DFArrayCount(startElements)-1); collapseRecursive(last,bookmarksById); } DFArrayRelease(startElements); DFArrayRelease(endElements); DFHashTableRelease(startIds); DFHashTableRelease(endIds); break; } default: collapseRecursive(child,bookmarksById); break; } } }
int CSSGetDefault(CSSProperties *properties) { return DFStringEquals(CSSGet(properties,"-uxwrite-default"),"true"); }
void Word_setupBookmarkLinks(WordPutData *put) { DFHashTable *referencesById = findReferences(put->conv->html); const char **sortedIds = DFHashTableCopyKeys(referencesById); DFSortStringsCaseSensitive(sortedIds); for (int idIndex = 0; sortedIds[idIndex]; idIndex++) { const char *targetId = sortedIds[idIndex]; DFArray *references = DFHashTableLookup(referencesById,targetId); DFNode *targetElem = DFElementForIdAttr(put->conv->html,targetId); if (targetElem == NULL) continue; // The following is only relevant for figures and tables int refText = 0; int refLabelNum = 0; int refCaptionText = 0; for (int refIndex = 0; refIndex < DFArrayCount(references); refIndex++) { DFNode *a = DFArrayItemAt(references,refIndex); const char *className = DFGetAttribute(a,HTML_CLASS); if (DFStringEquals(className,DFRefTextClass)) refText = 1; else if (DFStringEquals(className,DFRefLabelNumClass)) refLabelNum = 1; else if (DFStringEquals(className,DFRefCaptionTextClass)) refCaptionText = 1; } DFNode *concrete = WordConverterGetConcrete(put,targetElem); switch (targetElem->tag) { case HTML_H1: case HTML_H2: case HTML_H3: case HTML_H4: case HTML_H5: case HTML_H6: { const char *bookmarkId = NULL; const char *bookmarkName = NULL; DFNode *bookmarkElem = NULL; if ((concrete != NULL) && (concrete->tag == WORD_P)) { // FIXME: We only want to consider the bookmark to be the headings "correct" // bookmark in the case where it contains all of the heading's content, though // excluding other bookmarks that might come before or after it. // If you have the cursor inside a heading bookmark when you save the document, // word puts a bookmark called _GoBack there, and we of course don't want to // confuse that with the actual heading's bookmark (if any). // For now as a temporary hack we just explicitly filter out _GoBack; but there // needs to be a more general fix, as there may be other bookmarks that end up // in the heading. for (DFNode *child = concrete->first; child != NULL; child = child->next) { if ((child->tag == WORD_BOOKMARK) && !DFStringEquals(DFGetAttribute(child,WORD_NAME),"_GoBack")) { bookmarkElem = child; bookmarkId = DFGetAttribute(bookmarkElem,WORD_ID); bookmarkName = DFGetAttribute(bookmarkElem,WORD_NAME); break; } } } if ((bookmarkElem == NULL) || (bookmarkId == NULL) || (bookmarkName == NULL)) { // New bookmark WordBookmark *bookmark = WordObjectsAddBookmark(put->conv->objects); bookmarkId =bookmark->bookmarkId; bookmarkName = bookmark->bookmarkName; } DFNode *bookmarkSpan = DFCreateElement(put->conv->package->document,HTML_SPAN); DFSetAttribute(bookmarkSpan,HTML_CLASS,DFBookmarkClass); if (bookmarkElem != NULL) { // FIXME: Not covered by tests DFFormatAttribute(bookmarkSpan,HTML_ID,"%s%u",put->conv->idPrefix,bookmarkElem->seqNo); } DFSetAttribute(bookmarkSpan,WORD_NAME,bookmarkName); DFSetAttribute(bookmarkSpan,WORD_ID,bookmarkId); while (targetElem->first != NULL) DFAppendChild(bookmarkSpan,targetElem->first); DFAppendChild(targetElem,bookmarkSpan); break; } case HTML_TABLE: case HTML_FIGURE: { WordCaption *caption = WordObjectsCaptionForTarget(put->conv->objects,targetElem); if (caption == NULL) break; assert(caption->element != NULL); assert((caption->number == NULL) || (caption->number->parent == caption->element)); assert((caption->contentStart == NULL) || (caption->contentStart->parent == caption->element)); // Note: caption.number may be null (i.e. if the caption is unnumbered) // caption.contentStart may be null (if there is no text in the caption) WordBookmark *captionTextBookmark = NULL; WordBookmark *labelNumBookmark = NULL; WordBookmark *textBookmark = NULL; if (!refCaptionText && !refLabelNum && !refText) refText = 1; if (refCaptionText) { captionTextBookmark = createBookmark(put->conv); DFNode *nnext; for (DFNode *n = caption->contentStart; n != NULL; n = nnext) { nnext = n->next; DFAppendChild(captionTextBookmark->element,n); } DFAppendChild(caption->element,captionTextBookmark->element); } if (refLabelNum && (caption->number != NULL)) { labelNumBookmark = createBookmark(put->conv); DFNode *numberNext = caption->number->next; DFNode *nnext; for (DFNode *n = caption->element->first; (n != NULL) && (n != numberNext); n = nnext) { nnext = n->next; DFAppendChild(labelNumBookmark->element,n); } DFInsertBefore(caption->element,labelNumBookmark->element,caption->element->first); } if (refText) { textBookmark = createBookmark(put->conv); DFNode *nnext; for (DFNode *n = caption->element->first; n != NULL; n = nnext) { nnext = n->next; DFAppendChild(textBookmark->element,n); } DFAppendChild(caption->element,textBookmark->element); } caption->captionTextBookmark = captionTextBookmark; caption->labelNumBookmark = labelNumBookmark; caption->textBookmark = textBookmark; break; } } } free(sortedIds); DFHashTableRelease(referencesById); }
static void Word_postProcessHTML(WordConverter *conv, DFNode *node) { DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; switch (child->tag) { case HTML_SPAN: { const char *className = DFGetAttribute(child,HTML_CLASS); if (DFStringEquals(className,DFBookmarkClass)) { if (child->first != NULL) next = child->first; DFRemoveNodeButKeepChildren(child); } break; } case HTML_CAPTION: { const char *counterName = NULL; if ((child->prev != NULL) && (child->prev->tag == HTML_FIGURE) && (DFChildWithTag(child->prev,HTML_FIGCAPTION) == NULL)) { child->tag = HTML_FIGCAPTION; counterName = "figure"; DFAppendChild(child->prev,child); } else if ((child->prev != NULL) && (child->prev->tag == HTML_TABLE) && (DFChildWithTag(child->prev,HTML_CAPTION) == NULL)) { counterName = "table"; DFInsertBefore(child->prev,child,child->prev->first); } else if ((child->next != NULL) && (child->next->tag == HTML_FIGURE) && (DFChildWithTag(child->next,HTML_FIGCAPTION) == NULL)) { child->tag = HTML_FIGCAPTION; counterName = "figure"; DFInsertBefore(child->next,child,child->next->first); } else if ((child->next != NULL) && (child->next->tag == HTML_TABLE) && (DFChildWithTag(child->next,HTML_CAPTION) == NULL)) { counterName = "table"; DFSetAttribute(child,HTML_STYLE,"caption-side: top"); DFInsertBefore(child->next,child,child->next->first); } if (counterName != NULL) { char *beforeText = extractPrefix(child,counterName); if (beforeText != NULL) { CSSStyle *style = CSSSheetLookupElement(conv->styleSheet,DFNodeName(child),NULL,1,0); if (CSSGet(CSSStyleBefore(style),"content") == NULL) { CSSPut(CSSStyleRule(style),"counter-increment",counterName); CSSPut(CSSStyleBefore(style),"content",beforeText); } } free(beforeText); } break; } case HTML_NAV: { if (HTML_isParagraphTag(node->tag)) { if (child->prev != NULL) { DFNode *beforeP = DFCreateElement(conv->package->document,node->tag); while (child->prev != NULL) DFInsertBefore(beforeP,child->prev,beforeP->first); DFInsertBefore(node->parent,beforeP,node); } DFInsertBefore(node->parent,child,node); if ((node->first == NULL) || ((node->first->tag == HTML_BR) && (node->first->next == NULL))) { DFRemoveNode(node); return; } next = NULL; } break; } } } for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; Word_postProcessHTML(conv,child); } }
int CSSStyleIsNumbered(CSSStyle *style) { const char *beforeContent = CSSGet(CSSStyleBefore(style),"content"); return ((beforeContent != NULL) && !DFStringEquals(beforeContent,"\"\"")); }
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); }
static void WordPutStyle(DFNode *concrete, CSSStyle *style, WordConverter *converter) { // If we find a style with a counter-increment value of "<elementname> 0", this means that it's // an explicitly unnumbered paragraph. So we set the numbering id to 0, so word doesn't increment // the corresponding counter when encountering this style if (CSSGet(CSSStyleRule(style),"counter-increment") != NULL) { char *zeroIncrement = DFFormatString("%s 0",style->elementName); if (DFStringEquals(CSSGet(CSSStyleRule(style),"counter-increment"),zeroIncrement)) CSSPut(CSSStyleRule(style),"-word-numId","0"); free(zeroIncrement); } int outlineLvl = -1; if ((style->headingLevel >= 1) && (style->headingLevel <= 6)) { outlineLvl = style->headingLevel-1; char *nextSelector = CSSStyleCopyNext(style); if (nextSelector == NULL) { CSSStyle *def = CSSSheetDefaultStyleForFamily(converter->styleSheet,StyleFamilyParagraph); if (def != NULL) CSSStyleSetNext(style,def->selector); } free(nextSelector); } DFNode *children[PREDEFINED_TAG_COUNT]; childrenToArray(concrete,children); char *parentSelector = CSSStyleCopyParent(style); if (parentSelector == NULL) { if (style->className != NULL) { if ((style->tag != HTML_P) && (style->tag != HTML_SPAN) && (style->tag != HTML_TABLE)) { CSSStyle *parentStyle = CSSSheetLookupElement(converter->styleSheet,style->elementName,NULL,0,0); if ((parentStyle != NULL) && !parentStyle->latent) parentSelector = xstrdup(style->elementName); } } } // Based on const char *basedOn = WordSheetStyleIdForSelector(converter->styles,parentSelector); if (basedOn == NULL) { children[WORD_BASEDON] = NULL; } else { children[WORD_BASEDON] = DFCreateElement(concrete->doc,WORD_BASEDON); DFSetAttribute(children[WORD_BASEDON],WORD_VAL,basedOn); } // Style for next paragraph children[WORD_NEXT] = NULL; char *nextSelector = CSSStyleCopyNext(style); if (nextSelector != NULL) { StyleFamily thisFamily = WordStyleFamilyForSelector(style->selector); StyleFamily nextFamily = WordStyleFamilyForSelector(nextSelector); if (nextFamily == thisFamily) { children[WORD_NEXT] = DFCreateElement(concrete->doc,WORD_NEXT); const char *nextStyleId = WordSheetStyleIdForSelector(converter->styles,nextSelector); DFSetAttribute(children[WORD_NEXT],WORD_VAL,nextStyleId); } } if (WordStyleFamilyForSelector(style->selector) == StyleFamilyTable) { // Table properties if (children[WORD_TBLPR] == NULL) children[WORD_TBLPR] = DFCreateElement(concrete->doc,WORD_TBLPR);; const char *oldJc = DFGetChildAttribute(children[WORD_TBLPR],WORD_JC,WORD_VAL); CSSProperties *wholeTable = CSSStyleRuleForSuffix(style,DFTableSuffixWholeTable); WordPutTblPr(children[WORD_TBLPR],wholeTable,CSSStyleCell(style),converter->mainSection,NULL); const char *newJc = DFGetChildAttribute(children[WORD_TBLPR],WORD_JC,WORD_VAL); if (children[WORD_TBLPR]->first == NULL) children[WORD_TBLPR] = NULL; if (children[WORD_TRPR] == NULL) children[WORD_TRPR] = DFCreateElement(concrete->doc,WORD_TRPR); WordPutTrPr(children[WORD_TRPR],oldJc,newJc); if (children[WORD_TRPR]->first == NULL) children[WORD_TRPR] = NULL; } else { // Paragraph properties if (children[WORD_PPR] == NULL) children[WORD_PPR] = DFCreateElement(concrete->doc,WORD_PPR); WordPutPPr(children[WORD_PPR],CSSStyleRule(style),NULL,converter->mainSection,outlineLvl); if (children[WORD_PPR]->first == NULL) children[WORD_PPR] = NULL; // Run properties if (children[WORD_RPR] == NULL) children[WORD_RPR] = DFCreateElement(concrete->doc,WORD_RPR); WordPutRPr(children[WORD_RPR],CSSStyleRule(style),NULL,converter->theme); if (children[WORD_RPR]->first == NULL) children[WORD_RPR] = NULL; } replaceChildrenFromArray(concrete,children,WordStyle_Children); free(parentSelector); free(nextSelector); }
static void WordPutSectPr(DFNode *concrete, CSSSheet *styleSheet, WordSection *section) { // Note: The sectPr element can potentially contain one or more headerReference or // footerReference elements at the start, before the elements in WordSectPr_Children. // So the straight childrenToArray/replaceChildrenFromArray method used elsewhere doesn't // work here - we need to make sure these other elements are maintained DFNode *children[PREDEFINED_TAG_COUNT]; childrenToArray(concrete,children); CSSProperties *oldBody = CSSPropertiesNew(); CSSProperties *oldPage = CSSPropertiesNew(); WordGetSectPr(concrete,oldBody,oldPage,section); CSSProperties *newBody = CSSSheetBodyProperties(styleSheet); CSSProperties *newPage = CSSSheetPageProperties(styleSheet); // Page size if (children[WORD_PGSZ] == NULL) children[WORD_PGSZ] = DFCreateElement(concrete->doc,WORD_PGSZ); int updatePageSize = 0; const char *widthStr = DFGetAttribute(children[WORD_PGSZ],WORD_W); const char *heightStr = DFGetAttribute(children[WORD_PGSZ],WORD_H); int widthTwips; int heightTwips; if ((widthStr == NULL) || (atoi(widthStr) <= 0) || (heightStr == NULL) || (atoi(heightStr) <= 0)) { // Invalid or missing page size: Set to A4 portrait widthTwips = A4_WIDTH_TWIPS; heightTwips = A4_HEIGHT_TWIPS; updatePageSize = 1; } else { widthTwips = atoi(widthStr); heightTwips = atoi(heightStr); } if (!DFStringEquals(CSSGet(oldPage,"size"),CSSGet(newPage,"size"))) { const char *newSize = CSSGet(newPage,"size"); if (DFStringEqualsCI(newSize,"A4 portrait")) { widthTwips = A4_WIDTH_TWIPS; heightTwips = A4_HEIGHT_TWIPS; } else if (DFStringEqualsCI(newSize,"A4 landscape")) { widthTwips = A4_HEIGHT_TWIPS; heightTwips = A4_WIDTH_TWIPS; } else if (DFStringEqualsCI(newSize,"letter portrait")) { widthTwips = LETTER_WIDTH_TWIPS; heightTwips = LETTER_HEIGHT_TWIPS; } else if (DFStringEqualsCI(newSize,"letter landscape")) { widthTwips = LETTER_HEIGHT_TWIPS; heightTwips = LETTER_WIDTH_TWIPS; } else { widthTwips = A4_WIDTH_TWIPS; heightTwips = A4_HEIGHT_TWIPS; } updatePageSize = 1; } if (updatePageSize) { DFFormatAttribute(children[WORD_PGSZ],WORD_W,"%d",widthTwips); DFFormatAttribute(children[WORD_PGSZ],WORD_H,"%d",heightTwips); if (widthTwips > heightTwips) DFSetAttribute(children[WORD_PGSZ],WORD_ORIENT,"landscape"); else DFRemoveAttribute(children[WORD_PGSZ],WORD_ORIENT); } if (children[WORD_PGMAR] == NULL) children[WORD_PGMAR] = DFCreateElement(concrete->doc,WORD_PGMAR); // Page margins if (!DFStringEquals(CSSGet(oldBody,"margin-left"),CSSGet(newBody,"margin-left")) || updatePageSize) updateTwipsFromLength(children[WORD_PGMAR],WORD_LEFT,CSSGet(newBody,"margin-left"),widthTwips); if (!DFStringEquals(CSSGet(oldBody,"margin-right"),CSSGet(newBody,"margin-right")) || updatePageSize) updateTwipsFromLength(children[WORD_PGMAR],WORD_RIGHT,CSSGet(newBody,"margin-right"),widthTwips); if (!DFStringEquals(CSSGet(oldBody,"margin-top"),CSSGet(newBody,"margin-top")) || updatePageSize) updateTwipsFromLength(children[WORD_PGMAR],WORD_TOP,CSSGet(newBody,"margin-top"),widthTwips); if (!DFStringEquals(CSSGet(oldBody,"margin-bottom"),CSSGet(newBody,"margin-bottom")) || updatePageSize) updateTwipsFromLength(children[WORD_PGMAR],WORD_BOTTOM,CSSGet(newBody,"margin-bottom"),widthTwips); if (children[WORD_PGMAR]->attrsCount == 0) children[WORD_PGMAR] = NULL;; DFArray *extra = DFArrayNew(NULL,NULL); for (DFNode *child = concrete->first; child != NULL; child = child->next) { switch (child->tag) { case WORD_HEADERREFERENCE: case WORD_FOOTERREFERENCE: DFArrayAppend(extra,child); break; } } replaceChildrenFromArray(concrete,children,WordSectPr_Children); for (long i = (long)(DFArrayCount(extra)-1); i >= 0; i--) { DFNode *child = DFArrayItemAt(extra,i); DFInsertBefore(concrete,child,concrete->first); } DFArrayRelease(extra); CSSPropertiesRelease(oldBody); CSSPropertiesRelease(oldPage); }
static DFNode *WordTblGet(WordGetData *get, DFNode *concrete) { if (concrete->tag != WORD_TBL) return NULL;; DFNode *table = WordConverterCreateAbstract(get,HTML_TABLE,concrete); ConcreteInfo *cinfo = getConcreteInfo(get->conv,concrete); calcTotals(get,cinfo); const char *cellWidthType = cellWidthTypeForTable(concrete); int autoWidth = DFStringEquals(cellWidthType,"auto"); if ((CSSGet(cinfo->tableProperties,"width") == NULL) && autoWidth) { CSSPut(cinfo->tableProperties,"width",NULL); } else { // Determine column widths and table width if (cinfo->totalWidthPts > 0) { DFNode *colgroup = HTML_createColgroup(get->conv->html,cinfo->structure); DFAppendChild(table,colgroup); double tableWidthPct = 100.0; if (WordSectionContentWidth(get->conv->mainSection) > 0) { double contentWidthPts = WordSectionContentWidth(get->conv->mainSection)/20.0; tableWidthPct = 100.0*cinfo->totalWidthPts/contentWidthPts; if (CSSGet(cinfo->tableProperties,"width") == NULL) { char buf[100]; CSSPut(cinfo->tableProperties,"width",DFFormatDoublePct(buf,100,tableWidthPct)); } } } if (CSSGet(cinfo->tableProperties,"width") == NULL) CSSPut(cinfo->tableProperties,"width","100%"); } DFHashTable *collapsed = CSSCollapseProperties(cinfo->tableProperties); char *styleValue = CSSSerializeProperties(collapsed); DFHashTableRelease(collapsed); if (strlen(styleValue) > 0) DFSetAttribute(table,HTML_STYLE,styleValue); free(styleValue); if ((cinfo->style != NULL) && (cinfo->style->selector != NULL)) { char *className = CSSSelectorCopyClassName(cinfo->style->selector); DFSetAttribute(table,HTML_CLASS,className); free(className); } else { CSSStyle *defaultStyle = CSSSheetDefaultStyleForFamily(get->conv->styleSheet,StyleFamilyTable); if (defaultStyle != NULL) DFSetAttribute(table,HTML_CLASS,defaultStyle->className); } // Create rows and cells int row = 0; for (DFNode *tblChild = concrete->first; tblChild != NULL; tblChild = tblChild->next) { if (tblChild->tag != WORD_TR) continue; DFNode *tr = WordConverterCreateAbstract(get,HTML_TR,tblChild); DFAppendChild(table,tr); unsigned int col = 0; while (col < cinfo->structure->cols) { DFCell *cell = DFTableGetCell(cinfo->structure,row,col); if (cell == NULL) { DFNode *td = DFCreateElement(get->conv->html,HTML_TD); DFAppendChild(tr,td); col++; continue; } if (row == cell->row) { DFNode *td = WordTcGet(get,cell->element); DFAppendChild(tr,td); if (cell->colSpan != 1) DFFormatAttribute(td,HTML_COLSPAN,"%d",cell->colSpan); if (cell->rowSpan != 1) DFFormatAttribute(td,HTML_ROWSPAN,"%d",cell->rowSpan); } col += cell->colSpan; } row++; } ConcreteInfoFree(cinfo); return table; }
static void Word_fixListSingle(WordConverter *conv, DFNode *node) { ListStack stack; bzero(&stack,sizeof(ListStack)); DFNode *next; for (DFNode *child = node->first; child != NULL; child = next) { next = child->next; int isListItem = 0; if (child->tag == HTML_P) { DFNode *elem = child; const char *numIdStr = DFGetAttribute(elem,WORD_NUMID); const char *ilvlStr = DFGetAttribute(elem,WORD_ILVL); DFRemoveAttribute(elem,WORD_NUMID); DFRemoveAttribute(elem,WORD_ILVL); // A numId of 0 means that there is no numbering applied to this paragraph if ((numIdStr != NULL) && (atoi(numIdStr) == 0)) { numIdStr = NULL; ilvlStr = NULL; } if ((numIdStr != NULL) && (ilvlStr != NULL)) { isListItem = 1; int numId = atoi(numIdStr); int ilvl = atoi(ilvlStr); ListDimensions dimensions = listIndent(conv,numIdStr,ilvlStr); // Find the list at the same ilvl, and check if it has the same numId. If not, we're // starting a new list. ListFrame *sameLevelFrame = NULL; for (ListFrame *frame = stack.top; frame != NULL; frame = frame->parent) { if (frame->ilvl == ilvl) sameLevelFrame = frame; } if ((sameLevelFrame != NULL) && (sameLevelFrame->numId != numId)) fixTrailingParagraphs(&stack,ilvl); else fixTrailingParagraphs(&stack,ilvl+1); if ((stack.top != NULL) && (stack.top->numId != numId)) ListStackPopToAboveIlvl(&stack,ilvl); else if ((stack.top != NULL) && (stack.top->ilvl > ilvl)) ListStackPopToAboveIlvl(&stack,ilvl+1); if ((stack.top == NULL) || (stack.top->numId != numId) || (stack.top->ilvl < ilvl)) { WordConcreteNum *num = WordNumberingConcreteWithId(conv->numbering,numIdStr); // may be NULL WordNumLevel *level = WordConcreteNumGetLevel(num,ilvl); // may be NULL const char *type = WordNumLevelToListStyleType(level); // may be NULL Tag tag; if (DFStringEquals(type,"disc") || DFStringEquals(type,"circle") || DFStringEquals(type,"square")) tag = HTML_UL; else tag = HTML_OL; DFNode *element = DFCreateElement(conv->html,tag); if (type != NULL) DFFormatAttribute(element,HTML_STYLE,"list-style-type: %s",type); if (stack.top != NULL) { DFNode *li; if (stack.top->element->last != NULL) li = stack.top->element->last; else li = DFCreateChildElement(stack.top->element,HTML_LI); DFAppendChild(li,element); } else { DFInsertBefore(node,element,child); } ListStackPushFrame(&stack,element,numId,ilvl,dimensions); } } } if (stack.top != NULL) { DFNode *li; if ((stack.top->element->last != NULL) && !isListItem) li = stack.top->element->last; else li = DFCreateChildElement(stack.top->element,HTML_LI); DFAppendChild(li,child); } } fixTrailingParagraphs(&stack,-1); while (stack.top != NULL) ListStackPop(&stack); }
void WordPutPPr(DFNode *pPr, CSSProperties *properties, const char *styleId, WordSection *section, int outlineLvl) { assert(pPr->tag == WORD_PPR); // The child elements of pPr have to be in a specific order. So we build up a structure based // on the existing elements (updated as appropriate from newProperties), and then rebuild // from that CSSProperties *oldp = CSSPropertiesNew(); CSSProperties *newp = properties; const char *oldStyleId = NULL; const char *newStyleId = styleId; WordGetPPr(pPr,oldp,&oldStyleId,section); { DFNode *children[PREDEFINED_TAG_COUNT]; childrenToArray(pPr,children); int existingOutlineLvl = -1; if (children[WORD_OUTLINELVL] != NULL) { const char *value = DFGetAttribute(children[WORD_OUTLINELVL],WORD_VAL); if (value != NULL) existingOutlineLvl = atoi(value); } // Style name if (!DFStringEquals(oldStyleId,newStyleId)) { if (newStyleId != NULL) { children[WORD_PSTYLE] = DFCreateElement(pPr->doc,WORD_PSTYLE); DFSetAttribute(children[WORD_PSTYLE],WORD_VAL,newStyleId); } else { children[WORD_PSTYLE] = NULL; } } // Paragraph border (pBdr) if (children[WORD_PBDR] == NULL) children[WORD_PBDR] = DFCreateElement(pPr->doc,WORD_PBDR); WordPutPBdr(children[WORD_PBDR],oldp,newp); if (children[WORD_PBDR]->first == NULL) children[WORD_PBDR] = NULL; // Numbering and outline level // Don't change these properties for styles with outline level >= 6, as these can't be // represented with the standard HTML heading elements, which only go from h1 - h6 // (outline levels 0 - 5) if (existingOutlineLvl < 6) { if (children[WORD_NUMPR] == NULL) children[WORD_NUMPR] = DFCreateElement(pPr->doc,WORD_NUMPR); WordPutNumPr(children[WORD_NUMPR],newp); if (children[WORD_NUMPR]->first == NULL) children[WORD_NUMPR] = NULL; if ((outlineLvl >= 0) && (outlineLvl <= 5)) { if (children[WORD_OUTLINELVL] == NULL) children[WORD_OUTLINELVL] = DFCreateElement(pPr->doc,WORD_OUTLINELVL); DFFormatAttribute(children[WORD_OUTLINELVL],WORD_VAL,"%d",outlineLvl); } else { children[WORD_OUTLINELVL] = NULL; } } // background-color char *oldBackgroundColor = CSSHexColor(CSSGet(oldp,"background-color"),0); char *newBackgroundColor = CSSHexColor(CSSGet(newp,"background-color"),0); if (!DFStringEquals(oldBackgroundColor,newBackgroundColor)) WordPutShd(pPr->doc,&children[WORD_SHD],newBackgroundColor); free(oldBackgroundColor); free(newBackgroundColor); // text-align if (!DFStringEquals(CSSGet(oldp,"text-align"),CSSGet(newp,"text-align"))) { const char *newTextAlign = CSSGet(newp,"text-align"); if (newTextAlign != NULL) { const char *val = NULL; if (!strcmp(newTextAlign,"left")) val = "left"; else if (!strcmp(newTextAlign,"right")) val = "right"; else if (!strcmp(newTextAlign,"center")) val = "center"; else if (!strcmp(newTextAlign,"justify")) val = "both"; if (val != NULL) { children[WORD_JC] = DFCreateElement(pPr->doc,WORD_JC); DFSetAttribute(children[WORD_JC],WORD_VAL,val); } } else { children[WORD_JC] = NULL; } } if ((section != NULL) && (WordSectionContentWidth(section) >= 0)) { const char *oldMarginLeft = CSSGet(oldp,"margin-left"); const char *oldMarginRight = CSSGet(oldp,"margin-right"); const char *oldTextIndent = CSSGet(oldp,"text-indent"); const char *newMarginLeft = CSSGet(newp,"margin-left"); const char *newMarginRight = CSSGet(newp,"margin-right"); const char *newTextIndent = CSSGet(newp,"text-indent"); // Special case of the List_Paragraph style, which is used by Word to ensure lists are indented. We // don't set this property for HTML, because it automatically indents lists. However we need to ensure // that it remains unchanged when updating the word document const char *newWordMarginLeft = CSSGet(newp,"-word-margin-left"); if (newMarginLeft == NULL) newMarginLeft = newWordMarginLeft; if ((newMarginLeft == NULL) && (newMarginRight == NULL) && (newTextIndent == NULL)) { children[WORD_IND] = NULL; } else { if (children[WORD_IND] == NULL) children[WORD_IND] = DFCreateElement(pPr->doc,WORD_IND); if (!DFStringEquals(oldMarginLeft,newMarginLeft)) { if (newMarginLeft != NULL) updateTwipsFromLength(children[WORD_IND],WORD_LEFT,newMarginLeft,WordSectionContentWidth(section)); else DFRemoveAttribute(children[WORD_IND],WORD_LEFT); DFRemoveAttribute(children[WORD_IND],WORD_START); } if (!DFStringEquals(oldMarginRight,newMarginRight)) { if (newMarginRight != NULL) updateTwipsFromLength(children[WORD_IND],WORD_RIGHT,newMarginRight,WordSectionContentWidth(section)); else DFRemoveAttribute(children[WORD_IND],WORD_RIGHT); DFRemoveAttribute(children[WORD_IND],WORD_END); } if (!DFStringEquals(oldTextIndent,newTextIndent)) { if (newTextIndent != NULL) { CSSLength length = CSSLengthFromString(newTextIndent); if (CSSLengthIsValid(length)) { double pts = CSSLengthToPts(length,WordSectionContentWidthPts(section)); int twips = (int)round(pts*20); if (twips >= 0) { DFFormatAttribute(children[WORD_IND],WORD_FIRSTLINE,"%d",twips); DFRemoveAttribute(children[WORD_IND],WORD_HANGING); } else { DFFormatAttribute(children[WORD_IND],WORD_HANGING,"%d",-twips); DFRemoveAttribute(children[WORD_IND],WORD_FIRSTLINE); } } } else { DFRemoveAttribute(children[WORD_IND],WORD_FIRSTLINE); DFRemoveAttribute(children[WORD_IND],WORD_HANGING); } } } } if (!DFStringEquals(CSSGet(oldp,"margin-top"),CSSGet(newp,"margin-top")) || !DFStringEquals(CSSGet(oldp,"margin-bottom"),CSSGet(newp,"margin-bottom")) || !DFStringEquals(CSSGet(oldp,"line-height"),CSSGet(newp,"line-height"))) { if ((CSSGet(newp,"margin-top") == NULL) && (CSSGet(newp,"margin-bottom") == NULL) && (CSSGet(newp,"line-height") == NULL)) { children[WORD_SPACING] = NULL; } else { children[WORD_SPACING] = DFCreateElement(pPr->doc,WORD_SPACING); if (DFStringEquals(CSSGet(newp,"margin-top"),"-word-auto")) { DFSetAttribute(children[WORD_SPACING],WORD_BEFORE,"100"); DFSetAttribute(children[WORD_SPACING],WORD_BEFOREAUTOSPACING,"1"); } else { char *before = twipsFromCSS(CSSGet(newp,"margin-top"),WordSectionContentWidth(section)); DFSetAttribute(children[WORD_SPACING],WORD_BEFORE,before); DFSetAttribute(children[WORD_SPACING],WORD_BEFOREAUTOSPACING,NULL); free(before); } if (DFStringEquals(CSSGet(newp,"margin-bottom"),"-word-auto")) { DFSetAttribute(children[WORD_SPACING],WORD_AFTER,"100"); DFSetAttribute(children[WORD_SPACING],WORD_AFTERAUTOSPACING,"1"); } else { char *after = twipsFromCSS(CSSGet(newp,"margin-bottom"),WordSectionContentWidth(section)); DFSetAttribute(children[WORD_SPACING],WORD_AFTER,after); DFSetAttribute(children[WORD_SPACING],WORD_AFTERAUTOSPACING,NULL); free(after); } CSSLength lineHeight = CSSLengthFromString(CSSGet(newp,"line-height")); if (CSSLengthIsValid(lineHeight) && (lineHeight.units == UnitsPct)) { int value = (int)round(lineHeight.value*2.4); DFFormatAttribute(children[WORD_SPACING],WORD_LINE,"%d",value); } if (children[WORD_SPACING]->attrsCount == 0) children[WORD_SPACING] = NULL; } } replaceChildrenFromArray(pPr,children,WordPPR_Children); } CSSPropertiesRelease(oldp); }