Example #1
0
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);
}
Example #2
0
static void buildListMapFromHTML(WordPutData *put, DFNode *node)
{
    if (node->tag == HTML_P) {
        const char *htmlId = DFGetAttribute(node,CONV_LISTNUM);
        DFNode *conElem = (htmlId != NULL) ? WordConverterGetConcrete(put,node) : NULL;
        DFNode *pPrElem = (conElem != NULL) ? DFChildWithTag(conElem,WORD_PPR) : NULL;
        DFNode *numPrElem = (pPrElem != NULL) ? DFChildWithTag(pPrElem,WORD_NUMPR) : NULL;
        DFNode *numIdElem = (numPrElem != NULL) ? DFChildWithTag(numPrElem,WORD_NUMID) : NULL;
        const char *numId = (numIdElem != NULL) ? DFGetAttribute(numIdElem,WORD_VAL) : NULL;

        if (numId != NULL) {
            const char *existingHtmlId = DFHashTableLookup(put->htmlIdByNumId,numId);
            const char *existingNumId = DFHashTableLookup(put->numIdByHtmlId,htmlId);
            if ((existingHtmlId == NULL) && (existingNumId == NULL)) {
                DFHashTableAdd(put->htmlIdByNumId,numId,htmlId);
                DFHashTableAdd(put->numIdByHtmlId,htmlId,numId);

                WordConcreteNum *num = WordNumberingConcreteWithId(put->conv->numbering,numId);
                if (num != NULL)
                    num->referenceCount++;
            }
        }
    }

    for (DFNode *child = node->first; child != NULL; child = child->next)
        buildListMapFromHTML(put,child);
}
static int listProperties(WordConverter *conv, const char *numId, const char *ilvl, CSSProperties *properties)
{
    WordConcreteNum *num = WordNumberingConcreteWithId(conv->numbering,numId);
    if (num == NULL)
        return 0;;
    WordNumLevel *level = WordConcreteNumGetLevel(num,atoi(ilvl));
    if (level == NULL)
        return 0;;
    DFNode *pPr = DFChildWithTag(level->element,WORD_PPR);
    if (pPr == NULL)
        return 0;;
    const char *styleId = NULL;
    WordGetPPr(pPr,properties,&styleId,conv->mainSection);
    return 1;
}
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 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);
}
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);
}