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