Add simple android client.
[odfkit:webodf.git] / android / assets / lib / odf / Style2CSS.js
1 /*global XPathResult odf*/
2 /**
3  * @constructor
4  */
5 odf.Style2CSS = function Style2CSS() {
6     // helper constants
7     var xlinkns = 'http://www.w3.org/1999/xlink',
8
9         drawns = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
10         fons = "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
11         officens = "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
12         presentationns = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
13         stylens = "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
14         svgns = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
15         tablens = "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
16         textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
17         namespaces = {
18             draw: drawns,
19             fo: fons,
20             office: officens,
21             presentation: presentationns,
22             style: stylens,
23             table: tablens,
24             text: textns
25         },
26
27         familynamespaceprefixes = {
28             graphic: 'draw',
29             paragraph: 'text',
30             presentation: 'presentation',
31             ruby: 'text',
32             section: 'text',
33             table: 'table',
34             'table-cell': 'table',
35             'table-column': 'table',
36             'table-row': 'table',
37             text: 'text'
38         },
39
40         familytagnames = {
41             graphic: ['circle', 'connected', 'control', 'custom-shape',
42                 'ellipse', 'frame', 'g', 'line', 'measure', 'page',
43                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
44                 'regular-polygon' ],
45             paragraph: ['alphabetical-index-entry-template', 'h',
46                 'illustration-index-entry-template', 'index-source-style',
47                 'object-index-entry-template', 'p',
48                 'table-index-entry-template', 'table-of-content-entry-template',
49                 'user-index-entry-template'],
50             presentation: ['caption', 'circle', 'connector', 'control',
51                 'custom-shape', 'ellipse', 'frame', 'g', 'line', 'measure',
52                 'page-thumbnail', 'path', 'polygon', 'polyline', 'rect',
53                 'regular-polygon'],
54             ruby: ['ruby', 'ruby-text'],
55             section: ['alphabetical-index', 'bibliography',
56                 'illustration-index', 'index-title', 'object-index', 'section',
57                 'table-of-content', 'table-index', 'user-index'],
58             table: ['background', 'table'],
59             'table-cell': ['body', 'covered-table-cell', 'even-columns',
60                 'even-rows', 'first-column', 'first-row', 'last-column',
61                 'last-row', 'odd-columns', 'odd-rows', 'table-cell'],
62             'table-column': ['table-column'],
63             'table-row': ['table-row'],
64             text: ['a', 'index-entry-chapter', 'index-entry-link-end',
65                 'index-entry-link-start', 'index-entry-page-number',
66                 'index-entry-span', 'index-entry-tab-stop', 'index-entry-text',
67                 'index-title-template', 'linenumbering-configuration',
68                 'list-level-style-number', 'list-level-style-bullet',
69                 'outline-level-style', 'span']
70         },
71
72         textPropertySimpleMapping = [
73             [ fons, 'color', 'color' ],
74             // this sets the element background, not just the text background
75             [ fons, 'background-color', 'background-color' ],
76             [ fons, 'font-weight', 'font-weight' ],
77             [ fons, 'font-style', 'font-style' ],
78             [ fons, 'font-size', 'font-size' ]
79         ],
80
81         bgImageSimpleMapping = [
82             [ stylens, 'repeat', 'background-repeat' ]
83         ],
84
85         paragraphPropertySimpleMapping = [
86             [ fons, 'background-color', 'background-color' ],
87             [ fons, 'text-align', 'text-align' ],
88             [ fons, 'padding-left', 'padding-left' ],
89             [ fons, 'padding-right', 'padding-right' ],
90             [ fons, 'padding-top', 'padding-top' ],
91             [ fons, 'padding-bottom', 'padding-bottom' ],
92             [ fons, 'border-left', 'border-left' ],
93             [ fons, 'border-right', 'border-right' ],
94             [ fons, 'border-top', 'border-top' ],
95             [ fons, 'border-bottom', 'border-bottom' ],
96             [ fons, 'margin-left', 'margin-left' ],
97             [ fons, 'margin-right', 'margin-right' ],
98             [ fons, 'margin-top', 'margin-top' ],
99             [ fons, 'margin-bottom', 'margin-bottom' ],
100             [ fons, 'border', 'border' ]
101         ],
102         
103         graphicPropertySimpleMapping = [
104             [ drawns, 'fill-color', 'background-color' ],
105             [ drawns, 'fill', 'background' ],
106             [ fons, 'min-height', 'min-height' ],
107             [ drawns, 'stroke', 'border' ],
108             [ svgns, 'stroke-color', 'border-color' ]
109         ],
110     
111         tablecellPropertySimpleMapping = [
112             [ fons, 'background-color', 'background-color' ],
113             [ fons, 'border-left', 'border-left' ],
114             [ fons, 'border-right', 'border-right' ],
115             [ fons, 'border-top', 'border-top' ],
116             [ fons, 'border-bottom', 'border-bottom' ]
117         ];
118     
119     // helper functions
120     /**
121      * @param {string} prefix
122      * @return {string}
123      */
124     function namespaceResolver(prefix) {
125         return namespaces[prefix];
126     }
127     /**
128      * @param {!Document} doc
129      * @param {!Element} stylesnode
130      * @return {!Object}
131      */
132     function getStyleMap(doc, stylesnode) {
133         // put all style elements in a hash map by family and name
134         var stylemap = {}, iter, node, name, family;
135         node = stylesnode.firstChild;
136         while (node) {
137             if (node.namespaceURI === stylens && node.localName === 'style') {
138                 name = node.getAttributeNS(stylens, 'name');
139                 family = node.getAttributeNS(stylens, 'family');
140                 if (!stylemap[family]) {
141                     stylemap[family] = {};
142                 }
143                 stylemap[family][name] = node;
144             }
145             node = node.nextSibling;
146         }
147         return stylemap;
148     }
149     /**
150      * @param {?Object} stylestree
151      * @param {?string} name
152      * @return {?string}
153      */
154     function findStyle(stylestree, name) {
155         if (!name || !stylestree) {
156             return null;
157         }
158         if (stylestree[name]) {
159             return stylestree[name];
160         }
161         var derivedStyles = stylestree.derivedStyles,
162             n, style;
163         for (n in stylestree) {
164             if (stylestree.hasOwnProperty(n)) {
165                 style = findStyle(stylestree[n].derivedStyles, name);
166                 if (style) {
167                     return style;
168                 }
169             }
170         }
171         return null;
172     }
173     /**
174      * @param {!string} stylename
175      * @param {!Object} stylesmap
176      * @param {!Object} stylestree
177      * @return {undefined}
178      */
179     function addStyleToStyleTree(stylename, stylesmap, stylestree) {
180         var style = stylesmap[stylename], parentname, parentstyle;
181         if (!style) {
182             return;
183         }
184         parentname = style.getAttributeNS(stylens, 'parent-style-name');
185         parentstyle = null;
186         if (parentname) {
187             parentstyle = findStyle(stylestree, parentname);
188             if (!parentstyle && stylesmap[parentname]) {
189                 // parent style has not been handled yet, do that now
190                 addStyleToStyleTree(parentname, stylesmap, stylestree);
191                 parentstyle = stylesmap[parentname];
192                 stylesmap[parentname] = null;
193             }
194         }
195         if (parentstyle) {
196             if (!parentstyle.derivedStyles) {
197                 parentstyle.derivedStyles = {};
198             }
199             parentstyle.derivedStyles[stylename] = style;
200         } else {
201             // no parent so add the root
202             stylestree[stylename] = style;            
203         }
204     }
205     /**
206      * @param {!Object} stylesmap
207      * @param {!Object} stylestree
208      * @return {undefined}
209      */
210     function addStyleMapToStyleTree(stylesmap, stylestree) {
211         var name;
212         for (name in stylesmap) {
213             if (stylesmap.hasOwnProperty(name)) {
214                 addStyleToStyleTree(name, stylesmap, stylestree);
215                 stylesmap[name] = null;
216             }
217         }
218     }
219     /**
220      * @param {!string} family
221      * @param {!string} name
222      * @return {?string}
223      */
224     function createSelector(family, name) {
225         var prefix = familynamespaceprefixes[family],
226             namepart,
227             selector = "",
228             first = true;
229         if (prefix === null) {
230             return null;
231         }
232         namepart = '[' + prefix + '|style-name="' + name + '"]';
233         if (prefix === 'presentation') {
234             prefix = 'draw';
235             namepart = '[presentation|style-name="' + name + '"]';
236         }
237         return prefix + '|' + familytagnames[family].join(
238                 namepart + ',' + prefix + '|') + namepart;
239     }
240     /**
241      * @param {!string} family
242      * @param {!string} name
243      * @param {!Element} node
244      * @return {!Array}
245      */
246     function getSelectors(family, name, node) {
247         var selectors = [], n, ss, s;
248         selectors.push(createSelector(family, name));
249         for (n in node.derivedStyles) {
250             if (node.derivedStyles.hasOwnProperty(n)) {
251                 ss = getSelectors(family, n, node.derivedStyles[n]);
252                 for (s in ss) {
253                     if (ss.hasOwnProperty(s)) {
254                         selectors.push(ss[s]);
255                     }
256                 }
257             }
258         }
259         return selectors;
260     }
261     /**
262      * @param {?Element} node
263      * @param {!string} ns
264      * @param {!string} name
265      * @return {?Element}
266      */
267     function getDirectChild(node, ns, name) {
268         if (!node) {
269             return null;
270         }
271         var c = node.firstChild;
272         while (c) {
273             if (c.namespaceURI === ns && c.localName === name) {
274                 return /**@type{Element}*/(c);
275             }
276             c = c.nextSibling;
277         }
278         return null;
279     }
280     /**
281      * @param {!Element} props
282      * @param {!Object} mapping
283      * @return {!string}
284      */
285     function applySimpleMapping(props, mapping) {
286         var rule = '', r, value;
287         for (r in mapping) {
288             if (mapping.hasOwnProperty(r)) {
289                 r = mapping[r];
290                 value = props.getAttributeNS(r[0], r[1]);
291                 if (value) {
292                     rule += r[2] + ':' + value + ';';
293                 }
294             }
295         }
296         return rule;
297     }
298     /**
299      * @param {!string} name
300      * @return {!string}
301      */
302     function getFontDeclaration(name) {
303         return '"' + name + '"';
304     }
305     /**
306      * @param {!Element} props
307      * @return {!string}
308      */
309     function getTextProperties(props) {
310         var rule = '', value;
311         rule += applySimpleMapping(props, textPropertySimpleMapping);
312         value = props.getAttributeNS(stylens, 'text-underline-style');
313         if (value === 'solid') {
314             rule += 'text-decoration: underline;';
315         }
316         value = props.getAttributeNS(stylens, 'font-name');
317         if (value) {
318             value = getFontDeclaration(value);
319             if (value) {
320                 rule += 'font-family: ' + value + ';';
321             }
322         }
323         return rule;
324     }
325     /**
326      * @param {!Element} props
327      * @return {!string}
328      */
329     function getParagraphProperties(props) {
330         var rule = '', imageProps, url, element;
331         rule += applySimpleMapping(props, paragraphPropertySimpleMapping);
332         imageProps = props.getElementsByTagNameNS(stylens, 'background-image');
333         if (imageProps.length > 0) {
334             url = imageProps.item(0).getAttributeNS(xlinkns, 'href');
335             if (url) {
336                 rule += "background-image: url('odfkit:" + url + "');";
337                 //rule += "background-repeat: repeat;"; //FIXME test
338                 element = /**@type{!Element}*/(imageProps.item(0));
339                 rule += applySimpleMapping(element, bgImageSimpleMapping);
340             }
341         }
342         return rule;
343     }
344     /**
345      * @param {!Element} props
346      * @return {!string}
347      */
348     function getGraphicProperties(props) {
349         var rule = '';
350         rule += applySimpleMapping(props, graphicPropertySimpleMapping);
351         return rule;
352     }
353     /**
354      * @param {!Element} props
355      * @return {!string}
356      */
357     function getTableCellProperties(props) {
358         var rule = '';
359         rule += applySimpleMapping(props, tablecellPropertySimpleMapping);
360         return rule;
361     }
362     /**
363      * @param {!StyleSheet} sheet
364      * @param {!string} family
365      * @param {!string} name
366      * @param {!Element} node
367      * @return {undefined}
368      */
369     function addRule(sheet, family, name, node) {
370         var selectors = getSelectors(family, name, node),
371             selector = selectors.join(','),
372             rule = '',
373             properties = getDirectChild(node, stylens, 'text-properties');
374         if (properties) {
375             rule += getTextProperties(properties);
376         }
377         properties = getDirectChild(node, stylens, 'paragraph-properties');
378         if (properties) {
379             rule += getParagraphProperties(properties);
380         }
381         properties = getDirectChild(node, stylens, 'graphic-properties');
382         if (properties) {
383             rule += getGraphicProperties(properties);
384         }
385         properties = getDirectChild(node, stylens, 'table-cell-properties');
386         if (properties) {
387             rule += getTableCellProperties(properties);
388         }
389         if (rule.length === 0) {
390             return;
391         }
392         rule = selector + '{' + rule + '}';
393         try {
394             sheet.insertRule(rule, sheet.cssRules.length);
395         } catch (e) {
396             throw e;
397         }
398     }
399     /**
400      * @param {!StyleSheet} sheet
401      * @param {!string} family
402      * @param {!string} name
403      * @param {!Element} node
404      * @return {undefined}
405      */
406     function addRules(sheet, family, name, node) {
407         addRule(sheet, family, name, node);
408         var n;
409         for (n in node.derivedStyles) {
410             if (node.derivedStyles.hasOwnProperty(n)) {
411                 addRules(sheet, family, n, node.derivedStyles[n]);
412             }
413         }
414     }
415
416     // css vs odf styles
417     // ODF styles occur in families. A family is a group of odf elements to
418     // which an element applies. ODF families can be mapped to a group of css elements
419
420     /**
421      * @param {!StyleSheet} stylesheet
422      * @param {!Element} styles
423      * @param {!Element} autostyles
424      * @return {undefined}
425      */ 
426     this.style2css = function (stylesheet, styles, autostyles) {
427         var doc, prefix, styletree, tree, name, rule, family,
428             stylenodes, styleautonodes;
429     
430         // make stylesheet empty
431         while (stylesheet.cssRules.length) {
432             stylesheet.deleteRule(stylesheet.cssRules.length - 1);
433         }
434         doc = styles.ownerDocument;
435         if (!doc) {
436             return;
437         }
438         // add @namespace rules
439         for (prefix in namespaces) {
440             if (namespaces.hasOwnProperty(prefix)) {
441                 rule = '@namespace ' + prefix + ' url(' + namespaces[prefix] + ')';
442                 try {
443                     stylesheet.insertRule(rule, stylesheet.cssRules.length);
444                 } catch (e) {
445                     // WebKit can throw an exception here, but it will have retained
446                     // the namespace declarations anyway.
447                 }
448             }
449         }
450         
451         // add the various styles
452         stylenodes = getStyleMap(doc, styles);
453         styleautonodes = getStyleMap(doc, autostyles);
454         styletree = {}; 
455         for (family in familynamespaceprefixes) {
456             if (familynamespaceprefixes.hasOwnProperty(family)) {
457                 tree = styletree[family] = {};
458                 addStyleMapToStyleTree(stylenodes[family], tree);
459                 addStyleMapToStyleTree(styleautonodes[family], tree);
460     
461                 for (name in tree) {
462                     if (tree.hasOwnProperty(name)) {
463                         addRules(stylesheet, family, name, tree[name]);
464                     }
465                 }
466             }
467         }
468     };
469 };