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);
}
Beispiel #2
0
static void expandRecursive(DFNode *node)
{
    DFNode *next;
    for (DFNode *child = node->first; child != NULL; child = next) {
        next = child->next;
        expandRecursive(child);
    }

    if (node->tag == WORD_BOOKMARK) {
        const char *bookmarkId = DFGetAttribute(node,WORD_ID);
        const char *bookmarkName = DFGetAttribute(node,WORD_NAME);
        if (bookmarkId == NULL)
            bookmarkId = "";
        if (bookmarkName == NULL)
            bookmarkName = "";
        DFNode *startElem = DFCreateElement(node->doc,WORD_BOOKMARKSTART);
        DFNode *endElem = DFCreateElement(node->doc,WORD_BOOKMARKEND);
        DFSetAttribute(startElem,WORD_ID,bookmarkId);
        DFSetAttribute(startElem,WORD_NAME,bookmarkName);
        DFSetAttribute(endElem,WORD_ID,bookmarkId);
        DFInsertBefore(node->parent,startElem,node);
        DFInsertBefore(node->parent,endElem,node->next);
        DFRemoveNodeButKeepChildren(node);
    }
}
Beispiel #3
0
static WordBookmark *createBookmark(WordConverter *converter)
{
    WordBookmark *bookmark = WordObjectsAddBookmark(converter->objects);
    DFNode *bookmarkSpan = DFCreateElement(converter->package->document,HTML_SPAN);
    DFSetAttribute(bookmarkSpan,HTML_CLASS,DFBookmarkClass);
    DFSetAttribute(bookmarkSpan,WORD_NAME,bookmark->bookmarkName);
    DFSetAttribute(bookmarkSpan,WORD_ID,bookmark->bookmarkId);
    bookmark->element = bookmarkSpan;
    return bookmark;
}
static WordBookmark *createBookmark(WordPutData *put)
{
    WordBookmark *bookmark = WordObjectsAddBookmark(put->conv->objects);
    DFNode *bookmarkSpan = DFCreateElement(put->conv->html,HTML_SPAN);
    DFSetAttribute(bookmarkSpan,HTML_CLASS,DFBookmarkClass);
    DFSetAttribute(bookmarkSpan,WORD_NAME,bookmark->bookmarkName);
    DFSetAttribute(bookmarkSpan,WORD_ID,bookmark->bookmarkId);
    bookmark->element = bookmarkSpan;
    return bookmark;
}
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;
    }
}
Beispiel #6
0
void ODFManifestAddEntry(ODFManifest *manifest, const char *path, const char *mediaType,
                         const char *version)
{
    DFNode *entry = DFHashTableLookup(manifest->entriesByPath,path);
    if (entry == NULL) {
        entry = DFCreateChildElement(manifest->doc->root,MF_FILE_ENTRY);
        DFHashTableAdd(manifest->entriesByPath,path,entry);
    }
    DFSetAttribute(entry,MF_FULL_PATH,path);
    DFSetAttribute(entry,MF_MEDIA_TYPE,mediaType);
    DFSetAttribute(entry,MF_VERSION,version);
    if (!strcmp(path,"") && (version != NULL))
        DFSetAttribute(manifest->doc->root,MF_VERSION,version);
}
Beispiel #7
0
static DFNode *createAbstractPlaceholder(WordGetData *get, const char *placeholderText, DFNode *concrete)
{
    DFNode *span = WordConverterCreateAbstract(get,HTML_SPAN,concrete);
    DFSetAttribute(span,HTML_CLASS,DFPlaceholderClass);
    DFNode *text = DFCreateTextNode(get->conv->html,placeholderText);
    DFAppendChild(span,text);
    return span;
}
static void updateDefault(CSSStyle *style, DFNode *element, CSSSheet *styleSheet, WordConverter *converter)
{
    StyleFamily family = WordStyleFamilyForSelector(style->selector);
    if (CSSSheetDefaultStyleForFamily(styleSheet,family) == style)
        DFSetAttribute(element,WORD_DEFAULT,"1");
    else
        DFRemoveAttribute(element,WORD_DEFAULT);
}
Beispiel #9
0
static DFNode *WordBookmarkGet(WordGetData *get, DFNode *concrete)
{
    if (!WordBookmarkIsVisible2(concrete))
        return NULL;
    DFNode *abstract = WordConverterCreateAbstract(get,HTML_SPAN,concrete);
    DFSetAttribute(abstract,HTML_CLASS,DFBookmarkClass);
    WordContainerGet(get,&WordParagraphContentLens,abstract,concrete);
    return abstract;
}
static DFNode *WordFieldCreate(WordPutData *put, DFNode *abstract)
{
    DFNode *concrete = DFCreateElement(put->contentDoc,WORD_FLDSIMPLE);
    // fldSimple elements are required to have an instr attribute (even if it's empty), so set
    // it here in case update doesn't change it for some reason
    DFSetAttribute(concrete,WORD_INSTR,"");
    WordFieldPut(put,abstract,concrete);
    put->conv->haveFields = 1;
    return concrete;
}
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;
    }
}
Beispiel #12
0
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 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);
}
Beispiel #14
0
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;
}
Beispiel #15
0
DFNode *WordConverterCreateAbstract(WordGetData *get, Tag tag, DFNode *concrete)
{
    DFNode *element = DFCreateElement(get->conv->html,tag);
    if (concrete != NULL) {
        char *idStr;
        if (concrete->doc == get->conv->package->document)
            idStr = DFFormatString("%s%u",get->conv->idPrefix,concrete->seqNo);
        else
            idStr = DFFormatString("%s%u-%s",get->conv->idPrefix,concrete->seqNo,DFNodeName(concrete->doc->root));
        DFSetAttribute(element,HTML_ID,idStr);
        free(idStr);
    }
    return element;
}
Beispiel #16
0
static DFNode *WordRunGet(WordGetData *get, DFNode *concrete)
{
    assert(concrete->tag == WORD_R);

    // First check the run to see if it's a footnote or endnote reference. These need to be handled specially,
    // as we place the actual content of the footnote or endnote in-line in the HTML output.
    DFNode *note = WordRunGetNote(get,concrete);
    if (note != NULL)
        return note;

    // If we didn't find a footnote or endnote reference, treat it as a normal content run
    assert(concrete->tag == WORD_R);
    DFNode *rPr = DFChildWithTag(concrete,WORD_RPR);
    CSSProperties *properties = CSSPropertiesNew();
    const char *styleId = NULL;
    if (rPr != NULL)
        WordGetRPr(rPr,properties,&styleId,get->conv->theme);;

    const char *selector = WordSheetSelectorForStyleId(get->conv->styles,"character",styleId);
    Tag elementName = (selector == NULL) ? HTML_SPAN : CSSSelectorGetTag(selector);
    char *className = (selector == NULL) ? NULL : CSSSelectorCopyClassName(selector);
    DFNode *abstract = WordConverterCreateAbstract(get,elementName,concrete);
    DFSetAttribute(abstract,HTML_CLASS,className);
    free(className);

    DFHashTable *collapsed = CSSCollapseProperties(properties);
    char *styleValue = CSSSerializeProperties(collapsed);
    DFHashTableRelease(collapsed);
    if (strlen(styleValue) > 0)
        DFSetAttribute(abstract,HTML_STYLE,styleValue);
    free(styleValue);

    CSSPropertiesRelease(properties);

    WordContainerGet(get,&WordRunContentLens,abstract,concrete);
    return abstract;
}
DFNode *fromTidyNode(DFDocument *htmlDoc, TidyDoc tdoc, TidyNode tnode)
{
    switch (tidyNodeGetType(tnode)) {
        case TidyNode_Text: {
            char *value = copyTidyNodeValue(tnode,tdoc);
            DFNode *result = DFCreateTextNode(htmlDoc,value);
            free(value);
            return result;
        }
        case TidyNode_CDATA:
            break;
        case TidyNode_Comment:
            break;
        case TidyNode_Root:
            printf("Have root\n");
            break;
        default: {
            const char *name = tidyNodeGetName(tnode);
            if (name == NULL) {
                printf("NULL name for %p, type %d\n",tnode,tidyNodeGetType(tnode));
                return NULL;
            }
            const NamespaceDecl *namespaceDecl = DFNameMapNamespaceForID(htmlDoc->map,NAMESPACE_HTML);
            Tag tag = DFNameMapTagForName(htmlDoc->map,namespaceDecl->namespaceURI,name);
            DFNode *element = DFCreateElement(htmlDoc,tag);

            for (TidyAttr tattr = tidyAttrFirst(tnode); tattr != NULL; tattr = tidyAttrNext(tattr)) {
                const char *name = tidyAttrName(tattr);
                const char *value = tidyAttrValue(tattr);
                if (value == NULL) // Can happen in case of the empty string
                    value = "";;
                Tag attrTag = DFNameMapTagForName(htmlDoc->map,namespaceDecl->namespaceURI,name);
                DFSetAttribute(element,attrTag,value);
            }

            for (TidyNode tchild = tidyGetChild(tnode); tchild != NULL; tchild = tidyGetNext(tchild)) {
                DFNode *child = fromTidyNode(htmlDoc,tdoc,tchild);
                if (child != NULL)
                    DFAppendChild(element,child);
            }
            return element;
        }
    }
    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 DFNode *WordTcCreateAbstractNode(WordGetData *get, DFNode *concrete)
{
    DFNode *td = WordConverterCreateAbstract(get,HTML_TD,concrete);

    CSSProperties *properties = CSSPropertiesNew();
    DFNode *tcPr = DFChildWithTag(concrete,WORD_TCPR);
    if (tcPr != NULL)
        WordGetTcPr(tcPr,properties);;
    DFHashTable *collapsed = CSSCollapseProperties(properties);
    char *styleValue = CSSSerializeProperties(collapsed);
    DFHashTableRelease(collapsed);
    if (strlen(styleValue) > 0)
        DFSetAttribute(td,HTML_STYLE,styleValue);
    free(styleValue);

    CSSPropertiesRelease(properties);
    return td;
}
Beispiel #20
0
static DFNode *WordDocumentGet(WordGetData *get, DFNode *concrete)
{
    if (concrete->tag != WORD_DOCUMENT)
        return NULL;

    DFNode *html = WordConverterCreateAbstract(get,HTML_HTML,concrete);
    DFNode *head = WordConverterCreateAbstract(get,HTML_HEAD,NULL);
    DFAppendChild(html,head);
    DFNode *meta = WordConverterCreateAbstract(get,HTML_META,NULL);
    DFAppendChild(head,meta);
    DFSetAttribute(meta,HTML_CHARSET,"utf-8");

    DFNode *wordBody = DFChildWithTag(concrete,WORD_BODY);
    if (wordBody != NULL) {
        DFNode *htmlBody = WordBodyLens.get(get,wordBody);
        DFAppendChild(html,htmlBody);
    }
    return html;
}
Beispiel #21
0
static void SAXStartElement(void *ctx, const xmlChar *fullname, const xmlChar **atts)
{
    DFSAXParser *parser = (DFSAXParser *)ctx;
    const NamespaceDecl *namespaceDecl = DFNameMapNamespaceForID(parser->document->map,NAMESPACE_HTML);
    Tag tag = DFNameMapTagForName(parser->document->map,namespaceDecl->namespaceURI,(const char *)fullname);
    DFNode *element = DFCreateElement(parser->document,tag);
    if (atts != NULL) {
        for (int i = 0; atts[i] != NULL; i += 2) {
            const xmlChar *name = atts[i];
            const xmlChar *value = atts[i+1];
            Tag attrTag = DFNameMapTagForName(parser->document->map,namespaceDecl->namespaceURI,(const char *)name);
            DFSetAttribute(element,attrTag,(const char *)value);
        }
    }
    DFAppendChild(parser->parent,element);
    parser->parent = element;
    if (parser->document->root == NULL)
        parser->document->root = element;
}
static void replaceField(WordSimplification *simp)
{
    assert(simp->instrText != NULL);
    assert(simp->beginNode != NULL);
    assert(simp->endNode != NULL);

    if ((simp->beginNode->parent->tag == WORD_R) && (simp->endNode->parent->tag == WORD_R)) {
        DFNode *beginRun = simp->beginNode->parent;

        DFNode *simple = DFCreateElement(simp->beginNode->doc,WORD_FLDSIMPLE);
        DFSetAttribute(simple,WORD_INSTR,simp->instrText->data);
        DFInsertBefore(beginRun->parent,simple,beginRun);

        removeNodes(simp->beginNode,simp->endNode);
    }

    DFBufferRelease(simp->instrText);
    simp->instrText = NULL;
    simp->beginNode = NULL;
    simp->endNode = NULL;

    simp->haveFields = 1;
}
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 adjustMarginLeft(DFNode *element, double adjustPct, int noTextIndent)
{
    if ((element->tag != HTML_TABLE) && !HTML_isParagraphTag(element->tag))
        return;;

    const char *cssText = DFGetAttribute(element,HTML_STYLE);
    CSSProperties *properties = CSSPropertiesNewWithString(cssText);

    double oldMarginLeft = 0;
    if (CSSGet(properties,"margin-left") != NULL) {
        CSSLength length = CSSLengthFromString(CSSGet(properties,"margin-left"));
        if (CSSLengthIsValid(length) && (length.units == UnitsPct))
            oldMarginLeft = length.value;

        if (CSSGet(properties,"width") != NULL) {
            CSSLength length = CSSLengthFromString(CSSGet(properties,"width"));
            if (CSSLengthIsValid(length) && (length.units == UnitsPct)) {
                double oldWidth = length.value;
                double newWidth = oldWidth + oldMarginLeft;
                char buf[100];
                CSSPut(properties,"width",DFFormatDoublePct(buf,100,newWidth));
            }
        }
    }

    double oldTextIndent = 0;
    if (CSSGet(properties,"text-indent") != NULL) {
        CSSLength length = CSSLengthFromString(CSSGet(properties,"text-indent"));
        if (CSSLengthIsValid(length) && (length.units == UnitsPct))
            oldTextIndent = length.value;
    }

    double newMarginLeft = oldMarginLeft + adjustPct;
    double newTextIndent = oldTextIndent;

    if (newMarginLeft < 0)
        newMarginLeft = 0;
    if (fabs(newMarginLeft) >= 0.01) {
        char buf[100];
        CSSPut(properties,"margin-left",DFFormatDoublePct(buf,100,newMarginLeft));
    }
    else {
        CSSPut(properties,"margin-left",NULL);
    }

    if (noTextIndent) {
        CSSPut(properties,"text-indent",NULL);
    }
    else if (newTextIndent < -newMarginLeft) {
        // Don't allow negative text-indent
        newTextIndent = -newMarginLeft;
        if (fabs(newTextIndent) >= 0.01) {
            char buf[100];
            CSSPut(properties,"text-indent",DFFormatDoublePct(buf,100,newTextIndent));
        }
        else {
            CSSPut(properties,"text-indent",NULL);
        }
    }

    char *propertiesText = CSSPropertiesCopyDescription(properties);
    if (strlen(propertiesText) == 0)
        DFRemoveAttribute(element,HTML_STYLE);
    else
        DFSetAttribute(element,HTML_STYLE,propertiesText);
    free(propertiesText);

    CSSPropertiesRelease(properties);
}
Beispiel #25
0
void DFVFormatAttribute(DFNode *element, Tag tag, const char *format, va_list ap)
{
    char *value = DFVFormatString(format,ap);
    DFSetAttribute(element,tag,value);
    free(value);
}
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 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);
}
Beispiel #28
0
static void SAXStartElementNS(void *ctx, const xmlChar *localname,
                              const xmlChar *prefix, const xmlChar *URI,
                              int nb_namespaces, const xmlChar **namespaces,
                              int nb_attributes, int nb_defaulted, const xmlChar **attributes)
{
    DFSAXParser *parser = (DFSAXParser *)ctx;

    if (parser->ignoreDepth > 0) {
        parser->ignoreDepth++;
        return;
    }

    for (int i = 0; i < nb_namespaces; i++) {
        const xmlChar *nsPrefix = namespaces[i*2];
        const xmlChar *nsURI = namespaces[i*2+1];
        DFNameMapFoundNamespace(parser->document->map,(const char *)nsURI,(const char *)nsPrefix);
    }

    Tag tag = DFNameMapTagForName(parser->document->map,(const char *)URI,(const char *)localname);

    if (parser->compatibility != NULL) {
        const TagDecl *tagDecl = DFNameMapNameForTag(parser->document->map,tag);
        MCAction action = DFMarkupCompatibilityLookup(parser->compatibility,tagDecl->namespaceID,tag,1);
        if (action == MCActionIgnore) {
            parser->ignoreDepth++;
            return;
        }
    }

    if (parser->compatibility != NULL) {
        DFMarkupCompatibilityPush(parser->compatibility,nb_namespaces,(const char **)namespaces,parser->document->map);
    }

    DFNode *element = DFCreateElement(parser->document,tag);
    for (int i = 0; i < nb_attributes; i++) {
        const xmlChar *attrLocalName = attributes[i*5+0];
        const xmlChar *attrURI = attributes[i*5+2];
        const xmlChar *attrValueStart = attributes[i*5+3];
        const xmlChar *attrValueEnd = attributes[i*5+4];
        unsigned long attrValueLen = (unsigned long)(attrValueEnd - attrValueStart);

        Tag attrTag = DFNameMapTagForName(parser->document->map,(const char *)attrURI,(const char *)attrLocalName);
        const TagDecl *attrTagDecl = DFNameMapNameForTag(parser->document->map,attrTag);
        char *attrValue = (char *)xmalloc(attrValueLen+1);
        memcpy(attrValue,attrValueStart,attrValueLen);
        attrValue[attrValueLen] = '\0';
        if (parser->compatibility != NULL) {
            switch (attrTag) {
                case MC_IGNORABLE:
                case MC_PROCESSCONTENT:
                case MC_MUSTUNDERSTAND:
                    DFMarkupCompatibilityProcessAttr(parser->compatibility,attrTag,attrValue,parser->document->map);
                    break;
                default: {
                    MCAction action = DFMarkupCompatibilityLookup(parser->compatibility,attrTagDecl->namespaceID,0,0);
                    if (action != MCActionIgnore)
                        DFSetAttribute(element,attrTag,attrValue);
                    break;
                }
            }
        }
        else {
            DFSetAttribute(element,attrTag,attrValue);
        }
        free(attrValue);
    }

    DFAppendChild(parser->parent,element);
    parser->parent = element;
    if (parser->document->root == NULL)
        parser->document->root = element;
}
static void fixWordLists(DFNode *node, WordConverter *conv)
{
    for (DFNode *child = node->first; child != NULL; child = child->next)
        fixWordLists(child,conv);

    int haveParagraphs = 0;
    for (DFNode *child = node->first; child != NULL; child = child->next) {
        if (child->tag == WORD_P) {
            haveParagraphs = 1;
            break;
        }
    }

    if (!haveParagraphs)
        return;

    int createdHashTables = 0;
    DFHashTable *replacementNumIds = NULL;
    DFHashTable *itemNoByListKey = NULL;
    DFHashTable *lastNumIdByIlvl = NULL;
    DFHashTable *itemNoByIlvl = NULL;
    int maxIlvl = -1;

    for (DFNode *child = node->first; child != NULL; child = child->next) {
        if (child->tag != WORD_P)
            continue;
        DFNode *pPrElem = DFChildWithTag(child,WORD_PPR);
        DFNode *numPrElem = DFChildWithTag(pPrElem,WORD_NUMPR);
        DFNode *numIdElem = DFChildWithTag(numPrElem,WORD_NUMID);
        DFNode *ilvlElem = DFChildWithTag(numPrElem,WORD_ILVL);
        const char *numId = DFGetAttribute(numIdElem,WORD_VAL);
        const char *ilvl = DFGetAttribute(ilvlElem,WORD_VAL);

        if ((numId == NULL) || (atoi(numId) == 0))
            continue;

        if (!createdHashTables) {
            replacementNumIds = DFHashTableNew((DFCopyFunction)xstrdup,free);
            itemNoByListKey = DFHashTableNew((DFCopyFunction)xstrdup,free);
            lastNumIdByIlvl = DFHashTableNew((DFCopyFunction)xstrdup,free);
            itemNoByIlvl = DFHashTableNew((DFCopyFunction)xstrdup,free);
            createdHashTables = 1;
        }

        if (ilvl == NULL)
            ilvl = "0";;

        WordConcreteNum *concreteNum = WordNumberingConcreteWithId(conv->numbering,numId); // may be NULL
        WordNumLevel *numLevel = WordConcreteNumGetLevel(concreteNum,atoi(ilvl)); // may be NULL

        const char *levelStart = NULL;

        if (numLevel != NULL) {
            for (DFNode *lvlChild = numLevel->element->first; lvlChild != NULL; lvlChild = lvlChild->next) {
                switch (lvlChild->tag) {
                    case WORD_START:
                        levelStart = DFGetAttribute(lvlChild,WORD_VAL);
                        break;
                }
            }
        }

        char *listKey = DFFormatString("%s:%s",numId,ilvl);
        char *itemNo = DFStrDup(DFHashTableLookup(itemNoByListKey,listKey));
        if (itemNo == NULL) {
            itemNo = xstrdup("1");

            if ((levelStart != NULL) && (atoi(levelStart) > 1) && (atoi(ilvl) <= maxIlvl)) {
                const char *prevNumId = DFHashTableLookup(lastNumIdByIlvl,ilvl);
                const char *prevItemNo = DFHashTableLookup(itemNoByIlvl,ilvl);

                if ((prevNumId != NULL) && (prevItemNo != NULL)) {
                    DFHashTableAdd(replacementNumIds,numId,prevNumId);
                    free(itemNo);
                    itemNo = DFFormatString("%d",atoi(prevItemNo)+1);
                }
            }
        }
        else {
            int intValue = atoi(itemNo);
            free(itemNo);
            itemNo = DFFormatString("%d",intValue+1);
        }
        DFHashTableAdd(itemNoByListKey,listKey,itemNo);

        const char *replNumId = DFHashTableLookup(replacementNumIds,numId);
        if (replNumId != NULL) {
            numId = replNumId;
            DFSetAttribute(numIdElem,WORD_VAL,numId);
        }

        DFHashTableAdd(lastNumIdByIlvl,ilvl,numId);
        DFHashTableAdd(itemNoByIlvl,ilvl,itemNo);
        maxIlvl = atoi(ilvl);
        free(listKey);
        free(itemNo);
    }
    DFHashTableRelease(replacementNumIds);
    DFHashTableRelease(itemNoByListKey);
    DFHashTableRelease(lastNumIdByIlvl);
    DFHashTableRelease(itemNoByIlvl);
}
Beispiel #30
0
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);
}