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