UNPKG

node-webodf

Version:

WebODF - JavaScript Document Engine http://webodf.org/

924 lines (886 loc) 44.9 kB
/** * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com> * * @licstart * This file is part of WebODF. * * WebODF is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License (GNU AGPL) * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * WebODF is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with WebODF. If not, see <http://www.gnu.org/licenses/>. * @licend * * @source: http://www.webodf.org/ * @source: https://github.com/kogmbh/WebODF/ */ /*global Node, odf, runtime, xmldom*/ /** * @constructor */ odf.StyleInfo = function StyleInfo() { "use strict"; // helper constants var /**@const @type{!string}*/ chartns = odf.Namespaces.chartns, /**@const @type{!string}*/ dbns = odf.Namespaces.dbns, /**@const @type{!string}*/ dr3dns = odf.Namespaces.dr3dns, /**@const @type{!string}*/ drawns = odf.Namespaces.drawns, /**@const @type{!string}*/ formns = odf.Namespaces.formns, /**@const @type{!string}*/ numberns = odf.Namespaces.numberns, /**@const @type{!string}*/ officens = odf.Namespaces.officens, /**@const @type{!string}*/ presentationns = odf.Namespaces.presentationns, /**@const @type{!string}*/ stylens = odf.Namespaces.stylens, /**@const @type{!string}*/ tablens = odf.Namespaces.tablens, /**@const @type{!string}*/ textns = odf.Namespaces.textns, /**@const @type{!Object.<string,string>}*/ nsprefixes = { "urn:oasis:names:tc:opendocument:xmlns:chart:1.0": "chart:", "urn:oasis:names:tc:opendocument:xmlns:database:1.0": "db:", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0": "dr3d:", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0": "draw:", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0": "fo:", "urn:oasis:names:tc:opendocument:xmlns:form:1.0": "form:", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0": "number:", "urn:oasis:names:tc:opendocument:xmlns:office:1.0": "office:", "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0": "presentation:", "urn:oasis:names:tc:opendocument:xmlns:style:1.0": "style:", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0": "svg:", "urn:oasis:names:tc:opendocument:xmlns:table:1.0": "table:", "urn:oasis:names:tc:opendocument:xmlns:text:1.0": "chart:", "http://www.w3.org/XML/1998/namespace": "xml:" }, /** * Data about the styles. * ens: element namespace, * en: element name, * ans: attribute namespace, * a: attribute * @type{!Object.<string,!Array.<!{ens:string,en:string,ans:string,a:string}>>} */ elementstyles = { "text": [ { ens: stylens, en: 'tab-stop', ans: stylens, a: 'leader-text-style'}, { ens: stylens, en: 'drop-cap', ans: stylens, a: 'style-name'}, { ens: textns, en: 'notes-configuration', ans: textns, a: 'citation-body-style-name'}, { ens: textns, en: 'notes-configuration', ans: textns, a: 'citation-style-name'}, { ens: textns, en: 'a', ans: textns, a: 'style-name'}, { ens: textns, en: 'alphabetical-index', ans: textns, a: 'style-name'}, { ens: textns, en: 'linenumbering-configuration', ans: textns, a: 'style-name'}, { ens: textns, en: 'list-level-style-number', ans: textns, a: 'style-name'}, { ens: textns, en: 'ruby-text', ans: textns, a: 'style-name'}, { ens: textns, en: 'span', ans: textns, a: 'style-name'}, { ens: textns, en: 'a', ans: textns, a: 'visited-style-name'}, { ens: stylens, en: 'text-properties', ans: stylens, a: 'text-line-through-text-style'}, { ens: textns, en: 'alphabetical-index-source', ans: textns, a: 'main-entry-style-name'}, { ens: textns, en: 'index-entry-bibliography', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-chapter', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-link-end', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-link-start', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-page-number', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-span', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-tab-stop', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-entry-text', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-title-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'list-level-style-bullet', ans: textns, a: 'style-name'}, { ens: textns, en: 'outline-level-style', ans: textns, a: 'style-name'} ], "paragraph": [ { ens: drawns, en: 'caption', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'circle', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'connector', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'control', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'custom-shape', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'ellipse', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'frame', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'line', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'measure', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'path', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'polygon', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'polyline', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'rect', ans: drawns, a: 'text-style-name'}, { ens: drawns, en: 'regular-polygon', ans: drawns, a: 'text-style-name'}, { ens: officens, en: 'annotation', ans: drawns, a: 'text-style-name'}, { ens: formns, en: 'column', ans: formns, a: 'text-style-name'}, { ens: stylens, en: 'style', ans: stylens, a: 'next-style-name'}, { ens: tablens, en: 'body', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'even-columns', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'even-rows', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'first-column', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'first-row', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'last-column', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'last-row', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'odd-columns', ans: tablens, a: 'paragraph-style-name'}, { ens: tablens, en: 'odd-rows', ans: tablens, a: 'paragraph-style-name'}, { ens: textns, en: 'notes-configuration', ans: textns, a: 'default-style-name'}, { ens: textns, en: 'alphabetical-index-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'bibliography-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'h', ans: textns, a: 'style-name'}, { ens: textns, en: 'illustration-index-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-source-style', ans: textns, a: 'style-name'}, { ens: textns, en: 'object-index-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'p', ans: textns, a: 'style-name'}, { ens: textns, en: 'table-index-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'table-of-content-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'table-index-entry-template', ans: textns, a: 'style-name'}, { ens: textns, en: 'user-index-entry-template', ans: textns, a: 'style-name'}, { ens: stylens, en: 'page-layout-properties', ans: stylens, a: 'register-truth-ref-style-name'} ], "chart": [ { ens: chartns, en: 'axis', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'chart', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'data-label', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'data-point', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'equation', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'error-indicator', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'floor', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'footer', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'grid', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'legend', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'mean-value', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'plot-area', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'regression-curve', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'series', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'stock-gain-marker', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'stock-loss-marker', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'stock-range-line', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'subtitle', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'title', ans: chartns, a: 'style-name'}, { ens: chartns, en: 'wall', ans: chartns, a: 'style-name'} ], "section": [ { ens: textns, en: 'alphabetical-index', ans: textns, a: 'style-name'}, { ens: textns, en: 'bibliography', ans: textns, a: 'style-name'}, { ens: textns, en: 'illustration-index', ans: textns, a: 'style-name'}, { ens: textns, en: 'index-title', ans: textns, a: 'style-name'}, { ens: textns, en: 'object-index', ans: textns, a: 'style-name'}, { ens: textns, en: 'section', ans: textns, a: 'style-name'}, { ens: textns, en: 'table-of-content', ans: textns, a: 'style-name'}, { ens: textns, en: 'table-index', ans: textns, a: 'style-name'}, { ens: textns, en: 'user-index', ans: textns, a: 'style-name'} ], "ruby": [ { ens: textns, en: 'ruby', ans: textns, a: 'style-name'} ], "table": [ { ens: dbns, en: 'query', ans: dbns, a: 'style-name'}, { ens: dbns, en: 'table-representation', ans: dbns, a: 'style-name'}, { ens: tablens, en: 'background', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'table', ans: tablens, a: 'style-name'} ], "table-column": [ { ens: dbns, en: 'column', ans: dbns, a: 'style-name'}, { ens: tablens, en: 'table-column', ans: tablens, a: 'style-name'} ], "table-row": [ { ens: dbns, en: 'query', ans: dbns, a: 'default-row-style-name'}, { ens: dbns, en: 'table-representation', ans: dbns, a: 'default-row-style-name'}, { ens: tablens, en: 'table-row', ans: tablens, a: 'style-name'} ], "table-cell": [ { ens: dbns, en: 'column', ans: dbns, a: 'default-cell-style-name'}, { ens: tablens, en: 'table-column', ans: tablens, a: 'default-cell-style-name'}, { ens: tablens, en: 'table-row', ans: tablens, a: 'default-cell-style-name'}, { ens: tablens, en: 'body', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'covered-table-cell', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'even-columns', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'covered-table-cell', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'even-columns', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'even-rows', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'first-column', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'first-row', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'last-column', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'last-row', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'odd-columns', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'odd-rows', ans: tablens, a: 'style-name'}, { ens: tablens, en: 'table-cell', ans: tablens, a: 'style-name'} ], "graphic": [ { ens: dr3dns, en: 'cube', ans: drawns, a: 'style-name'}, { ens: dr3dns, en: 'extrude', ans: drawns, a: 'style-name'}, { ens: dr3dns, en: 'rotate', ans: drawns, a: 'style-name'}, { ens: dr3dns, en: 'scene', ans: drawns, a: 'style-name'}, { ens: dr3dns, en: 'sphere', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'caption', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'circle', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'connector', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'control', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'custom-shape', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'ellipse', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'frame', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'g', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'line', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'measure', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'page-thumbnail', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'path', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'polygon', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'polyline', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'rect', ans: drawns, a: 'style-name'}, { ens: drawns, en: 'regular-polygon', ans: drawns, a: 'style-name'}, { ens: officens, en: 'annotation', ans: drawns, a: 'style-name'} ], "presentation": [ { ens: dr3dns, en: 'cube', ans: presentationns, a: 'style-name'}, { ens: dr3dns, en: 'extrude', ans: presentationns, a: 'style-name'}, { ens: dr3dns, en: 'rotate', ans: presentationns, a: 'style-name'}, { ens: dr3dns, en: 'scene', ans: presentationns, a: 'style-name'}, { ens: dr3dns, en: 'sphere', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'caption', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'circle', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'connector', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'control', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'custom-shape', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'ellipse', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'frame', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'g', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'line', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'measure', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'page-thumbnail', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'path', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'polygon', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'polyline', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'rect', ans: presentationns, a: 'style-name'}, { ens: drawns, en: 'regular-polygon', ans: presentationns, a: 'style-name'}, { ens: officens, en: 'annotation', ans: presentationns, a: 'style-name'} ], "drawing-page": [ { ens: drawns, en: 'page', ans: drawns, a: 'style-name'}, { ens: presentationns, en: 'notes', ans: drawns, a: 'style-name'}, { ens: stylens, en: 'handout-master', ans: drawns, a: 'style-name'}, { ens: stylens, en: 'master-page', ans: drawns, a: 'style-name'} ], "list-style": [ { ens: textns, en: 'list', ans: textns, a: 'style-name'}, { ens: textns, en: 'numbered-paragraph', ans: textns, a: 'style-name'}, { ens: textns, en: 'list-item', ans: textns, a: 'style-override'}, { ens: stylens, en: 'style', ans: stylens, a: 'list-style-name'} ], // See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1416346_253892949 "data": [ { ens: stylens, en: 'style', ans: stylens, a: 'data-style-name'}, { ens: stylens, en: 'style', ans: stylens, a: 'percentage-data-style-name'}, { ens: presentationns, en: 'date-time-decl', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'creation-date', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'creation-time', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'database-display', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'date', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'editing-duration', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'expression', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'meta-field', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'modification-date', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'modification-time', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'print-date', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'print-time', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'table-formula', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'time', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'user-defined', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'user-field-get', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'user-field-input', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'variable-get', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'variable-input', ans: stylens, a: 'data-style-name'}, { ens: textns, en: 'variable-set', ans: stylens, a: 'data-style-name'} ], "page-layout": [ { ens: presentationns, en: 'notes', ans: stylens, a: 'page-layout-name'}, { ens: stylens, en: 'handout-master', ans: stylens, a: 'page-layout-name'}, { ens: stylens, en: 'master-page', ans: stylens, a: 'page-layout-name'} ] }, /** * Inversion of elementstyles, created with "inverse(elementstyles);" in * init section * Map with element name as primary key, element namespace as secondary * key, then an array of { * ns: namespace of attribute, * localname: name of attribute, * keyname: keyname * } * @type {!Object.<!string,!Object.<!string,!Array.<!{keyname:!string,ns:!string,localname:!string}>>>} */ elements, xpath = xmldom.XPath; /** * Return if a particular element is the parent style for any other style of * the same family. * @param {!Element} odfbody * @param {!function(string):?string} nsResolver * @param {!Element} styleElement * @return {!boolean} */ function hasDerivedStyles(odfbody, nsResolver, styleElement) { var nodes, xp, styleName = styleElement.getAttributeNS(stylens, 'name'), styleFamily = styleElement.getAttributeNS(stylens, 'family'); xp = '//style:*[@style:parent-style-name="' + styleName + '"][@style:family="' + styleFamily + '"]'; nodes = xpath.getODFElementsWithXPath(odfbody, xp, nsResolver); if (nodes.length) { return true; } return false; } /** * Prefixes all style ids used to refer to styles in the given DOM element * tree with the given prefix. * @param {!Element} element root element of tree of elements using styles * @param {!string} prefix * @return {undefined} */ function prefixUsedStyleNames(element, prefix) { var i, stylename, a, e, ns, elname, elns, /**@type{string}*/ localName, length = 0; elname = elements[element.localName]; if (elname) { elns = elname[element.namespaceURI]; if (elns) { length = elns.length; } } // prefix any used style ids for (i = 0; i < length; i += 1) { a = /**@type{!{ns:string,localname:string}}*/(elns[i]); ns = a.ns; localName = a.localname; stylename = element.getAttributeNS(ns, localName); if (stylename) { // a style reference has been found! element.setAttributeNS(ns, nsprefixes[ns] + localName, prefix + stylename); } } // continue prefixing with all child elements e = element.firstElementChild; while (e) { prefixUsedStyleNames(e, prefix); e = e.nextElementSibling; } } /** * Prefixes the id of the style defined in the given DOM element with the * given prefix. * @param {!Element} styleElement * @param {!string} prefix * @return {undefined} */ function prefixStyleName(styleElement, prefix) { var stylename = styleElement.getAttributeNS(drawns, "name"), ns; if (stylename) { ns = drawns; } else { stylename = styleElement.getAttributeNS(stylens, "name"); if (stylename) { ns = stylens; } } if (ns) { styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", prefix + stylename); } } /** * Prefixes all style ids with the given prefix. This will affect all style * ids as set in the style definitions by the child elements of * styleElementsRoot and all style ids used to refer to styles, both in * these style definitions and in the given DOM element tree * styleUsingElementsRoot. * @param {?Element} styleElementsRoot root element with styles nodes as childs * @param {!string} prefix * @param {?Element} styleUsingElementsRoot root element of tree of elements using styles * @return {undefined} */ function prefixStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) { var s; if (styleElementsRoot) { // prefix all set style ids s = styleElementsRoot.firstChild; while (s) { if (s.nodeType === Node.ELEMENT_NODE) { prefixStyleName(/**@type{!Element}*/(s), prefix); } s = s.nextSibling; } // prefix all ids in style references prefixUsedStyleNames(styleElementsRoot, prefix); if (styleUsingElementsRoot) { prefixUsedStyleNames(styleUsingElementsRoot, prefix); } } } /** * @param {!Element} element root element of tree of elements using styles * @param {!RegExp} regExp * @return {undefined} */ function removeRegExpFromUsedStyleNames(element, regExp) { var i, stylename, e, elname, elns, a, ns, localName, length = 0; elname = elements[element.localName]; if (elname) { elns = elname[element.namespaceURI]; if (elns) { length = elns.length; } } // remove prefix from any used style id for (i = 0; i < length; i += 1) { a = /**@type{!{ns:string,localname:string}}*/(elns[i]); ns = a.ns; localName = a.localname; stylename = element.getAttributeNS(ns, localName); if (stylename) { // a style reference has been found! stylename = stylename.replace(regExp, ''); element.setAttributeNS(ns, nsprefixes[ns] + localName, stylename); } } // continue removal with all child elements e = element.firstElementChild; while (e) { removeRegExpFromUsedStyleNames(e, regExp); e = e.nextElementSibling; } } /** * Remove the given regular expression from the id of the style defined in * the given DOM element. * @param {!Element} styleElement * @param {!RegExp} regExp * @return {undefined} */ function removeRegExpFromStyleName(styleElement, regExp) { var stylename = styleElement.getAttributeNS(drawns, "name"), ns; if (stylename) { ns = drawns; } else { stylename = styleElement.getAttributeNS(stylens, "name"); if (stylename) { ns = stylens; } } if (ns) { stylename = stylename.replace(regExp, ''); styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", stylename); } } /** * Removes the given prefix from all style ids. This will affect all style * ids as set in the style definitions by the child elements of * styleElementsRoot and all style ids used to refer to styles, both in * these style definitions and in the given DOM element tree * styleUsingElementsRoot. * @param {?Element} styleElementsRoot root element with styles nodes as childs * @param {!string} prefix * @param {?Element} styleUsingElementsRoot root element of tree of elements using styles */ function removePrefixFromStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) { var s, regExp = new RegExp("^" + prefix); if (styleElementsRoot) { // remove prefix from all set style ids s = styleElementsRoot.firstChild; while (s) { if (s.nodeType === Node.ELEMENT_NODE) { removeRegExpFromStyleName(/**@type{!Element}*/(s), regExp); } s = s.nextSibling; } // remove prefix from all ids in style references removeRegExpFromUsedStyleNames(styleElementsRoot, regExp); if (styleUsingElementsRoot) { removeRegExpFromUsedStyleNames(styleUsingElementsRoot, regExp); } } } /** * Determines all stylenames that are referenced in the passed element * @param {!Element} element element to check for styles * @param {!Object.<string,!Object.<string,number>>=} usedStyles map of used styles names, grouped by style family * @return {!Object.<string,!Object.<string,number>>|undefined} Returns either map of used styles, or undefined if none * have been found an usedStyles was not passed in */ function determineStylesForNode(element, usedStyles) { var i, stylename, elname, elns, a, ns, localName, keyname, length = 0, map; elname = elements[element.localName]; if (elname) { elns = elname[element.namespaceURI]; if (elns) { length = elns.length; } } // check if any styles are referenced for (i = 0; i < length; i += 1) { a = /**@type{!{ns:string,localname:string,keyname:string}}*/(elns[i]); ns = a.ns; localName = a.localname; stylename = element.getAttributeNS(ns, localName); if (stylename) { // a style has been found! usedStyles = usedStyles || {}; keyname = a.keyname; if (usedStyles.hasOwnProperty(keyname)) { usedStyles[keyname][stylename] = 1; } else { map = {}; map[stylename] = 1; usedStyles[keyname] = map; } } } return usedStyles; } /** * Determines all stylenames that are referenced in the passed element tree * @param {!Element} styleUsingElementsRoot root element of tree of elements using styles * @param {!Object.<string,Object.<string,number>>} usedStyles map of used styles names, grouped by style family * @return {undefined} */ function determineUsedStyles(styleUsingElementsRoot, usedStyles) { var i, e; determineStylesForNode(styleUsingElementsRoot, usedStyles); // continue determination with all child elements i = styleUsingElementsRoot.firstChild; while (i) { if (i.nodeType === Node.ELEMENT_NODE) { e = /**@type{!Element}*/(i); determineUsedStyles(e, usedStyles); } i = i.nextSibling; } } /** * Node defining a style, with references to all required styles necessary to construct it * @param {!string} key Style key * @param {!string} name Style name * @param {!string} family Style family * @constructor */ function StyleDefinition(key, name, family) { /** * Unique style definition key * @type {string} */ this.key = key; /** * Style name * @type {string} */ this.name = name; /** * Style family (e.g., paragraph, table-cell, text) * @type {string} */ this.family = family; /** * Styles directly required by this style * @type {Object.<string, StyleDefinition>} */ this.requires = {}; } /** * @param {!string} stylename * @param {!string} stylefamily * @param {!Object.<string,StyleDefinition>} knownStyles map of used stylesnames, grouped by keyname * @return {!StyleDefinition} */ function getStyleDefinition(stylename, stylefamily, knownStyles) { var styleKey = stylename + '"' + stylefamily, styleDefinition = knownStyles[styleKey]; if (!styleDefinition) { styleDefinition = knownStyles[styleKey] = new StyleDefinition(styleKey, stylename, stylefamily); } return styleDefinition; } /** * Builds a style dependency map for the supplied style tree * @param {!Element} element root element of tree of elements using styles * @param {?StyleDefinition} styleScope parent style the specified style element is part of * @param {!Object.<string,StyleDefinition>} knownStyles map of used stylesnames, grouped by keyname * @return {!Object.<string,StyleDefinition>} */ function determineDependentStyles(element, styleScope, knownStyles) { var i, stylename, elname, elns, a, ns, localName, e, referencedStyleFamily, referencedStyleDef, length = 0, newScopeName = element.getAttributeNS(stylens, 'name'), newScopeFamily = element.getAttributeNS(stylens, 'family'); if (newScopeName && newScopeFamily) { styleScope = getStyleDefinition(newScopeName, newScopeFamily, knownStyles); } if (styleScope) { elname = elements[element.localName]; if (elname) { elns = elname[element.namespaceURI]; if (elns) { length = elns.length; } } // check if any styles are referenced for (i = 0; i < length; i += 1) { a = /**@type{!{ns:string,localname:string,keyname:string}}*/(elns[i]); ns = a.ns; localName = a.localname; stylename = element.getAttributeNS(ns, localName); if (stylename) { // a style has been found! referencedStyleFamily = a.keyname; referencedStyleDef = getStyleDefinition(stylename, referencedStyleFamily, knownStyles); styleScope.requires[referencedStyleDef.key] = referencedStyleDef; } } } // continue determination with all child elements e = element.firstElementChild; while (e) { determineDependentStyles(e, styleScope, knownStyles); e = e.nextElementSibling; } return knownStyles; } /** * Creates the elements data from the elementstyles data. * @return {!Object.<string,Object.<string,Array.<Object.<string,string>>>>} */ function inverse() { var i, l, /**@type{string}*/ keyname, /**@type{!Array.<!{ens:string,en:string,ans:string,a:string}>}*/ list, /**@type{!{en:string,ens:string}}*/ item, /**@type{!Object.<string,Object.<string,Array.<Object.<string,string>>>>}*/ e = {}, map, array, en, ens; for (keyname in elementstyles) { if (elementstyles.hasOwnProperty(keyname)) { list = elementstyles[keyname]; l = list.length; for (i = 0; i < l; i += 1) { item = list[i]; en = item.en; ens = item.ens; if (e.hasOwnProperty(en)) { map = e[en]; } else { e[en] = map = {}; } if (map.hasOwnProperty(ens)) { array = map[ens]; } else { map[ens] = array = []; } array.push({ ns: item.ans, localname: item.a, keyname: keyname }); } } } return e; } /** * Merges the specified style, and style required to complete it into the usedStyles map * @param {!StyleDefinition} styleDependency Style to merge * @param {!Object.<string,Object.<string,number>>} usedStyles Styles map to merge data into * @return {undefined} */ function mergeRequiredStyles(styleDependency, usedStyles) { var family = usedStyles[styleDependency.family]; if (!family) { family = usedStyles[styleDependency.family] = {}; } family[styleDependency.name] = 1; Object.keys(/**@type {!Object}*/(styleDependency.requires)).forEach(function(requiredStyleKey) { mergeRequiredStyles(/**@type {!StyleDefinition}*/(styleDependency.requires[requiredStyleKey]) , usedStyles); }); } /** * Marks all required styles as used for any automatic styles referenced within the existing usedStyles map * @param {!Element} automaticStylesRoot Automatic styles tree root * @param {!Object.<string,Object.<string,number>>} usedStyles Styles already referenced * @return {undefined} */ function mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles) { var automaticStyles = determineDependentStyles(automaticStylesRoot, null, {}); // Merge into usedStyles Object.keys(automaticStyles).forEach(function(styleKey) { var automaticStyleDefinition = automaticStyles[styleKey], usedFamily = usedStyles[automaticStyleDefinition.family]; // For each style referenced by the main root, mark all required automatic styles as used as well if (usedFamily && usedFamily.hasOwnProperty(automaticStyleDefinition.name)) { mergeRequiredStyles(automaticStyleDefinition, usedStyles); } }); } /** * Collects all names of font-face declarations which are referenced in the * children elements of the given root element. * @param {!Object.<!string,!boolean>} usedFontFaceDeclMap * @param {?Element} styleElement root element with style elements as childs * @return {undefined} */ function collectUsedFontFaces(usedFontFaceDeclMap, styleElement) { var localNames = ["font-name", "font-name-asian", "font-name-complex"], e, /**@type{!Element}*/ currentElement; /** * @param {string} localName */ function collectByAttribute(localName) { var fontFaceName = currentElement.getAttributeNS(stylens, localName); if (fontFaceName) { usedFontFaceDeclMap[fontFaceName] = true; } } e = styleElement && styleElement.firstElementChild; while (e) { // TODO: only check elements which have those attributes defined currentElement = e; localNames.forEach(collectByAttribute); collectUsedFontFaces(usedFontFaceDeclMap, currentElement); e = e.nextElementSibling; } } this.collectUsedFontFaces = collectUsedFontFaces; /** * Changes all names of font-face declarations which are referenced in the * children elements of the given root element. * @param {?Element} styleElement root element with style elements as childs * @param {!Object.<!string,!string>} fontFaceNameChangeMap * @return {undefined} */ function changeFontFaceNames(styleElement, fontFaceNameChangeMap) { var localNames = ["font-name", "font-name-asian", "font-name-complex"], e, /**@type{!Element}*/ currentElement; /** * @param {string} localName */ function changeFontFaceNameByAttribute(localName) { var fontFaceName = currentElement.getAttributeNS(stylens, localName); if (fontFaceName && fontFaceNameChangeMap.hasOwnProperty(fontFaceName)) { currentElement.setAttributeNS(stylens, "style:" + localName, fontFaceNameChangeMap[fontFaceName]); } } e = styleElement && styleElement.firstElementChild; while (e) { // TODO: only check elements which have those attributes defined currentElement = e; localNames.forEach(changeFontFaceNameByAttribute); changeFontFaceNames(currentElement, fontFaceNameChangeMap); e = e.nextElementSibling; } } this.changeFontFaceNames = changeFontFaceNames; /** * Object which collects all style names that are used in the passed element tree * @constructor * @param {!Element} styleUsingElementsRoot root element of tree of elements using styles * @param {?Element=} automaticStylesRoot Additional style information. Styles in this tree are only important * when used as part of a chain of styles referenced from within the stylesUsingElementsRoot node */ this.UsedStyleList = function (styleUsingElementsRoot, automaticStylesRoot) { // usedStyles stores all style names used in the passed element tree. // As styles from different types can have the same names, // all styles are grouped by: // * family attribute for style:style // * "data" for all number:* (boolean-style,currency-style,date-style, // number-style,percentage-style,text-style,time-style) // * localName for text:list-style, style:page-layout var /** @type !Object.<string,Object.<string,number>> */usedStyles = {}; /** * Checks whether the passed style is referenced by anything * @param {!Element} element odf style describing element * @return {!boolean} */ this.uses = function (element) { var localName = element.localName, name = element.getAttributeNS(drawns, "name") || element.getAttributeNS(stylens, "name"), keyName, map; if (localName === "style") { keyName = element.getAttributeNS(stylens, "family"); } else if (element.namespaceURI === numberns) { keyName = "data"; } else { keyName = localName; // list-style or page-layout } map = usedStyles[keyName]; return map ? (map[name] > 0) : false; }; determineUsedStyles(styleUsingElementsRoot, usedStyles); if (automaticStylesRoot) { mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles); } }; /** * Return the name of the style for the given family if it is associated * with the element. * @param {!string} family * @param {!Element} element * @return {!string|undefined} */ function getStyleName(family, element) { var stylename, i, map = elements[element.localName]; if (map) { map = map[element.namespaceURI]; if (map) { for (i = 0; i < map.length; i += 1) { if (map[i].keyname === family) { map = map[i]; if (element.hasAttributeNS(map.ns, map.localname)) { stylename = element.getAttributeNS(map.ns, map.localname); break; } } } } } return stylename; } this.getStyleName = getStyleName; this.hasDerivedStyles = hasDerivedStyles; this.prefixStyleNames = prefixStyleNames; this.removePrefixFromStyleNames = removePrefixFromStyleNames; this.determineStylesForNode = determineStylesForNode; // init elements = inverse(); };