1 /** 2 * Copyright (C) 2010-2014 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global odf*/ 26 27 (function () { 28 "use strict"; 29 30 var /**@const 31 @type{!string}*/ 32 stylens = odf.Namespaces.stylens, 33 /**@const 34 @type{!string}*/ 35 textns = odf.Namespaces.textns, 36 /** 37 * This dictionary maps between ODF style family names and the 38 * tag prefixes they use. This is currently a duplicate of the same 39 * dictionary in Styles2CSS. Here only the style family names are used 40 * as keys in the StyleTree that is generated. 41 * @const 42 @type{!Object.<string,string>}*/ 43 familyNamespacePrefixes = { 44 'graphic': 'draw', 45 'drawing-page': 'draw', 46 'paragraph': 'text', 47 'presentation': 'presentation', 48 'ruby': 'text', 49 'section': 'text', 50 'table': 'table', 51 'table-cell': 'table', 52 'table-column': 'table', 53 'table-row': 'table', 54 'text': 'text', 55 'list': 'text', 56 'page': 'office' 57 }; 58 59 /** 60 * StyleTreeNode encapsulates an ODF style element and its derived styles 61 * This class is used within the StyleTree where it is associated with a key 62 * that is the style name for this node. 63 * @constructor 64 * @param {!Element} element 65 */ 66 odf.StyleTreeNode = function StyleTreeNode(element) { 67 /**@type{!Object.<string,!odf.StyleTreeNode>}*/ 68 this.derivedStyles = {}; 69 70 /**@type{!Element}*/ 71 this.element = element; 72 }; 73 74 /** 75 * StyleTree creates a nested dictionary of the styles in an ODF document 76 * This is to allow the lookup of all styles for a given family by using the family name as the key 77 * and then the specific style in that family by using the style name as the key. 78 * @constructor 79 * @param {!Element} styles 80 * @param {!Element} autoStyles 81 */ 82 odf.StyleTree = function StyleTree(styles, autoStyles) { 83 84 var tree = {}; 85 86 /** 87 * @param {!Element} stylesNode 88 * @return {!Object.<string,!Object.<string,?Element>>} 89 */ 90 function getStyleMap(stylesNode) { 91 // put all style elements in a hash map by family and name 92 var node, name, family, style, 93 /**@type{!Object.<string,!Object.<string,?Element>>}*/ 94 styleMap = {}; 95 if (!stylesNode) { 96 return styleMap; 97 } 98 node = stylesNode.firstElementChild; 99 while (node) { 100 if (node.namespaceURI === stylens && 101 ((node.localName === 'style') || 102 (node.localName === 'default-style'))) { 103 family = node.getAttributeNS(stylens, 'family'); 104 } else if (node.namespaceURI === textns && 105 node.localName === 'list-style') { 106 family = "list"; 107 } else if (node.namespaceURI === stylens && 108 (node.localName === 'page-layout' || node.localName === 'default-page-layout')) { 109 family = "page"; 110 } else { 111 // Skip insignificant white-space only nodes in the style tree 112 family = undefined; 113 } 114 115 if (family) { 116 // get style name 117 name = node.getAttributeNS(stylens, 'name'); 118 if (!name) { 119 // For a default style, there is no name 120 name = ''; 121 } 122 123 // get style (and create, if not yet existing) 124 if (styleMap.hasOwnProperty(family)) { 125 style = styleMap[family]; 126 } else { 127 styleMap[family] = style = {}; 128 } 129 130 // then store style node in map 131 style[name] = node; 132 } 133 134 node = node.nextElementSibling; 135 } 136 137 return styleMap; 138 } 139 140 /** 141 * @param {!Object.<string,!odf.StyleTreeNode>} stylesTree 142 * @param {string} name 143 * @return {odf.StyleTreeNode} 144 */ 145 function findStyleTreeNode(stylesTree, name) { 146 if (stylesTree.hasOwnProperty(name)) { 147 return stylesTree[name]; 148 } 149 var style = null, 150 styleNames = Object.keys(stylesTree), 151 i; 152 153 for (i = 0; i < styleNames.length; i += 1) { 154 style = findStyleTreeNode(stylesTree[styleNames[i]].derivedStyles, name); 155 if (style) { 156 break; 157 } 158 } 159 return style; 160 } 161 162 /** 163 * Creates the StyleTreeNode from the style element 164 * and inserts it into the given StyleTree 165 * @param {string} styleName 166 * @param {!Object.<string,!Element>} stylesMap 167 * @param {!Object.<string,!odf.StyleTreeNode>} stylesTree 168 * @return {?odf.StyleTreeNode} 169 */ 170 function createStyleTreeNode(styleName, stylesMap, stylesTree) { 171 var style, parentname, parentstyle; 172 if (!stylesMap.hasOwnProperty(styleName)) { 173 return null; 174 } 175 style = new odf.StyleTreeNode(stylesMap[styleName]); 176 parentname = style.element.getAttributeNS(stylens, 'parent-style-name'); 177 parentstyle = null; 178 if (parentname) { 179 parentstyle = findStyleTreeNode(stylesTree, parentname) 180 || createStyleTreeNode(parentname, stylesMap, stylesTree); 181 } 182 if (parentstyle) { 183 parentstyle.derivedStyles[styleName] = style; 184 } else { 185 // no parent so add the root 186 stylesTree[styleName] = style; 187 } 188 delete stylesMap[styleName]; 189 return style; 190 } 191 192 /** 193 * @param {!Object.<string,!Element>} stylesMap 194 * @param {!Object.<string,!odf.StyleTreeNode>} stylesTree 195 * @return {undefined} 196 */ 197 function addStyleMapToStyleTree(stylesMap, stylesTree) { 198 if (stylesMap) { 199 Object.keys(stylesMap).forEach(function (styleName) { 200 createStyleTreeNode(styleName, stylesMap, stylesTree); 201 }); 202 } 203 } 204 205 /** 206 * @return {!odf.StyleTree.Tree} 207 */ 208 this.getStyleTree = function () { 209 return tree; 210 }; 211 212 function init() { 213 var subTree, 214 styleNodes, 215 autoStyleNodes; 216 217 styleNodes = getStyleMap(styles); 218 autoStyleNodes = getStyleMap(autoStyles); 219 220 Object.keys(familyNamespacePrefixes).forEach(function (family) { 221 subTree = tree[family] = {}; 222 addStyleMapToStyleTree(styleNodes[family], subTree); 223 addStyleMapToStyleTree(autoStyleNodes[family], subTree); 224 }); 225 } 226 227 init(); 228 }; 229 }()); 230 231 /** 232 * @typedef{!Object.<string,!Object.<string,!odf.StyleTreeNode>>} 233 */ 234 odf.StyleTree.Tree; 235