1 /** 2 * Copyright (C) 2012-2013 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 Node, odf, runtime, xmldom*/ 26 27 /** 28 * @constructor 29 */ 30 odf.StyleInfo = function StyleInfo() { 31 "use strict"; 32 // helper constants 33 var /**@const 34 @type{!string}*/ 35 chartns = odf.Namespaces.chartns, 36 /**@const 37 @type{!string}*/ 38 dbns = odf.Namespaces.dbns, 39 /**@const 40 @type{!string}*/ 41 dr3dns = odf.Namespaces.dr3dns, 42 /**@const 43 @type{!string}*/ 44 drawns = odf.Namespaces.drawns, 45 /**@const 46 @type{!string}*/ 47 formns = odf.Namespaces.formns, 48 /**@const 49 @type{!string}*/ 50 numberns = odf.Namespaces.numberns, 51 /**@const 52 @type{!string}*/ 53 officens = odf.Namespaces.officens, 54 /**@const 55 @type{!string}*/ 56 presentationns = odf.Namespaces.presentationns, 57 /**@const 58 @type{!string}*/ 59 stylens = odf.Namespaces.stylens, 60 /**@const 61 @type{!string}*/ 62 tablens = odf.Namespaces.tablens, 63 /**@const 64 @type{!string}*/ 65 textns = odf.Namespaces.textns, 66 /**@const 67 @type{!Object.<string,string>}*/ 68 nsprefixes = { 69 "urn:oasis:names:tc:opendocument:xmlns:chart:1.0": "chart:", 70 "urn:oasis:names:tc:opendocument:xmlns:database:1.0": "db:", 71 "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0": "dr3d:", 72 "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0": "draw:", 73 "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0": "fo:", 74 "urn:oasis:names:tc:opendocument:xmlns:form:1.0": "form:", 75 "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0": "number:", 76 "urn:oasis:names:tc:opendocument:xmlns:office:1.0": "office:", 77 "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0": "presentation:", 78 "urn:oasis:names:tc:opendocument:xmlns:style:1.0": "style:", 79 "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0": "svg:", 80 "urn:oasis:names:tc:opendocument:xmlns:table:1.0": "table:", 81 "urn:oasis:names:tc:opendocument:xmlns:text:1.0": "chart:", 82 "http://www.w3.org/XML/1998/namespace": "xml:" 83 }, 84 /** 85 * Data about the styles. 86 * ens: element namespace, 87 * en: element name, 88 * ans: attribute namespace, 89 * a: attribute 90 * @type{!Object.<string,!Array.<!{ens:string,en:string,ans:string,a:string}>>} 91 */ 92 elementstyles = { 93 "text": [ 94 { ens: stylens, en: 'tab-stop', ans: stylens, a: 'leader-text-style'}, 95 { ens: stylens, en: 'drop-cap', ans: stylens, a: 'style-name'}, 96 { ens: textns, en: 'notes-configuration', ans: textns, a: 'citation-body-style-name'}, 97 { ens: textns, en: 'notes-configuration', ans: textns, a: 'citation-style-name'}, 98 { ens: textns, en: 'a', ans: textns, a: 'style-name'}, 99 { ens: textns, en: 'alphabetical-index', ans: textns, a: 'style-name'}, 100 { ens: textns, en: 'linenumbering-configuration', ans: textns, a: 'style-name'}, 101 { ens: textns, en: 'list-level-style-number', ans: textns, a: 'style-name'}, 102 { ens: textns, en: 'ruby-text', ans: textns, a: 'style-name'}, 103 { ens: textns, en: 'span', ans: textns, a: 'style-name'}, 104 { ens: textns, en: 'a', ans: textns, a: 'visited-style-name'}, 105 { ens: stylens, en: 'text-properties', ans: stylens, a: 'text-line-through-text-style'}, 106 { ens: textns, en: 'alphabetical-index-source', ans: textns, a: 'main-entry-style-name'}, 107 { ens: textns, en: 'index-entry-bibliography', ans: textns, a: 'style-name'}, 108 { ens: textns, en: 'index-entry-chapter', ans: textns, a: 'style-name'}, 109 { ens: textns, en: 'index-entry-link-end', ans: textns, a: 'style-name'}, 110 { ens: textns, en: 'index-entry-link-start', ans: textns, a: 'style-name'}, 111 { ens: textns, en: 'index-entry-page-number', ans: textns, a: 'style-name'}, 112 { ens: textns, en: 'index-entry-span', ans: textns, a: 'style-name'}, 113 { ens: textns, en: 'index-entry-tab-stop', ans: textns, a: 'style-name'}, 114 { ens: textns, en: 'index-entry-text', ans: textns, a: 'style-name'}, 115 { ens: textns, en: 'index-title-template', ans: textns, a: 'style-name'}, 116 { ens: textns, en: 'list-level-style-bullet', ans: textns, a: 'style-name'}, 117 { ens: textns, en: 'outline-level-style', ans: textns, a: 'style-name'} 118 ], 119 "paragraph": [ 120 { ens: drawns, en: 'caption', ans: drawns, a: 'text-style-name'}, 121 { ens: drawns, en: 'circle', ans: drawns, a: 'text-style-name'}, 122 { ens: drawns, en: 'connector', ans: drawns, a: 'text-style-name'}, 123 { ens: drawns, en: 'control', ans: drawns, a: 'text-style-name'}, 124 { ens: drawns, en: 'custom-shape', ans: drawns, a: 'text-style-name'}, 125 { ens: drawns, en: 'ellipse', ans: drawns, a: 'text-style-name'}, 126 { ens: drawns, en: 'frame', ans: drawns, a: 'text-style-name'}, 127 { ens: drawns, en: 'line', ans: drawns, a: 'text-style-name'}, 128 { ens: drawns, en: 'measure', ans: drawns, a: 'text-style-name'}, 129 { ens: drawns, en: 'path', ans: drawns, a: 'text-style-name'}, 130 { ens: drawns, en: 'polygon', ans: drawns, a: 'text-style-name'}, 131 { ens: drawns, en: 'polyline', ans: drawns, a: 'text-style-name'}, 132 { ens: drawns, en: 'rect', ans: drawns, a: 'text-style-name'}, 133 { ens: drawns, en: 'regular-polygon', ans: drawns, a: 'text-style-name'}, 134 { ens: officens, en: 'annotation', ans: drawns, a: 'text-style-name'}, 135 { ens: formns, en: 'column', ans: formns, a: 'text-style-name'}, 136 { ens: stylens, en: 'style', ans: stylens, a: 'next-style-name'}, 137 { ens: tablens, en: 'body', ans: tablens, a: 'paragraph-style-name'}, 138 { ens: tablens, en: 'even-columns', ans: tablens, a: 'paragraph-style-name'}, 139 { ens: tablens, en: 'even-rows', ans: tablens, a: 'paragraph-style-name'}, 140 { ens: tablens, en: 'first-column', ans: tablens, a: 'paragraph-style-name'}, 141 { ens: tablens, en: 'first-row', ans: tablens, a: 'paragraph-style-name'}, 142 { ens: tablens, en: 'last-column', ans: tablens, a: 'paragraph-style-name'}, 143 { ens: tablens, en: 'last-row', ans: tablens, a: 'paragraph-style-name'}, 144 { ens: tablens, en: 'odd-columns', ans: tablens, a: 'paragraph-style-name'}, 145 { ens: tablens, en: 'odd-rows', ans: tablens, a: 'paragraph-style-name'}, 146 { ens: textns, en: 'notes-configuration', ans: textns, a: 'default-style-name'}, 147 { ens: textns, en: 'alphabetical-index-entry-template', ans: textns, a: 'style-name'}, 148 { ens: textns, en: 'bibliography-entry-template', ans: textns, a: 'style-name'}, 149 { ens: textns, en: 'h', ans: textns, a: 'style-name'}, 150 { ens: textns, en: 'illustration-index-entry-template', ans: textns, a: 'style-name'}, 151 { ens: textns, en: 'index-source-style', ans: textns, a: 'style-name'}, 152 { ens: textns, en: 'object-index-entry-template', ans: textns, a: 'style-name'}, 153 { ens: textns, en: 'p', ans: textns, a: 'style-name'}, 154 { ens: textns, en: 'table-index-entry-template', ans: textns, a: 'style-name'}, 155 { ens: textns, en: 'table-of-content-entry-template', ans: textns, a: 'style-name'}, 156 { ens: textns, en: 'table-index-entry-template', ans: textns, a: 'style-name'}, 157 { ens: textns, en: 'user-index-entry-template', ans: textns, a: 'style-name'}, 158 { ens: stylens, en: 'page-layout-properties', ans: stylens, a: 'register-truth-ref-style-name'} 159 ], 160 "chart": [ 161 { ens: chartns, en: 'axis', ans: chartns, a: 'style-name'}, 162 { ens: chartns, en: 'chart', ans: chartns, a: 'style-name'}, 163 { ens: chartns, en: 'data-label', ans: chartns, a: 'style-name'}, 164 { ens: chartns, en: 'data-point', ans: chartns, a: 'style-name'}, 165 { ens: chartns, en: 'equation', ans: chartns, a: 'style-name'}, 166 { ens: chartns, en: 'error-indicator', ans: chartns, a: 'style-name'}, 167 { ens: chartns, en: 'floor', ans: chartns, a: 'style-name'}, 168 { ens: chartns, en: 'footer', ans: chartns, a: 'style-name'}, 169 { ens: chartns, en: 'grid', ans: chartns, a: 'style-name'}, 170 { ens: chartns, en: 'legend', ans: chartns, a: 'style-name'}, 171 { ens: chartns, en: 'mean-value', ans: chartns, a: 'style-name'}, 172 { ens: chartns, en: 'plot-area', ans: chartns, a: 'style-name'}, 173 { ens: chartns, en: 'regression-curve', ans: chartns, a: 'style-name'}, 174 { ens: chartns, en: 'series', ans: chartns, a: 'style-name'}, 175 { ens: chartns, en: 'stock-gain-marker', ans: chartns, a: 'style-name'}, 176 { ens: chartns, en: 'stock-loss-marker', ans: chartns, a: 'style-name'}, 177 { ens: chartns, en: 'stock-range-line', ans: chartns, a: 'style-name'}, 178 { ens: chartns, en: 'subtitle', ans: chartns, a: 'style-name'}, 179 { ens: chartns, en: 'title', ans: chartns, a: 'style-name'}, 180 { ens: chartns, en: 'wall', ans: chartns, a: 'style-name'} 181 ], 182 "section": [ 183 { ens: textns, en: 'alphabetical-index', ans: textns, a: 'style-name'}, 184 { ens: textns, en: 'bibliography', ans: textns, a: 'style-name'}, 185 { ens: textns, en: 'illustration-index', ans: textns, a: 'style-name'}, 186 { ens: textns, en: 'index-title', ans: textns, a: 'style-name'}, 187 { ens: textns, en: 'object-index', ans: textns, a: 'style-name'}, 188 { ens: textns, en: 'section', ans: textns, a: 'style-name'}, 189 { ens: textns, en: 'table-of-content', ans: textns, a: 'style-name'}, 190 { ens: textns, en: 'table-index', ans: textns, a: 'style-name'}, 191 { ens: textns, en: 'user-index', ans: textns, a: 'style-name'} 192 ], 193 "ruby": [ 194 { ens: textns, en: 'ruby', ans: textns, a: 'style-name'} 195 ], 196 "table": [ 197 { ens: dbns, en: 'query', ans: dbns, a: 'style-name'}, 198 { ens: dbns, en: 'table-representation', ans: dbns, a: 'style-name'}, 199 { ens: tablens, en: 'background', ans: tablens, a: 'style-name'}, 200 { ens: tablens, en: 'table', ans: tablens, a: 'style-name'} 201 ], 202 "table-column": [ 203 { ens: dbns, en: 'column', ans: dbns, a: 'style-name'}, 204 { ens: tablens, en: 'table-column', ans: tablens, a: 'style-name'} 205 ], 206 "table-row": [ 207 { ens: dbns, en: 'query', ans: dbns, a: 'default-row-style-name'}, 208 { ens: dbns, en: 'table-representation', ans: dbns, a: 'default-row-style-name'}, 209 { ens: tablens, en: 'table-row', ans: tablens, a: 'style-name'} 210 ], 211 "table-cell": [ 212 { ens: dbns, en: 'column', ans: dbns, a: 'default-cell-style-name'}, 213 { ens: tablens, en: 'table-column', ans: tablens, a: 'default-cell-style-name'}, 214 { ens: tablens, en: 'table-row', ans: tablens, a: 'default-cell-style-name'}, 215 { ens: tablens, en: 'body', ans: tablens, a: 'style-name'}, 216 { ens: tablens, en: 'covered-table-cell', ans: tablens, a: 'style-name'}, 217 { ens: tablens, en: 'even-columns', ans: tablens, a: 'style-name'}, 218 { ens: tablens, en: 'covered-table-cell', ans: tablens, a: 'style-name'}, 219 { ens: tablens, en: 'even-columns', ans: tablens, a: 'style-name'}, 220 { ens: tablens, en: 'even-rows', ans: tablens, a: 'style-name'}, 221 { ens: tablens, en: 'first-column', ans: tablens, a: 'style-name'}, 222 { ens: tablens, en: 'first-row', ans: tablens, a: 'style-name'}, 223 { ens: tablens, en: 'last-column', ans: tablens, a: 'style-name'}, 224 { ens: tablens, en: 'last-row', ans: tablens, a: 'style-name'}, 225 { ens: tablens, en: 'odd-columns', ans: tablens, a: 'style-name'}, 226 { ens: tablens, en: 'odd-rows', ans: tablens, a: 'style-name'}, 227 { ens: tablens, en: 'table-cell', ans: tablens, a: 'style-name'} 228 ], 229 "graphic": [ 230 { ens: dr3dns, en: 'cube', ans: drawns, a: 'style-name'}, 231 { ens: dr3dns, en: 'extrude', ans: drawns, a: 'style-name'}, 232 { ens: dr3dns, en: 'rotate', ans: drawns, a: 'style-name'}, 233 { ens: dr3dns, en: 'scene', ans: drawns, a: 'style-name'}, 234 { ens: dr3dns, en: 'sphere', ans: drawns, a: 'style-name'}, 235 { ens: drawns, en: 'caption', ans: drawns, a: 'style-name'}, 236 { ens: drawns, en: 'circle', ans: drawns, a: 'style-name'}, 237 { ens: drawns, en: 'connector', ans: drawns, a: 'style-name'}, 238 { ens: drawns, en: 'control', ans: drawns, a: 'style-name'}, 239 { ens: drawns, en: 'custom-shape', ans: drawns, a: 'style-name'}, 240 { ens: drawns, en: 'ellipse', ans: drawns, a: 'style-name'}, 241 { ens: drawns, en: 'frame', ans: drawns, a: 'style-name'}, 242 { ens: drawns, en: 'g', ans: drawns, a: 'style-name'}, 243 { ens: drawns, en: 'line', ans: drawns, a: 'style-name'}, 244 { ens: drawns, en: 'measure', ans: drawns, a: 'style-name'}, 245 { ens: drawns, en: 'page-thumbnail', ans: drawns, a: 'style-name'}, 246 { ens: drawns, en: 'path', ans: drawns, a: 'style-name'}, 247 { ens: drawns, en: 'polygon', ans: drawns, a: 'style-name'}, 248 { ens: drawns, en: 'polyline', ans: drawns, a: 'style-name'}, 249 { ens: drawns, en: 'rect', ans: drawns, a: 'style-name'}, 250 { ens: drawns, en: 'regular-polygon', ans: drawns, a: 'style-name'}, 251 { ens: officens, en: 'annotation', ans: drawns, a: 'style-name'} 252 ], 253 "presentation": [ 254 { ens: dr3dns, en: 'cube', ans: presentationns, a: 'style-name'}, 255 { ens: dr3dns, en: 'extrude', ans: presentationns, a: 'style-name'}, 256 { ens: dr3dns, en: 'rotate', ans: presentationns, a: 'style-name'}, 257 { ens: dr3dns, en: 'scene', ans: presentationns, a: 'style-name'}, 258 { ens: dr3dns, en: 'sphere', ans: presentationns, a: 'style-name'}, 259 { ens: drawns, en: 'caption', ans: presentationns, a: 'style-name'}, 260 { ens: drawns, en: 'circle', ans: presentationns, a: 'style-name'}, 261 { ens: drawns, en: 'connector', ans: presentationns, a: 'style-name'}, 262 { ens: drawns, en: 'control', ans: presentationns, a: 'style-name'}, 263 { ens: drawns, en: 'custom-shape', ans: presentationns, a: 'style-name'}, 264 { ens: drawns, en: 'ellipse', ans: presentationns, a: 'style-name'}, 265 { ens: drawns, en: 'frame', ans: presentationns, a: 'style-name'}, 266 { ens: drawns, en: 'g', ans: presentationns, a: 'style-name'}, 267 { ens: drawns, en: 'line', ans: presentationns, a: 'style-name'}, 268 { ens: drawns, en: 'measure', ans: presentationns, a: 'style-name'}, 269 { ens: drawns, en: 'page-thumbnail', ans: presentationns, a: 'style-name'}, 270 { ens: drawns, en: 'path', ans: presentationns, a: 'style-name'}, 271 { ens: drawns, en: 'polygon', ans: presentationns, a: 'style-name'}, 272 { ens: drawns, en: 'polyline', ans: presentationns, a: 'style-name'}, 273 { ens: drawns, en: 'rect', ans: presentationns, a: 'style-name'}, 274 { ens: drawns, en: 'regular-polygon', ans: presentationns, a: 'style-name'}, 275 { ens: officens, en: 'annotation', ans: presentationns, a: 'style-name'} 276 ], 277 "drawing-page": [ 278 { ens: drawns, en: 'page', ans: drawns, a: 'style-name'}, 279 { ens: presentationns, en: 'notes', ans: drawns, a: 'style-name'}, 280 { ens: stylens, en: 'handout-master', ans: drawns, a: 'style-name'}, 281 { ens: stylens, en: 'master-page', ans: drawns, a: 'style-name'} 282 ], 283 "list-style": [ 284 { ens: textns, en: 'list', ans: textns, a: 'style-name'}, 285 { ens: textns, en: 'numbered-paragraph', ans: textns, a: 'style-name'}, 286 { ens: textns, en: 'list-item', ans: textns, a: 'style-override'}, 287 { ens: stylens, en: 'style', ans: stylens, a: 'list-style-name'} 288 ], 289 // See http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1416346_253892949 290 "data": [ 291 { ens: stylens, en: 'style', ans: stylens, a: 'data-style-name'}, 292 { ens: stylens, en: 'style', ans: stylens, a: 'percentage-data-style-name'}, 293 { ens: presentationns, en: 'date-time-decl', ans: stylens, a: 'data-style-name'}, 294 { ens: textns, en: 'creation-date', ans: stylens, a: 'data-style-name'}, 295 { ens: textns, en: 'creation-time', ans: stylens, a: 'data-style-name'}, 296 { ens: textns, en: 'database-display', ans: stylens, a: 'data-style-name'}, 297 { ens: textns, en: 'date', ans: stylens, a: 'data-style-name'}, 298 { ens: textns, en: 'editing-duration', ans: stylens, a: 'data-style-name'}, 299 { ens: textns, en: 'expression', ans: stylens, a: 'data-style-name'}, 300 { ens: textns, en: 'meta-field', ans: stylens, a: 'data-style-name'}, 301 { ens: textns, en: 'modification-date', ans: stylens, a: 'data-style-name'}, 302 { ens: textns, en: 'modification-time', ans: stylens, a: 'data-style-name'}, 303 { ens: textns, en: 'print-date', ans: stylens, a: 'data-style-name'}, 304 { ens: textns, en: 'print-time', ans: stylens, a: 'data-style-name'}, 305 { ens: textns, en: 'table-formula', ans: stylens, a: 'data-style-name'}, 306 { ens: textns, en: 'time', ans: stylens, a: 'data-style-name'}, 307 { ens: textns, en: 'user-defined', ans: stylens, a: 'data-style-name'}, 308 { ens: textns, en: 'user-field-get', ans: stylens, a: 'data-style-name'}, 309 { ens: textns, en: 'user-field-input', ans: stylens, a: 'data-style-name'}, 310 { ens: textns, en: 'variable-get', ans: stylens, a: 'data-style-name'}, 311 { ens: textns, en: 'variable-input', ans: stylens, a: 'data-style-name'}, 312 { ens: textns, en: 'variable-set', ans: stylens, a: 'data-style-name'} 313 ], 314 "page-layout": [ 315 { ens: presentationns, en: 'notes', ans: stylens, a: 'page-layout-name'}, 316 { ens: stylens, en: 'handout-master', ans: stylens, a: 'page-layout-name'}, 317 { ens: stylens, en: 'master-page', ans: stylens, a: 'page-layout-name'} 318 ] 319 }, 320 /** 321 * Inversion of elementstyles, created with "inverse(elementstyles);" in 322 * init section 323 * Map with element name as primary key, element namespace as secondary 324 * key, then an array of { 325 * ns: namespace of attribute, 326 * localname: name of attribute, 327 * keyname: keyname 328 * } 329 * @type {!Object.<!string,!Object.<!string,!Array.<!{keyname:!string,ns:!string,localname:!string}>>>} 330 */ 331 elements, 332 xpath = xmldom.XPath; 333 334 /** 335 * Return if a particular element is the parent style for any other style of 336 * the same family. 337 * @param {!Element} odfbody 338 * @param {!function(string):?string} nsResolver 339 * @param {!Element} styleElement 340 * @return {!boolean} 341 */ 342 function hasDerivedStyles(odfbody, nsResolver, styleElement) { 343 var nodes, 344 xp, 345 styleName = styleElement.getAttributeNS(stylens, 'name'), 346 styleFamily = styleElement.getAttributeNS(stylens, 'family'); 347 348 xp = '//style:*[@style:parent-style-name="' + styleName 349 + '"][@style:family="' + styleFamily + '"]'; 350 nodes = xpath.getODFElementsWithXPath(odfbody, xp, nsResolver); 351 if (nodes.length) { 352 return true; 353 } 354 return false; 355 } 356 357 /** 358 * Prefixes all style ids used to refer to styles in the given DOM element 359 * tree with the given prefix. 360 * @param {!Element} element root element of tree of elements using styles 361 * @param {!string} prefix 362 * @return {undefined} 363 */ 364 function prefixUsedStyleNames(element, prefix) { 365 var i, stylename, a, e, ns, elname, elns, 366 /**@type{string}*/ 367 localName, 368 length = 0; 369 elname = elements[element.localName]; 370 if (elname) { 371 elns = elname[element.namespaceURI]; 372 if (elns) { 373 length = elns.length; 374 } 375 } 376 // prefix any used style ids 377 for (i = 0; i < length; i += 1) { 378 a = /**@type{!{ns:string,localname:string}}*/(elns[i]); 379 ns = a.ns; 380 localName = a.localname; 381 stylename = element.getAttributeNS(ns, localName); 382 if (stylename) { // a style reference has been found! 383 element.setAttributeNS(ns, nsprefixes[ns] + localName, 384 prefix + stylename); 385 } 386 } 387 // continue prefixing with all child elements 388 e = element.firstElementChild; 389 while (e) { 390 prefixUsedStyleNames(e, prefix); 391 e = e.nextElementSibling; 392 } 393 } 394 /** 395 * Prefixes the id of the style defined in the given DOM element with the 396 * given prefix. 397 * @param {!Element} styleElement 398 * @param {!string} prefix 399 * @return {undefined} 400 */ 401 function prefixStyleName(styleElement, prefix) { 402 var stylename = styleElement.getAttributeNS(drawns, "name"), 403 ns; 404 if (stylename) { 405 ns = drawns; 406 } else { 407 stylename = styleElement.getAttributeNS(stylens, "name"); 408 if (stylename) { 409 ns = stylens; 410 } 411 } 412 413 if (ns) { 414 styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", 415 prefix + stylename); 416 } 417 } 418 419 /** 420 * Prefixes all style ids with the given prefix. This will affect all style 421 * ids as set in the style definitions by the child elements of 422 * styleElementsRoot and all style ids used to refer to styles, both in 423 * these style definitions and in the given DOM element tree 424 * styleUsingElementsRoot. 425 * @param {?Element} styleElementsRoot root element with styles nodes as childs 426 * @param {!string} prefix 427 * @param {?Element} styleUsingElementsRoot root element of tree of elements using styles 428 * @return {undefined} 429 */ 430 function prefixStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) { 431 var s; 432 if (styleElementsRoot) { 433 // prefix all set style ids 434 s = styleElementsRoot.firstChild; 435 while (s) { 436 if (s.nodeType === Node.ELEMENT_NODE) { 437 prefixStyleName(/**@type{!Element}*/(s), prefix); 438 } 439 s = s.nextSibling; 440 } 441 // prefix all ids in style references 442 prefixUsedStyleNames(styleElementsRoot, prefix); 443 if (styleUsingElementsRoot) { 444 prefixUsedStyleNames(styleUsingElementsRoot, prefix); 445 } 446 } 447 } 448 449 /** 450 * @param {!Element} element root element of tree of elements using styles 451 * @param {!RegExp} regExp 452 * @return {undefined} 453 */ 454 function removeRegExpFromUsedStyleNames(element, regExp) { 455 var i, stylename, e, elname, elns, a, ns, localName, 456 length = 0; 457 elname = elements[element.localName]; 458 if (elname) { 459 elns = elname[element.namespaceURI]; 460 if (elns) { 461 length = elns.length; 462 } 463 } 464 // remove prefix from any used style id 465 for (i = 0; i < length; i += 1) { 466 a = /**@type{!{ns:string,localname:string}}*/(elns[i]); 467 ns = a.ns; 468 localName = a.localname; 469 stylename = element.getAttributeNS(ns, localName); 470 if (stylename) { // a style reference has been found! 471 stylename = stylename.replace(regExp, ''); 472 element.setAttributeNS(ns, nsprefixes[ns] + localName, 473 stylename); 474 } 475 } 476 // continue removal with all child elements 477 e = element.firstElementChild; 478 while (e) { 479 removeRegExpFromUsedStyleNames(e, regExp); 480 e = e.nextElementSibling; 481 } 482 } 483 /** 484 * Remove the given regular expression from the id of the style defined in 485 * the given DOM element. 486 * @param {!Element} styleElement 487 * @param {!RegExp} regExp 488 * @return {undefined} 489 */ 490 function removeRegExpFromStyleName(styleElement, regExp) { 491 var stylename = styleElement.getAttributeNS(drawns, "name"), 492 ns; 493 if (stylename) { 494 ns = drawns; 495 } else { 496 stylename = styleElement.getAttributeNS(stylens, "name"); 497 if (stylename) { 498 ns = stylens; 499 } 500 } 501 502 if (ns) { 503 stylename = stylename.replace(regExp, ''); 504 styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", stylename); 505 } 506 } 507 508 /** 509 * Removes the given prefix from all style ids. This will affect all style 510 * ids as set in the style definitions by the child elements of 511 * styleElementsRoot and all style ids used to refer to styles, both in 512 * these style definitions and in the given DOM element tree 513 * styleUsingElementsRoot. 514 * @param {?Element} styleElementsRoot root element with styles nodes as childs 515 * @param {!string} prefix 516 * @param {?Element} styleUsingElementsRoot root element of tree of elements using styles 517 */ 518 function removePrefixFromStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) { 519 var s, 520 regExp = new RegExp("^" + prefix); 521 522 if (styleElementsRoot) { 523 // remove prefix from all set style ids 524 s = styleElementsRoot.firstChild; 525 while (s) { 526 if (s.nodeType === Node.ELEMENT_NODE) { 527 removeRegExpFromStyleName(/**@type{!Element}*/(s), regExp); 528 } 529 s = s.nextSibling; 530 } 531 // remove prefix from all ids in style references 532 removeRegExpFromUsedStyleNames(styleElementsRoot, regExp); 533 if (styleUsingElementsRoot) { 534 removeRegExpFromUsedStyleNames(styleUsingElementsRoot, regExp); 535 } 536 } 537 } 538 539 /** 540 * Determines all stylenames that are referenced in the passed element 541 * @param {!Element} element element to check for styles 542 * @param {!Object.<string,!Object.<string,number>>=} usedStyles map of used styles names, grouped by style family 543 * @return {!Object.<string,!Object.<string,number>>|undefined} Returns either map of used styles, or undefined if none 544 * have been found an usedStyles was not passed in 545 */ 546 function determineStylesForNode(element, usedStyles) { 547 var i, stylename, elname, elns, a, ns, localName, keyname, 548 length = 0, map; 549 elname = elements[element.localName]; 550 if (elname) { 551 elns = elname[element.namespaceURI]; 552 if (elns) { 553 length = elns.length; 554 } 555 } 556 // check if any styles are referenced 557 for (i = 0; i < length; i += 1) { 558 a = /**@type{!{ns:string,localname:string,keyname:string}}*/(elns[i]); 559 ns = a.ns; 560 localName = a.localname; 561 stylename = element.getAttributeNS(ns, localName); 562 if (stylename) { // a style has been found! 563 usedStyles = usedStyles || {}; 564 keyname = a.keyname; 565 if (usedStyles.hasOwnProperty(keyname)) { 566 usedStyles[keyname][stylename] = 1; 567 } else { 568 map = {}; 569 map[stylename] = 1; 570 usedStyles[keyname] = map; 571 } 572 } 573 } 574 return usedStyles; 575 } 576 577 /** 578 * Determines all stylenames that are referenced in the passed element tree 579 * @param {!Element} styleUsingElementsRoot root element of tree of elements using styles 580 * @param {!Object.<string,Object.<string,number>>} usedStyles map of used styles names, grouped by style family 581 * @return {undefined} 582 */ 583 function determineUsedStyles(styleUsingElementsRoot, usedStyles) { 584 var i, e; 585 determineStylesForNode(styleUsingElementsRoot, usedStyles); 586 // continue determination with all child elements 587 i = styleUsingElementsRoot.firstChild; 588 while (i) { 589 if (i.nodeType === Node.ELEMENT_NODE) { 590 e = /**@type{!Element}*/(i); 591 determineUsedStyles(e, usedStyles); 592 } 593 i = i.nextSibling; 594 } 595 } 596 597 /** 598 * Node defining a style, with references to all required styles necessary to construct it 599 * @param {!string} key Style key 600 * @param {!string} name Style name 601 * @param {!string} family Style family 602 * @constructor 603 */ 604 function StyleDefinition(key, name, family) { 605 /** 606 * Unique style definition key 607 * @type {string} 608 */ 609 this.key = key; 610 611 /** 612 * Style name 613 * @type {string} 614 */ 615 this.name = name; 616 617 /** 618 * Style family (e.g., paragraph, table-cell, text) 619 * @type {string} 620 */ 621 this.family = family; 622 623 /** 624 * Styles directly required by this style 625 * @type {Object.<string, StyleDefinition>} 626 */ 627 this.requires = {}; 628 } 629 630 /** 631 * @param {!string} stylename 632 * @param {!string} stylefamily 633 * @param {!Object.<string,StyleDefinition>} knownStyles map of used stylesnames, grouped by keyname 634 * @return {!StyleDefinition} 635 */ 636 function getStyleDefinition(stylename, stylefamily, knownStyles) { 637 var styleKey = stylename + '"' + stylefamily, 638 styleDefinition = knownStyles[styleKey]; 639 if (!styleDefinition) { 640 styleDefinition = knownStyles[styleKey] = new StyleDefinition(styleKey, stylename, stylefamily); 641 } 642 return styleDefinition; 643 } 644 645 /** 646 * Builds a style dependency map for the supplied style tree 647 * @param {!Element} element root element of tree of elements using styles 648 * @param {?StyleDefinition} styleScope parent style the specified style element is part of 649 * @param {!Object.<string,StyleDefinition>} knownStyles map of used stylesnames, grouped by keyname 650 * @return {!Object.<string,StyleDefinition>} 651 */ 652 function determineDependentStyles(element, styleScope, knownStyles) { 653 var i, stylename, elname, elns, a, ns, localName, e, 654 referencedStyleFamily, referencedStyleDef, 655 length = 0, 656 newScopeName = element.getAttributeNS(stylens, 'name'), 657 newScopeFamily = element.getAttributeNS(stylens, 'family'); 658 if (newScopeName && newScopeFamily) { 659 styleScope = getStyleDefinition(newScopeName, newScopeFamily, 660 knownStyles); 661 } 662 if (styleScope) { 663 elname = elements[element.localName]; 664 if (elname) { 665 elns = elname[element.namespaceURI]; 666 if (elns) { 667 length = elns.length; 668 } 669 } 670 // check if any styles are referenced 671 for (i = 0; i < length; i += 1) { 672 a = /**@type{!{ns:string,localname:string,keyname:string}}*/(elns[i]); 673 ns = a.ns; 674 localName = a.localname; 675 stylename = element.getAttributeNS(ns, localName); 676 if (stylename) { // a style has been found! 677 referencedStyleFamily = a.keyname; 678 referencedStyleDef = getStyleDefinition(stylename, referencedStyleFamily, knownStyles); 679 styleScope.requires[referencedStyleDef.key] = referencedStyleDef; 680 } 681 } 682 } 683 684 // continue determination with all child elements 685 e = element.firstElementChild; 686 while (e) { 687 determineDependentStyles(e, styleScope, knownStyles); 688 e = e.nextElementSibling; 689 } 690 return knownStyles; 691 } 692 693 /** 694 * Creates the elements data from the elementstyles data. 695 * @return {!Object.<string,Object.<string,Array.<Object.<string,string>>>>} 696 */ 697 function inverse() { 698 var i, l, 699 /**@type{string}*/ 700 keyname, 701 /**@type{!Array.<!{ens:string,en:string,ans:string,a:string}>}*/ 702 list, 703 /**@type{!{en:string,ens:string}}*/ 704 item, 705 /**@type{!Object.<string,Object.<string,Array.<Object.<string,string>>>>}*/ 706 e = {}, 707 map, array, en, ens; 708 for (keyname in elementstyles) { 709 if (elementstyles.hasOwnProperty(keyname)) { 710 list = elementstyles[keyname]; 711 l = list.length; 712 for (i = 0; i < l; i += 1) { 713 item = list[i]; 714 en = item.en; 715 ens = item.ens; 716 if (e.hasOwnProperty(en)) { 717 map = e[en]; 718 } else { 719 e[en] = map = {}; 720 } 721 if (map.hasOwnProperty(ens)) { 722 array = map[ens]; 723 } else { 724 map[ens] = array = []; 725 } 726 array.push({ 727 ns: item.ans, 728 localname: item.a, 729 keyname: keyname 730 }); 731 } 732 } 733 } 734 return e; 735 } 736 737 /** 738 * Merges the specified style, and style required to complete it into the usedStyles map 739 * @param {!StyleDefinition} styleDependency Style to merge 740 * @param {!Object.<string,Object.<string,number>>} usedStyles Styles map to merge data into 741 * @return {undefined} 742 */ 743 function mergeRequiredStyles(styleDependency, usedStyles) { 744 var family = usedStyles[styleDependency.family]; 745 if (!family) { 746 family = usedStyles[styleDependency.family] = {}; 747 } 748 family[styleDependency.name] = 1; 749 Object.keys(/**@type {!Object}*/(styleDependency.requires)).forEach(function(requiredStyleKey) { 750 mergeRequiredStyles(/**@type {!StyleDefinition}*/(styleDependency.requires[requiredStyleKey]) , usedStyles); 751 }); 752 } 753 754 /** 755 * Marks all required styles as used for any automatic styles referenced within the existing usedStyles map 756 * @param {!Element} automaticStylesRoot Automatic styles tree root 757 * @param {!Object.<string,Object.<string,number>>} usedStyles Styles already referenced 758 * @return {undefined} 759 */ 760 function mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles) { 761 var automaticStyles = determineDependentStyles(automaticStylesRoot, null, {}); 762 // Merge into usedStyles 763 Object.keys(automaticStyles).forEach(function(styleKey) { 764 var automaticStyleDefinition = automaticStyles[styleKey], 765 usedFamily = usedStyles[automaticStyleDefinition.family]; 766 767 // For each style referenced by the main root, mark all required automatic styles as used as well 768 if (usedFamily && usedFamily.hasOwnProperty(automaticStyleDefinition.name)) { 769 mergeRequiredStyles(automaticStyleDefinition, usedStyles); 770 } 771 }); 772 } 773 774 /** 775 * Collects all names of font-face declarations which are referenced in the 776 * children elements of the given root element. 777 * @param {!Object.<!string,!boolean>} usedFontFaceDeclMap 778 * @param {?Element} styleElement root element with style elements as childs 779 * @return {undefined} 780 */ 781 function collectUsedFontFaces(usedFontFaceDeclMap, styleElement) { 782 var localNames = ["font-name", "font-name-asian", "font-name-complex"], 783 e, 784 /**@type{!Element}*/ 785 currentElement; 786 787 /** 788 * @param {string} localName 789 */ 790 function collectByAttribute(localName) { 791 var fontFaceName = currentElement.getAttributeNS(stylens, 792 localName); 793 if (fontFaceName) { 794 usedFontFaceDeclMap[fontFaceName] = true; 795 } 796 } 797 798 e = styleElement && styleElement.firstElementChild; 799 while (e) { 800 // TODO: only check elements which have those attributes defined 801 currentElement = e; 802 localNames.forEach(collectByAttribute); 803 collectUsedFontFaces(usedFontFaceDeclMap, currentElement); 804 e = e.nextElementSibling; 805 } 806 } 807 this.collectUsedFontFaces = collectUsedFontFaces; 808 809 /** 810 * Changes all names of font-face declarations which are referenced in the 811 * children elements of the given root element. 812 * @param {?Element} styleElement root element with style elements as childs 813 * @param {!Object.<!string,!string>} fontFaceNameChangeMap 814 * @return {undefined} 815 */ 816 function changeFontFaceNames(styleElement, fontFaceNameChangeMap) { 817 var localNames = ["font-name", "font-name-asian", "font-name-complex"], 818 e, 819 /**@type{!Element}*/ 820 currentElement; 821 822 /** 823 * @param {string} localName 824 */ 825 function changeFontFaceNameByAttribute(localName) { 826 var fontFaceName = currentElement.getAttributeNS(stylens, localName); 827 if (fontFaceName && fontFaceNameChangeMap.hasOwnProperty(fontFaceName)) { 828 currentElement.setAttributeNS(stylens, "style:" + localName, fontFaceNameChangeMap[fontFaceName]); 829 } 830 } 831 832 e = styleElement && styleElement.firstElementChild; 833 while (e) { 834 // TODO: only check elements which have those attributes defined 835 currentElement = e; 836 localNames.forEach(changeFontFaceNameByAttribute); 837 changeFontFaceNames(currentElement, fontFaceNameChangeMap); 838 e = e.nextElementSibling; 839 } 840 } 841 this.changeFontFaceNames = changeFontFaceNames; 842 843 /** 844 * Object which collects all style names that are used in the passed element tree 845 * @constructor 846 * @param {!Element} styleUsingElementsRoot root element of tree of elements using styles 847 * @param {?Element=} automaticStylesRoot Additional style information. Styles in this tree are only important 848 * when used as part of a chain of styles referenced from within the stylesUsingElementsRoot node 849 */ 850 this.UsedStyleList = function (styleUsingElementsRoot, automaticStylesRoot) { 851 // usedStyles stores all style names used in the passed element tree. 852 // As styles from different types can have the same names, 853 // all styles are grouped by: 854 // * family attribute for style:style 855 // * "data" for all number:* (boolean-style,currency-style,date-style, 856 // number-style,percentage-style,text-style,time-style) 857 // * localName for text:list-style, style:page-layout 858 var /** @type !Object.<string,Object.<string,number>> */usedStyles = {}; 859 860 /** 861 * Checks whether the passed style is referenced by anything 862 * @param {!Element} element odf style describing element 863 * @return {!boolean} 864 */ 865 this.uses = function (element) { 866 var localName = element.localName, 867 name = element.getAttributeNS(drawns, "name") || 868 element.getAttributeNS(stylens, "name"), 869 keyName, map; 870 if (localName === "style") { 871 keyName = element.getAttributeNS(stylens, "family"); 872 } else if (element.namespaceURI === numberns) { 873 keyName = "data"; 874 } else { 875 keyName = localName; // list-style or page-layout 876 } 877 map = usedStyles[keyName]; 878 return map ? (map[name] > 0) : false; 879 }; 880 881 determineUsedStyles(styleUsingElementsRoot, usedStyles); 882 if (automaticStylesRoot) { 883 mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles); 884 } 885 }; 886 887 /** 888 * Return the name of the style for the given family if it is associated 889 * with the element. 890 * @param {!string} family 891 * @param {!Element} element 892 * @return {!string|undefined} 893 */ 894 function getStyleName(family, element) { 895 var stylename, i, 896 map = elements[element.localName]; 897 if (map) { 898 map = map[element.namespaceURI]; 899 if (map) { 900 for (i = 0; i < map.length; i += 1) { 901 if (map[i].keyname === family) { 902 map = map[i]; 903 if (element.hasAttributeNS(map.ns, map.localname)) { 904 stylename = element.getAttributeNS(map.ns, map.localname); 905 break; 906 } 907 } 908 } 909 } 910 } 911 return stylename; 912 } 913 this.getStyleName = getStyleName; 914 915 this.hasDerivedStyles = hasDerivedStyles; 916 this.prefixStyleNames = prefixStyleNames; 917 this.removePrefixFromStyleNames = removePrefixFromStyleNames; 918 this.determineStylesForNode = determineStylesForNode; 919 920 921 // init 922 elements = inverse(); 923 }; 924