| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "DFPlatform.h" |
| #include "WordNumPr.h" |
| #include "WordNumbering.h" |
| #include "WordSheet.h" |
| #include "CSS.h" |
| #include "DFString.h" |
| #include "DFCommon.h" |
| #include <assert.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| static char *parseLvlText(const char *input, WordConcreteNum *num) |
| { |
| DFBuffer *result = DFBufferNew(); |
| size_t len = strlen(input); |
| size_t pos = 0; |
| |
| int endsWithWhitespace = 0; |
| size_t start = 0; |
| while (1) { |
| if ((pos >= len) || (input[pos] == '%')) { |
| if (pos > start) { |
| char *str = DFSubstring(input,start,pos); |
| if (result->len > 0) |
| DFBufferFormat(result," "); |
| char *quotedStr = DFQuote(str); |
| DFBufferFormat(result,"%s",quotedStr); |
| free(quotedStr); |
| endsWithWhitespace = (strlen(str) > 0) && isspace(str[strlen(str)-1]); |
| free(str); |
| } |
| start = pos; |
| } |
| if (pos >= len) |
| break; |
| if ((input[pos] == '%') && (pos+1 < len) && |
| (input[pos+1] >= '1') && (input[pos+1] <= '6')) { |
| int ilvl = input[pos+1] - '1'; |
| |
| if (result->len > 0) |
| DFBufferFormat(result," "); |
| char *counterName = DFFormatString("h%d",ilvl+1); |
| |
| WordNumLevel *numLevel = WordConcreteNumGetLevel(num,ilvl); |
| const char *type = (numLevel != NULL) ? WordNumLevelToListStyleType(numLevel) : NULL; |
| if ((type != NULL) && !DFStringEquals(type,"decimal")) |
| DFBufferFormat(result,"counter(%s,%s)",counterName,type); |
| else |
| DFBufferFormat(result,"counter(%s)",counterName); |
| free(counterName); |
| endsWithWhitespace = 0; |
| |
| pos += 2; |
| start = pos; |
| } |
| else { |
| pos++; |
| } |
| } |
| |
| if ((result->len > 0) && !endsWithWhitespace) |
| DFBufferFormat(result," \" \""); |
| char *str = xstrdup(result->data); |
| DFBufferRelease(result); |
| return str; |
| } |
| |
| void WordGetNumPrStyle(DFNode *numPr, CSSStyle *style, WordConverter *converter) |
| { |
| DFNode *ilvlElem = DFChildWithTag(numPr,WORD_ILVL); |
| DFNode *numIdElem = DFChildWithTag(numPr,WORD_NUMID); |
| const char *ilvlStr = (ilvlElem != NULL) ? DFGetAttribute(ilvlElem,WORD_VAL) : NULL; |
| const char *numIdStr = (numIdElem != NULL) ? DFGetAttribute(numIdElem,WORD_VAL) : NULL; |
| if (numIdStr == NULL) |
| return; |
| |
| int ilvl = (ilvlStr != NULL) ? atoi(ilvlStr) : 0; |
| WordConcreteNum *num = WordNumberingConcreteWithId(converter->numbering,numIdStr); |
| if (num == NULL) { |
| return; |
| } |
| |
| CSSProperties *before = CSSStyleBefore(style); |
| CSSProperties *during = CSSStyleRule(style); |
| |
| WordNumLevel *level = WordConcreteNumGetLevel(num,ilvl); |
| if ((level != NULL) && (level->lvlText != NULL)) { |
| char *parsed = parseLvlText(level->lvlText,num); |
| CSSPut(before,"content",parsed); |
| free(parsed); |
| } |
| |
| char *mainCounterName = DFFormatString("h%d",ilvl+1); |
| CSSPut(during,"counter-increment",mainCounterName); |
| free(mainCounterName); |
| |
| DFBuffer *reset = DFBufferNew(); |
| for (int i = ilvl+1; i <= 5; i++) { |
| char *counterName = DFFormatString("h%d",i+1); |
| if (i == ilvl+1) |
| DFBufferFormat(reset,"%s",counterName); |
| else |
| DFBufferFormat(reset," %s",counterName); |
| free(counterName); |
| } |
| CSSPut(during,"counter-reset",reset->data); |
| DFBufferRelease(reset); |
| } |
| |
| typedef struct WordNumInfo WordNumInfo; |
| |
| struct WordNumInfo { |
| char *cssType; // Value range: CSS list-style-type |
| char *cssLvlText; // Values: Word's lvlText format |
| char *wordNumId; |
| char *wordIlvl; |
| WordNumLevel *wordLevel; |
| }; |
| |
| static WordNumInfo *WordNumInfoNew(void) |
| { |
| return (WordNumInfo *)xcalloc(1,sizeof(WordNumInfo)); |
| } |
| |
| static void WordNumInfoFree(WordNumInfo *info) |
| { |
| free(info->cssType); |
| free(info->cssLvlText); |
| free(info->wordNumId); |
| free(info->wordIlvl); |
| free(info); |
| } |
| |
| void updateNumbering(WordConverter *converter, CSSSheet *cssSheet) |
| { |
| DFHashTable *infoByStyleId = DFHashTableNew(NULL,(DFFreeFunction)WordNumInfoFree); |
| WordSheet *wordSheet = converter->styles; |
| |
| int cssLevelNumbered[6]; |
| int wordLevelNumbered[6]; |
| for (int i = 1; i <= 6; i++) { |
| cssLevelNumbered[i-1] = 0; |
| wordLevelNumbered[i-1] = 0; |
| } |
| |
| const char **cssSelectors = CSSSheetCopySelectors(cssSheet); |
| for (int i = 0; cssSelectors[i]; i++) { |
| const char *selector = cssSelectors[i]; |
| if (WordStyleFamilyForSelector(selector) != StyleFamilyParagraph) |
| continue; |
| |
| WordNumInfo *info = WordNumInfoNew(); |
| DFHashTableAdd(infoByStyleId,selector,info); |
| |
| CSSStyle *cssStyle = CSSSheetLookupSelector(cssSheet,selector,0,0); |
| if ((cssStyle != NULL) && (CSSGet(CSSStyleBefore(cssStyle),"content") != NULL)) { |
| char *elementName = CSSSelectorCopyElementName(selector); |
| |
| if ((cssStyle->headingLevel >= 1) && (cssStyle->headingLevel <= 6)) |
| cssLevelNumbered[cssStyle->headingLevel-1] = 1;; |
| |
| DFArray *contentParts = CSSParseContent(CSSGet(CSSStyleBefore(cssStyle),"content")); |
| DFBuffer *format = DFBufferNew(); |
| for (size_t partIndex = 0; partIndex < DFArrayCount(contentParts); partIndex++) { |
| ContentPart *part = DFArrayItemAt(contentParts,partIndex); |
| if (part->type == ContentPartCounter) { |
| if (DFStringEquals(part->value,elementName)) { |
| free(info->cssType); |
| if (part->arg != NULL) |
| info->cssType = xstrdup(part->arg); |
| else |
| info->cssType = xstrdup("decimal"); |
| } |
| if ((strlen(part->value) == 2) && (part->value[0] == 'h')) |
| DFBufferFormat(format,"%%%c",part->value[1]); |
| } |
| else if (part->type == ContentPartString) { |
| // FIXME: Need test case for string that actually contains % |
| char *noPercent = DFStringReplace(part->value,"%",""); |
| DFBufferFormat(format,"%s",noPercent); |
| free(noPercent); |
| } |
| } |
| DFArrayRelease(contentParts); |
| |
| if ((format->len > 0) && isspace(format->data[format->len-1])) { |
| format->data[format->len-1] = '\0'; |
| format->len--; |
| } |
| |
| info->cssLvlText = xstrdup(format->data); |
| free(elementName); |
| DFBufferRelease(format); |
| } |
| |
| WordStyle *wordStyle = WordSheetStyleForSelector(wordSheet,selector); |
| if (wordStyle != NULL) { |
| DFNode *pPr = DFChildWithTag(wordStyle->element,WORD_PPR); |
| DFNode *numPr = DFChildWithTag(pPr,WORD_NUMPR); |
| const char *numId = DFGetChildAttribute(numPr,WORD_NUMID,WORD_VAL); |
| const char *ilvl = DFGetChildAttribute(numPr,WORD_ILVL,WORD_VAL); |
| if (numId != NULL) { |
| if (ilvl == NULL) |
| ilvl = "0";; |
| WordConcreteNum *concreteNum = WordNumberingConcreteWithId(converter->numbering,numId); |
| if (concreteNum != NULL) { |
| WordNumLevel *level = WordConcreteNumGetLevel(concreteNum,atoi(ilvl)); |
| if (level != NULL) { |
| info->wordNumId = xstrdup(numId); |
| info->wordIlvl = xstrdup(ilvl); |
| info->wordLevel = level; |
| |
| int ilvlValue = atoi(ilvl); |
| if ((ilvlValue >= 0) && (ilvlValue <= 5)) |
| wordLevelNumbered[ilvlValue] = 1; |
| } |
| } |
| } |
| } |
| } |
| free(cssSelectors); |
| |
| // For any CSS heading styles that are missing either a type or level text, clear the |
| // corresponding word documents, so these are removed from styles.xml if they previously |
| // existed. |
| cssSelectors = DFHashTableCopyKeys(infoByStyleId); |
| for (int i = 0; cssSelectors[i]; i++) { |
| const char *selector = cssSelectors[i]; |
| WordNumInfo *info = DFHashTableLookup(infoByStyleId,selector); |
| if ((info != NULL) && (info->cssLvlText == NULL)) { |
| free(info->wordNumId); |
| free(info->wordIlvl); |
| info->wordLevel = NULL; |
| info->wordNumId = NULL; |
| info->wordIlvl = NULL; |
| } |
| } |
| free(cssSelectors); |
| |
| int totalCSSNumbered = 0; |
| int totalWordNumbered = 0; |
| |
| for (int i = 0; i <= 5; i++) { |
| if (cssLevelNumbered[i]) |
| totalCSSNumbered++; |
| if (wordLevelNumbered[i]) |
| totalWordNumbered++; |
| } |
| |
| if ((totalCSSNumbered > 0) && (totalWordNumbered < 6)) { |
| // We don't have all 6 heading levels. Recreate everything |
| WordAbstractNum *abstractNum = WordNumberingCreateAbstractNum(converter->numbering); |
| WordConcreteNum *concreteNum = WordNumberingAddConcreteWithAbstract(converter->numbering,abstractNum); |
| char *prevType = NULL; |
| char *prevLvlText = NULL; |
| |
| DFHashTable *selectorsByLevel = DFHashTableNew(NULL,NULL); |
| const char **allSelectors = CSSSheetCopySelectors(cssSheet); |
| for (int i = 0; allSelectors[i]; i++) { |
| CSSStyle *style = CSSSheetLookupSelector(cssSheet,allSelectors[i],0,0); |
| if ((style->headingLevel >= 1) && (style->headingLevel <= 6)) { |
| int level = style->headingLevel - 1; |
| SelectorList *item = (SelectorList *)xcalloc(1,sizeof(SelectorList)); |
| item->selector = xstrdup(style->selector); |
| item->next = DFHashTableLookupInt(selectorsByLevel,level); |
| DFHashTableAddInt(selectorsByLevel,level,item); |
| } |
| } |
| free(allSelectors); |
| |
| DFHashTable *numStylesByLevel = DFHashTableNew((DFCopyFunction)CSSStyleRetain,(DFFreeFunction)CSSStyleRelease); |
| |
| for (int i = 0; i < 6; i++) { |
| SelectorList *list = DFHashTableLookupInt(selectorsByLevel,i); |
| SelectorList *next; |
| for (SelectorList *item = list; item != NULL; item = next) { |
| next = item->next; |
| CSSStyle *style = CSSSheetLookupSelector(cssSheet,item->selector,0,0); |
| // FIXME: need to do this comparison based on lvlText |
| if ((CSSGet(CSSStyleBefore(style),"content") != NULL) && |
| !DFStringEquals(CSSGet(CSSStyleBefore(style),"content"),"none") && |
| !DFStringEquals(CSSGet(CSSStyleBefore(style),"content"),"\"\"")) { |
| DFHashTableAddInt(numStylesByLevel,i,style); |
| } |
| free(item->selector); |
| free(item); |
| } |
| } |
| |
| for (int i = 0; i < 6; i++) { |
| CSSStyle *style = DFHashTableLookupInt(numStylesByLevel,i); |
| char *curType = NULL; |
| char *curLvlText = NULL; |
| WordNumInfo *info = NULL; |
| |
| |
| if (style != NULL) { |
| info = DFHashTableLookup(infoByStyleId,style->selector); |
| if (info == NULL) { |
| info = WordNumInfoNew(); |
| DFHashTableAdd(infoByStyleId,style->selector,info); |
| } |
| curType = xstrdup(info->cssType); |
| curLvlText = xstrdup(info->cssLvlText); |
| } |
| |
| |
| if (curType == NULL) |
| curType = (prevType != NULL) ? xstrdup(prevType) : xstrdup("decimal"); |
| |
| if ((curLvlText == NULL) || (strlen(curLvlText) == 0)) { |
| free(curLvlText); |
| if ((prevLvlText == NULL) || (strlen(prevLvlText) == 0)) |
| curLvlText = DFFormatString("%%%d",i+1); |
| else |
| curLvlText = DFFormatString("%s.%%%d",prevLvlText,i+1); |
| } |
| |
| WordNumLevel *numLevel = WordNumberingCreateLevel(converter->numbering,curType,curLvlText,i,0); |
| DFAppendChild(abstractNum->element,numLevel->element); |
| WordAbstractNumAddLevel(abstractNum,numLevel); |
| |
| if (info != NULL) { |
| info->wordLevel = numLevel; |
| assert(info->wordLevel != NULL); |
| free(info->wordNumId); |
| free(info->wordIlvl); |
| info->wordNumId = xstrdup(concreteNum->numId); |
| info->wordIlvl = DFFormatString("%d",i); |
| } |
| |
| free(prevType); |
| free(prevLvlText); |
| prevType = curType; |
| prevLvlText = curLvlText; |
| } |
| free(prevType); |
| free(prevLvlText); |
| DFHashTableRelease(selectorsByLevel); |
| DFHashTableRelease(numStylesByLevel); |
| } |
| |
| cssSelectors = DFHashTableCopyKeys(infoByStyleId); |
| for (int i = 0; cssSelectors[i]; i++) { |
| const char *selector = cssSelectors[i]; |
| WordNumInfo *info = DFHashTableLookup(infoByStyleId,selector); |
| CSSStyle *cssStyle = CSSSheetLookupSelector(cssSheet,selector,0,0); |
| if (cssStyle != NULL) { |
| CSSPut(CSSStyleRule(cssStyle),"-word-numId",info->wordNumId); |
| if (DFStringEquals(info->wordIlvl,"0")) |
| CSSPut(CSSStyleRule(cssStyle),"-word-ilvl",NULL); |
| else |
| CSSPut(CSSStyleRule(cssStyle),"-word-ilvl",info->wordIlvl); |
| } |
| } |
| free(cssSelectors); |
| DFHashTableRelease(infoByStyleId); |
| } |