blob: 2c47bc6038ff2aa0a2bae8542390bf9ca8152588 [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 "CSSClassNames.h"
#include "CSS.h"
#include "CSSProperties.h"
#include "CSSStyle.h"
#include "CSSSheet.h"
#include "DFDOM.h"
#include "DFXML.h"
#include "DFNameMap.h"
#include "DFString.h"
#include "DFHashTable.h"
#include "DFHTML.h"
#include "WordStyles.h"
#include "Word.h"
#include "DFCommon.h"
#include "DFPlatform.h"
#include <stdlib.h>
#include <string.h>
static const char *classPrefixForElementName(const char *elementName);
static void ensureStylesReferencedFromNode(DFNode *node, CSSSheet *styleSheet)
{
switch (node->tag) {
case HTML_H1:
case HTML_H2:
case HTML_H3:
case HTML_H4:
case HTML_H5:
case HTML_H6:
case HTML_P:
case HTML_FIGURE: {
char *selector = CSSMakeNodeSelector(node);
CSSSheetLookupSelector(styleSheet,selector,1,0);
free(selector);
break;
}
case HTML_A: {
if (HTML_nodeIsHyperlink(node)) {
CSSStyle *style = CSSSheetLookupElement(styleSheet,"span","Hyperlink",0,0);
if (style == NULL) {
style = CSSSheetLookupElement(styleSheet,"span","Hyperlink",1,0);
CSSPut(CSSStyleRule(style),"color","#0000FF");
CSSSetUnderline(CSSStyleRule(style),1);
CSSStyle *parent = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyCharacter);
if (parent != NULL)
CSSStyleSetParent(style,parent->selector);
}
}
break;
}
case HTML_TABLE: {
const char *className = DFGetAttribute(node,HTML_CLASS);
if (className == NULL) {
CSSStyle *dflt = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyTable);
int changed = 0;
CSSStyle *grid = WordSetupTableGridStyle(styleSheet,&changed);
DFSetAttribute(node,HTML_CLASS,grid->className);
// Copy over cell padding rules. These are not automatically inherited in Word, and must be
// explicitly duplicated for each descendant of the TableNormal style.
// Under normal circumstances, when the user has added a table in the editor, the TableGrid
// style will already be defined, with the appropriate padding rules set in the document's
// stylesheet, and StyleSheet.removeRedundantProperties will make sure that each table style
// keeps the padding properties. However if we are creating a HTML file that was not
// oringally created with the intention of being converted to a word document, the TableGrid
// style will not exist, and must be created. In this case (i.e. where changed is true) we
// have to explicitly copy these properties over.
if (changed) {
CSSPut(CSSStyleCell(grid),"padding-left",CSSGet(CSSStyleCell(dflt),"padding-left"));
CSSPut(CSSStyleCell(grid),"padding-right",CSSGet(CSSStyleCell(dflt),"padding-right"));
CSSPut(CSSStyleCell(grid),"padding-top",CSSGet(CSSStyleCell(dflt),"padding-top"));
CSSPut(CSSStyleCell(grid),"padding-bottom",CSSGet(CSSStyleCell(dflt),"padding-bottom"));
}
}
break;
}
}
for (DFNode *child = node->first; child != NULL; child = child->next)
ensureStylesReferencedFromNode(child,styleSheet);
}
void CSSEnsureReferencedStylesPresent(DFDocument *htmlDoc, CSSSheet *styleSheet)
{
CSSSheetLookupSelector(styleSheet,"p",1,0);
ensureStylesReferencedFromNode(htmlDoc->root,styleSheet);
}
static void addHTMLDefaults(CSSStyle *style, CSSStyle *parent)
{
if (style->headingLevel > 0) {
const char *fontWeight = NULL;
const char *fontSize = NULL;
switch (style->tag) {
case HTML_H1:
fontWeight = "bold";
fontSize = "24pt";
break;
case HTML_H2:
fontWeight = "bold";
fontSize = "18pt";
break;
case HTML_H3:
fontWeight = "bold";
fontSize = "14pt";
break;
case HTML_H4:
fontWeight = "bold";
fontSize = "12pt";
break;
case HTML_H5:
fontWeight = "bold";
fontSize = "10pt";
break;
case HTML_H6:
fontWeight = "bold";
fontSize = "8pt";
break;
}
CSSProperties *properties = CSSStyleRule(style);
if ((CSSGet(properties,"font-weight") == NULL) && ((parent == NULL) || (CSSGet(CSSStyleRule(parent),"font-weight") == NULL)))
CSSPut(properties,"font-weight",fontWeight);
if ((CSSGet(properties,"font-size") == NULL) && ((parent == NULL) || (CSSGet(CSSStyleRule(parent),"font-size") == NULL)))
CSSPut(properties,"font-size",fontSize);
// FIXME: This likely differs between languages
// FIXME: Not covered by tests
char *styleNext = CSSStyleCopyNext(style);
char *parentNext = (parent != NULL) ? CSSStyleCopyNext(parent) : NULL;
if ((styleNext == NULL) && ((parent == NULL) || (parentNext == NULL)))
CSSStyleSetNext(style,"p.Normal");
free(styleNext);
free(parentNext);
}
}
void CSSSetHTMLDefaults(CSSSheet *styleSheet)
{
// For any heading elements that have an unnamed style declaration, set the default HTML properties on that.
// All named style declarations inherit from these.
// For any heading elements that have only named style declarations, set the default HTML properties on each
// of them individually.
for (int i = 1; i <= 6; i++) {
char *elementName = DFFormatString("h%d",i);
CSSStyle *style = CSSSheetLookupElement(styleSheet,elementName,NULL,0,0);
if (style != NULL)
addHTMLDefaults(style,NULL);
free(elementName);
}
const char **allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
const char *selector = allSelectors[i];
if (CSSSelectorIsHeading(selector) && (CSSSelectorHasClassName(selector))) {
char *elementName = CSSSelectorCopyElementName(selector);
CSSStyle *namedStyle = CSSSheetLookupSelector(styleSheet,selector,0,0);
CSSStyle *unnamedStyle = CSSSheetLookupSelector(styleSheet,elementName,0,0);
CSSStyle *flattened = NULL;
CSSStyle *parent = CSSSheetGetStyleParent(styleSheet,namedStyle);
if (parent != NULL)
flattened = CSSSheetFlattenedStyle(styleSheet,parent);
if (unnamedStyle == NULL)
addHTMLDefaults(namedStyle,flattened);
CSSStyleRelease(flattened);
free(elementName);
}
}
free(allSelectors);
}
static void setMissingDisplayNames(CSSSheet *styleSheet)
{
const char **allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
CSSStyle *style = CSSSheetLookupSelector(styleSheet,allSelectors[i],0,0);
char *className = NULL;
char *displayName = CSSStyleCopyDisplayName(style);
if ((style->headingLevel > 0) && (displayName == NULL)) {
const char *defaultClass = classPrefixForElementName(style->elementName);
if ((style->className != NULL) && (defaultClass != NULL) && DFStringHasPrefix(style->className,defaultClass))
className = DFSubstring(style->className,strlen(defaultClass),strlen(style->className));
else
className = DFStrDup(style->className);
if ((className != NULL) && strlen(className) > 0)
displayName = DFFormatString("Heading %d (%s)",style->headingLevel,className);
else
displayName = DFFormatString("heading %d",style->headingLevel);
CSSStyleSetDisplayName(style,displayName);
}
free(displayName);
free(className);
}
free(allSelectors);
}
static void setParentForHeadings(CSSSheet *styleSheet, DFHashTable *repls)
{
const char **allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
const char *selector = allSelectors[i];
if (!CSSSelectorIsHeading(selector))
continue;
CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,0,0);
const char *defaultClass = classPrefixForElementName(style->elementName);
if (defaultClass == NULL)
continue;
CSSStyle *parentStyle = CSSSheetLookupElement(styleSheet,style->elementName,defaultClass,0,0);
if ((parentStyle != NULL) && (parentStyle != style))
CSSStyleSetParent(style,parentStyle->selector);
}
free(allSelectors);
}
static const char *classPrefixForElementName(const char *elementName)
{
if (!strcmp(elementName,"h1"))
return "heading_1";
else if (!strcmp(elementName,"h2"))
return "heading_2";
else if (!strcmp(elementName,"h3"))
return "heading_3";
else if (!strcmp(elementName,"h4"))
return "heading_4";
else if (!strcmp(elementName,"h5"))
return "heading_5";
else if (!strcmp(elementName,"h6"))
return "heading_6";
else
return elementName;
}
static void determineReplacements(CSSSheet *styleSheet, DFHashTable *repls)
{
// char * -> linked list of SelectorList nodes
DFHashTable *selectorsByClassName = DFHashTableNew(NULL,NULL);
const char **allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
const char *selector = allSelectors[i];
char *className = CSSSelectorCopyClassName(selector);
if (className == NULL)
className = xstrdup("");;
SelectorList *item = (SelectorList *)xcalloc(1,sizeof(SelectorList));
item->selector = xstrdup(selector);
item->next = DFHashTableLookup(selectorsByClassName,className);
DFHashTableAdd(selectorsByClassName,className,item);
free(className);
}
free(allSelectors);
const char **allClasses = DFHashTableCopyKeys(selectorsByClassName);
for (int i = 0; allClasses[i]; i++) {
const char *className = allClasses[i];
SelectorList *list = DFHashTableLookup(selectorsByClassName,className);
SelectorList *start = list;
SelectorList *next;
for (; list != NULL; list = next) {
next = list->next;
const char *oldSelector = list->selector;
char *elementName = CSSSelectorCopyElementName(oldSelector);
int haveUnnamed = (CSSSheetLookupSelector(styleSheet,elementName,0,0) != NULL);
if (((start != NULL) && (start->next != NULL)) || !strcmp(className,"") || haveUnnamed) {
if (CSSSelectorIsHeading(oldSelector)) {
const char *prefix = classPrefixForElementName(elementName);
char *newSelector = DFFormatString("%s.%s%s",elementName,prefix,className);
DFHashTableAdd(repls,oldSelector,newSelector);
free(newSelector);
}
}
free(elementName);
}
// Free selector list
for (list = start; list != NULL; list = next) {
next = list->next;
free(list->selector);
free(list);
}
}
free(allClasses);
DFHashTableRelease(selectorsByClassName);
// Remove any "double replacements", where the new selector is itself a key in the hash table, e.g.:
// h1 -> h1.Heading1 -> h1.Heading1Heading1
//
// This avoids a problem where a style is renamed twice, but the relevant nodes in the DOM tree are only updated
// to refer to the first replacement, not the second, causing an assertion failure in WordParagraphPut, which
// expects there to be a style for every selector it encounters during traversal.
const char **allOldSelectors = DFHashTableCopyKeys(repls);
for (int i = 0; allOldSelectors[i]; i++) {
const char *newSelector = DFHashTableLookup(repls,allOldSelectors[i]);
if (newSelector != NULL) // may have already been removed
DFHashTableRemove(repls,newSelector);
}
free(allOldSelectors);
}
static void replaceSelectorsInNode(DFHashTable *repls, DFNode *node)
{
switch (node->tag) {
case HTML_H1:
case HTML_H2:
case HTML_H3:
case HTML_H4:
case HTML_H5:
case HTML_H6: {
const char *className = DFGetAttribute(node,HTML_CLASS);
char *oldSelector = CSSMakeTagSelector(node->tag,className);
const char *newSelector = DFHashTableLookup(repls,oldSelector);
if (newSelector != NULL) {
char *className = CSSSelectorCopyClassName(newSelector);
DFSetAttribute(node,HTML_CLASS,className);
free(className);
}
free(oldSelector);
break;
}
}
for (DFNode *child = node->first; child != NULL; child = child->next)
replaceSelectorsInNode(repls,child);
}
static void replaceSelectorsInSheet(DFHashTable *repls, CSSSheet *styleSheet)
{
const char **allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
const char *oldSelector = allSelectors[i];
const char *newSelector = DFHashTableLookup(repls,oldSelector);
if ((newSelector == NULL) || !strcmp(newSelector,oldSelector))
continue;
CSSStyle *style = CSSSheetLookupSelector(styleSheet,oldSelector,0,0);
CSSStyle *existing = CSSSheetLookupSelector(styleSheet,newSelector,0,0);
if (existing != NULL) {
CSSSheetRemoveStyle(styleSheet,style);
}
else {
// We must retain a reference to the style here, otherwise it will be deleted when removed from the sheet
CSSStyleRetain(style);
CSSSheetRemoveStyle(styleSheet,style);
CSSStyleSetSelector(style,newSelector);
CSSSheetAddStyle(styleSheet,style);
CSSStyleRelease(style);
}
}
free(allSelectors);
// FIXME: Not covered by tests
allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
CSSStyle *style = CSSSheetLookupSelector(styleSheet,allSelectors[i],0,0);
char *parentSelector = CSSStyleCopyParent(style);
if ((parentSelector != NULL) && (DFHashTableLookup(repls,parentSelector) != NULL))
CSSStyleSetParent(style,DFHashTableLookup(repls,parentSelector));
free(parentSelector);
char *nextSelector = CSSStyleCopyNext(style);
if ((nextSelector != NULL) && (DFHashTableLookup(repls,nextSelector) != NULL))
CSSStyleSetNext(style,DFHashTableLookup(repls,nextSelector));
free(nextSelector);
}
free(allSelectors);
}
void CSSEnsureUnique(CSSSheet *styleSheet, DFDocument *htmlDoc, int creating)
{
DFHashTable *repls = DFHashTableNew((DFCopyFunction)xstrdup,free);
determineReplacements(styleSheet,repls);
replaceSelectorsInSheet(repls,styleSheet);
replaceSelectorsInNode(repls,htmlDoc->root);
setMissingDisplayNames(styleSheet);
setParentForHeadings(styleSheet,repls);
DFHashTableRelease(repls);
}