1 /**
  2  * Copyright (C) 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 runtime, odf*/
 26 
 27 /**
 28  * Object that retrieves properties lazily and caches them.
 29  * If the element does not define the property it is retrieved from a parent
 30  * LazyStyleProperties object.
 31  * An object with getters functions is passed into the constructor. There must
 32  * be a getter function for each property.
 33  * @constructor
 34  * @param {!odf.LazyStyleProperties|undefined} parent
 35  * @param {!Object.<!string,function():*>} getters
 36  */
 37 odf.LazyStyleProperties = function (parent, getters) {
 38     "use strict";
 39     var /**@type{!Object.<!string,*>}*/
 40         data = {};
 41     /**
 42      * Retrieve a value by name.
 43      * The getter for the value must be defined in the getters object.
 44      * If the getter returns undefined and a parent object is provided, the
 45      * value is gotten from the parent object.
 46      * @param {!string} name
 47      * @return {*}
 48      */
 49     this.value = function (name) {
 50         var /**@type{*}*/
 51             v;
 52         if (data.hasOwnProperty(name)) {
 53             v = data[name];
 54         } else {
 55             v = getters[name]();
 56             if (v === undefined && parent) {
 57                 v = parent.value(name);
 58             }
 59             data[name] = v;
 60         }
 61         return v;
 62     };
 63     /**
 64      * Give a new parent to the LazyStyleProperties.
 65      * The cache is invalidated when this is done.
 66      * @param {!odf.LazyStyleProperties|undefined} p
 67      * @return {undefined}
 68      */
 69     this.reset = function (p) {
 70         parent = p;
 71         data = {};
 72     };
 73 };
 74 /**
 75  * A collection of helper functions for parsing style attributes.
 76  * @constructor
 77  */
 78 odf.StyleParseUtils = function () {
 79     "use strict";
 80     var stylens = odf.Namespaces.stylens;
 81     /**
 82      * Returns the length split as value and unit, from an ODF attribute.
 83      * If the length does not match the regular expression, null is returned.
 84      * @param {?string|undefined} length
 85      * @return {?{value:!number,unit:!string}}
 86      */
 87     function splitLength(length) {
 88         var re = /(-?[0-9]*[0-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px))/,
 89             m = re.exec(length);
 90         if (!m) {
 91             return null;
 92         }
 93         return {value: parseFloat(m[1]), unit: m[3]};
 94     }
 95     /**
 96      * Convert a unit in a string to number of pixels at 96 dpi.
 97      * If the input value has unit 'px' or is a number, the number is taken as
 98      * is. Other allowed unit: cm, mm, pt, pc.
 99      * If the value cannot be parsed, the value undefined is returned.
100      * @param {?string|undefined} val
101      * @return {!number|undefined}
102      */
103     function parseLength(val) {
104         var n, length, unit;
105         length = splitLength(val);
106         unit = length && length.unit;
107         if (unit === "px") {
108             n = length.value;
109         } else if (unit === "cm") {
110             n = length.value / 2.54 * 96;
111         } else if (unit === "mm") {
112             n = length.value / 25.4 * 96;
113         } else if (unit === "in") {
114             n = length.value * 96;
115         } else if (unit === "pt") {
116             n = length.value / 0.75;
117         } else if (unit === "pc") {
118             n = length.value * 16;
119         }
120         return n;
121     }
122     this.parseLength = parseLength;
123     /**
124      * Parse a percentage of the form -?([0-9]+(\.[0-9]*)?|\.[0-9]+)%.
125      * If parsing fails undefined is returned.
126      * @param {?string|undefined} value
127      * @return {!number|undefined}
128      */
129     function parsePercent(value) {
130         var v;
131         if (value) {
132             v = parseFloat(value.substr(0, value.indexOf("%")));
133             if (isNaN(v)) {
134                 v = undefined;
135             }
136         }
137         return v;
138     }
139     /**
140      * Parse a value that is a positive length or a percentage.
141      * If parsing fails undefined is returned.
142      * @param {?string|undefined} value
143      * @param {!string} name
144      * @param {!odf.LazyStyleProperties|undefined} parent
145      * @return {!number|undefined}
146      */
147     function parsePositiveLengthOrPercent(value, name, parent) {
148         var v = parsePercent(value),
149             parentValue;
150         if (v !== undefined) {
151             if (parent) {
152                 parentValue = parent.value(name);
153             }
154             if (parentValue === undefined) {
155                 v = undefined;
156             } else {
157                 v *= /**@type{!number}*/(parentValue) / 100;
158             }
159         } else {
160             v = parseLength(value);
161         }
162         return v;
163     }
164     this.parsePositiveLengthOrPercent = parsePositiveLengthOrPercent;
165     /**
166      * Find a child element from the ODF style namespace with the given local
167      * name.
168      * The search is started after the given previousPropertyElement or, if
169      * previousPropertyElement is not given, from the first child element.
170      * @param {!string} name
171      * @param {!Element} styleElement
172      * @param {?Element=} previousPropertyElement
173      * @return {?Element}
174      */
175     function getPropertiesElement(name, styleElement, previousPropertyElement) {
176         var e = previousPropertyElement
177                 ? previousPropertyElement.nextElementSibling
178                 : styleElement.firstElementChild;
179         while (e !== null && (e.localName !== name || e.namespaceURI !== stylens)) {
180             e = e.nextElementSibling;
181         }
182         return e;
183     }
184     this.getPropertiesElement = getPropertiesElement;
185 
186 
187     /**
188      * Split a space-separated attribute list into it's list items. Ignores leading & trailing
189      * whitespace, and collapses excessive internal whitespace. If the input text is null, undefined
190      * or pure whitespace, an empty array will be returned.
191      *
192      * @param {?string|undefined} text
193      * @return {!Array.<!string>}
194      */
195     /*jslint regexp: true*/
196     function parseAttributeList(text) {
197         if (text) {
198             text = text.replace(/^\s*(.*?)\s*$/g, "$1"); // Trim leading + trailing whitespace
199         }
200         // Calling split on an empty string returns a [""]. Avoid this by only attempting to split if the
201         // string is non-zero-length
202         return text && text.length > 0 ? text.split(/\s+/) : [];
203     }
204     /*jslint regexp: false*/
205     this.parseAttributeList = parseAttributeList;
206 };
207