blob: b8e431da8cd35b1dcf797bd0ec9ceb72fcf9f861 [file] [log] [blame]
// 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);
}