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 Word_flattenList(WordConverter *word, DFNode *list, int ilvl, DFNode *parent, DFNode *nextSibling) { const char *type = (list->tag == HTML_OL) ? "decimal" : "disc"; const char *cssText = DFGetAttribute(list,HTML_STYLE); CSSProperties *properties = CSSPropertiesNewWithString(cssText); if (CSSGet(properties,"list-style-type") != NULL) type = CSSGet(properties,"list-style-type"); DFFormatAttribute(list,CONV_LISTNUM,"%u",list->seqNo); DFFormatAttribute(list,CONV_ILVL,"%d",ilvl); DFSetAttribute(list,CONV_LISTTYPE,type); for (DFNode *li = list->first; li != NULL; li = li->next) { DFNode *first = li->first; DFNode *liChildNext; for (DFNode *liChild = li->first; liChild != NULL; liChild = liChildNext) { liChildNext = liChild->next; if ((liChild->tag == HTML_UL) || (liChild->tag == HTML_OL)) { Word_flattenList(word,liChild,ilvl+1,parent,nextSibling); } else { if (liChild->tag == HTML_P) { DFFormatAttribute(liChild,CONV_LISTNUM,"%u",list->seqNo); DFFormatAttribute(liChild,CONV_ILVL,"%d",ilvl); DFSetAttribute(liChild,CONV_LISTTYPE,type); if (liChild == first) DFSetAttribute(liChild,CONV_LISTITEM,"true"); } DFInsertBefore(parent,liChild,nextSibling); Word_preProcessLists(word,liChild,ilvl); } } } DFRemoveNode(list); CSSPropertiesRelease(properties); }
static DFNode *imageWithFilename(WordGetData *get, const char *filename, double widthPts, DFNode *concrete) { const char *concretePath = get->conv->concretePath; const char *abstractPath = get->conv->abstractPath; char *abstractImagesPath = DFAppendPathComponent(abstractPath,"images"); char *lastComponent = DFPathBaseName(filename); char *srcImagePath = DFAppendPathComponent(concretePath,filename); char *dstImagePath = DFAppendPathComponent(abstractImagesPath,lastComponent); if (DFFileExists(dstImagePath)) DFDeleteFile(dstImagePath,NULL); DFError *error = NULL; DFNode *imageNode = NULL; if (!DFFileExists(abstractImagesPath) && !DFCreateDirectory(abstractImagesPath,1,&error)) { WordConverterWarning(get->conv,"Create %s: %s",abstractImagesPath,DFErrorMessage(&error)); DFErrorRelease(error); imageNode = createAbstractPlaceholder(get,"[Error reading image]",concrete); } else if (!DFCopyFile(srcImagePath,dstImagePath,&error)) { WordConverterWarning(get->conv,"Copy %s to %s: %s",srcImagePath,dstImagePath,DFErrorMessage(&error)); DFErrorRelease(error); imageNode = createAbstractPlaceholder(get,"[Error reading image]",concrete); } else { imageNode = WordConverterCreateAbstract(get,HTML_IMG,concrete); DFFormatAttribute(imageNode,HTML_SRC,"images/%s",lastComponent); double contentWidthPts = WordSectionContentWidthPts(get->conv->mainSection); if (contentWidthPts > 0) { double widthPct = widthPts/contentWidthPts*100.0; CSSProperties *properties = CSSPropertiesNew(); char buf[100]; CSSPut(properties,"width",DFFormatDoublePct(buf,100,widthPct)); char *propertiesText = CSSPropertiesCopyDescription(properties); DFSetAttribute(imageNode,HTML_STYLE,propertiesText); free(propertiesText); CSSPropertiesRelease(properties); } } free(abstractImagesPath); free(lastComponent); free(srcImagePath); free(dstImagePath); return imageNode; }
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); }
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_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); }
static void populateDrawingElement(WordConverter *converter, DFNode *root, double widthPts, double heightPts, const char *drawingId, const char *rId) { EMUSize size; size.widthEmu = round(widthPts*EMUS_PER_POINT); size.heightEmu = round(heightPts*EMUS_PER_POINT); root->tag = WORD_DRAWING; DFRemoveAllAttributes(root); while (root->first != NULL) DFRemoveNode(root->first); char *docName = DFFormatString("Picture %s",drawingId); const char *imgName = "image"; const char *imgId = "0"; DFNode *inlin = DFCreateChildElement(root,DML_WP_INLINE); DFNode *extent = DFCreateChildElement(inlin,DML_WP_EXTENT); // a_CT_PositiveSize2D DFFormatAttribute(extent,NULL_CX,"%llu",size.widthEmu); DFFormatAttribute(extent,NULL_CY,"%llu",size.heightEmu); DFNode *docPr = DFCreateChildElement(inlin,DML_WP_DOCPR); // a_CT_NonVisualDrawingProps DFSetAttribute(docPr,NULL_ID,drawingId); DFSetAttribute(docPr,NULL_NAME,docName); DFNode *cNvGraphicFramePr = DFCreateChildElement(inlin,DML_WP_CNVGRAPHICFRAMEPR); DFNode *graphicFrameLocks = DFCreateChildElement(cNvGraphicFramePr,DML_MAIN_GRAPHICFRAMELOCKS); DFSetAttribute(graphicFrameLocks,NULL_NOCHANGEASPECT,"1"); DFNode *graphic = DFCreateChildElement(inlin,DML_MAIN_GRAPHIC); DFNode *graphicData = DFCreateChildElement(graphic,DML_MAIN_GRAPHICDATA); DFSetAttribute(graphicData,NULL_URI,"http://schemas.openxmlformats.org/drawingml/2006/picture"); DFNode *pic = DFCreateChildElement(graphicData,DML_PICTURE_PIC); DFNode *nvPicPr = DFCreateChildElement(pic,DML_PICTURE_NVPICPR); DFNode *blipFill = DFCreateChildElement(pic,DML_PICTURE_BLIPFILL); // a_CT_BlipFillProperties DFNode *spPr = DFCreateChildElement(pic,DML_PICTURE_SPPR); // a_CT_ShapeProperties // Graphic - Non-visual properties DFNode *cNvPr = DFCreateChildElement(nvPicPr,DML_PICTURE_CNVPR); DFNode *cNvPicPr = DFCreateChildElement(nvPicPr,DML_PICTURE_CNVPICPR); DFSetAttribute(cNvPr,NULL_ID,imgId); DFSetAttribute(cNvPr,NULL_NAME,imgName); // Blip DFNode *blip = DFCreateChildElement(blipFill,DML_MAIN_BLIP); DFSetAttribute(blip,OREL_EMBED,rId); DFNode *stretch = DFCreateChildElement(blipFill,DML_MAIN_STRETCH); DFNode *fillRect = DFCreateChildElement(stretch,DML_MAIN_FILLRECT); // Shape properties DFNode *xfrm = DFCreateChildElement(spPr,DML_MAIN_XFRM); DFNode *off = DFCreateChildElement(xfrm,DML_MAIN_OFF); DFSetAttribute(off,NULL_X,"0"); DFSetAttribute(off,NULL_Y,"0"); DFNode *ext = DFCreateChildElement(xfrm,DML_MAIN_EXT); DFFormatAttribute(ext,NULL_CX,"%llu",size.widthEmu); DFFormatAttribute(ext,NULL_CY,"%llu",size.heightEmu); DFNode *prstGeom = DFCreateChildElement(spPr,DML_MAIN_PRSTGEOM); DFSetAttribute(prstGeom,NULL_PRST,"rect"); (void)cNvPicPr; (void)fillRect; free(docName); }
static DFNode *WordFieldGet(WordGetData *get, DFNode *concrete) { if (concrete->tag != WORD_FLDSIMPLE) return NULL;; const char *instr = DFGetAttribute(concrete,WORD_INSTR); if (instr != NULL) { const char **args = Word_parseField(instr); size_t argCount = DFStringArrayCount(args); if ((argCount >= 2) && !strcmp(args[0],"REF")) { WordBookmark *bookmark = WordObjectsBookmarkWithName(get->conv->objects,args[1]); if ((bookmark != NULL) && (bookmark->target != NULL)) { WordRefType type = WordRefTypeGet(args,bookmark); DFNode *a = WordConverterCreateAbstract(get,HTML_A,concrete); DFFormatAttribute(a,HTML_HREF,"#%s%u",get->conv->idPrefix,bookmark->target->seqNo); DFSetAttribute(a,HTML_CLASS,WordRefTypeClassName(type)); free(args); return a; } } else if ((argCount >= 1) && !strcmp(args[0],"TOC")) { if ((argCount >= 2) && !strcmp(args[1],"\\o")) { DFNode *nav = WordConverterCreateAbstract(get,HTML_NAV,concrete); DFSetAttribute(nav,HTML_CLASS,DFTableOfContentsClass); free(args); return nav; } else if ((argCount >= 3) && !strcmp(args[1],"\\c")) { // FIXME: The names "Figure" and "Table" here will be different if the document // was created in a language other than English. We need to look through the // document to figure out which counter names are used in captions adjacent to // figures and tables to know what the counter names used in the document // actually are. // Another option might be just to collect a static list of names used in all the // major languages and base the detection on that. These would need to be checked // with multiple versions of word, as the names used could in theory change // between releases. // We should keep track of a set of "document parameters", which record the names // used for figure and table counters, as well as the prefixes used on numbered // figures and tables. The latter would correspond to the content property of the // caption::before and figcaption::before CSS rules. if (!strcmp(args[2],"Figure")) { DFNode *nav = WordConverterCreateAbstract(get,HTML_NAV,concrete); DFSetAttribute(nav,HTML_CLASS,DFListOfFiguresClass); free(args); return nav; } else if (!strcmp(args[2],"Table")) { DFNode *nav = WordConverterCreateAbstract(get,HTML_NAV,concrete); DFSetAttribute(nav,HTML_CLASS,DFListOfTablesClass); free(args); return nav; } } } DFNode *span = WordConverterCreateAbstract(get,HTML_SPAN,concrete); DFSetAttribute(span,HTML_CLASS,DFFieldClass); DFNode *text = DFCreateTextNode(get->conv->html,instr); DFAppendChild(span,text); free(args); return span; } return NULL; }
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); }
static void WordTblPut(WordPutData *put, DFNode *abstract, DFNode *concrete) { if ((abstract->tag != HTML_TABLE) || (concrete->tag != WORD_TBL)) return;; DFTable *abstractStructure = HTML_tableStructure(abstract); const char *inlineCSSText = DFGetAttribute(abstract,HTML_STYLE); CSSProperties *tableProperties = CSSPropertiesNewWithString(inlineCSSText); CSSProperties *cellProperties = CSSPropertiesNew(); const char *className = DFGetAttribute(abstract,HTML_CLASS); char *selector = CSSMakeSelector("table",className); WordStyle *style = WordSheetStyleForSelector(put->conv->styles,selector); CellPadding padding = getPadding(put->conv->styleSheet,style,cellProperties); DFNode *tblPr = DFChildWithTag(concrete,WORD_TBLPR); if (tblPr == NULL) tblPr = DFCreateElement(concrete->doc,WORD_TBLPR);; DFNode *tblGrid = DFChildWithTag(concrete,WORD_TBLGRID); if (tblGrid == NULL) tblGrid = DFCreateElement(concrete->doc,WORD_TBLGRID); while (concrete->first != NULL) DFRemoveNode(concrete->first); const char *oldJc = DFGetChildAttribute(tblPr,WORD_JC,WORD_VAL); WordPutTblPr(tblPr,tableProperties,NULL,put->conv->mainSection,style != NULL ? style->styleId : NULL); const char *newJc = DFGetChildAttribute(tblPr,WORD_JC,WORD_VAL); double tableWidthPct = 100; if (CSSGet(tableProperties,"width") != NULL) { CSSLength length = CSSLengthFromString(CSSGet(tableProperties,"width")); if (CSSLengthIsValid(length) && (length.units == UnitsPct)) tableWidthPct = length.value; } double contentWidthPts = WordSectionContentWidthPts(put->conv->mainSection); double totalWidthPts = (contentWidthPts+padding.leftPts+padding.rightPts)*(tableWidthPct/100.0); while (tblGrid->first != NULL) DFRemoveNode(tblGrid->first); for (unsigned int i = 0; i < abstractStructure->cols; i++) { DFNode *gridCol = DFCreateChildElement(tblGrid,WORD_GRIDCOL); double colWidthPct = DFTablePctWidthForCol(abstractStructure,i); double colWidthPts = totalWidthPts*colWidthPct/100.0; int colWidthTwips = (int)round(colWidthPts*20); DFFormatAttribute(gridCol,WORD_W,"%d",colWidthTwips); } DFAppendChild(concrete,tblPr); DFAppendChild(concrete,tblGrid); for (unsigned int row = 0; row < abstractStructure->rows; row++) { DFNode *htmlTr = DFTableGetRowElement(abstractStructure,row); DFNode *wordTr = concreteRowForAbstractRow(put,htmlTr); updateTrJc(wordTr,oldJc,newJc); DFAppendChild(concrete,wordTr); unsigned int col = 0; while (col < abstractStructure->cols) { DFCell *cell = DFTableGetCell(abstractStructure,row,col); assert(cell != NULL); DFNode *tc = WordConverterGetConcrete(put,cell->element); if ((tc == NULL) || (row != cell->row)) tc = DFCreateElement(concrete->doc,WORD_TC); DFAppendChild(wordTr,tc); if (cell->row == row) WordTcPut(put,cell->element,tc);; const char *vMerge = NULL; if (cell->rowSpan > 1) { if (row == cell->row) vMerge = "restart"; else vMerge = "continue"; } DFNode *tcPr = DFChildWithTag(tc,WORD_TCPR); if (tcPr == NULL) tcPr = DFCreateElement(concrete->doc,WORD_TCPR); // Make sure tcPr comes first DFInsertBefore(tc,tcPr,tc->first); WordPutTcPr2(tcPr,cell->colSpan,vMerge); const char *inlineCSSText = DFGetAttribute(cell->element,HTML_STYLE); CSSProperties *innerCellProperties = CSSPropertiesNewWithString(inlineCSSText); if ((row == cell->row) && (totalWidthPts > 0)) { double spannedWidthPct = 0; for (unsigned int c = col; c < col + cell->colSpan; c++) spannedWidthPct += DFTablePctWidthForCol(abstractStructure,c); char buf[100]; CSSPut(innerCellProperties,"width",DFFormatDoublePct(buf,100,spannedWidthPct)); } WordPutTcPr1(tcPr,innerCellProperties); int haveBlockLevelElement = 0; for (DFNode *tcChild = tc->first; tcChild != NULL; tcChild = tcChild->next) { if (WordBlockLevelLens.isVisible(put,tcChild)) haveBlockLevelElement = 1; } // Every cell must contain at least one block-level element if (!haveBlockLevelElement) { DFNode *p = DFCreateElement(concrete->doc,WORD_P); DFAppendChild(tc,p); } col += cell->colSpan; CSSPropertiesRelease(innerCellProperties); } } free(selector); DFTableRelease(abstractStructure); CSSPropertiesRelease(tableProperties); CSSPropertiesRelease(cellProperties); }
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; }