Added Android code
[wl-app.git] / Android / folioreader / src / main / assets / js / rangy-serializer.js
1 /**
2  * Serializer module for Rangy.
3  * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a
4  * cookie or local storage and restore it on the user's next visit to the same page.
5  *
6  * Part of Rangy, a cross-browser JavaScript range and selection library
7  * https://github.com/timdown/rangy
8  *
9  * Depends on Rangy core.
10  *
11  * Copyright 2015, Tim Down
12  * Licensed under the MIT license.
13  * Version: 1.3.0
14  * Build date: 10 May 2015
15  */
16 (function(factory, root) {
17     if (typeof define == "function" && define.amd) {
18         // AMD. Register as an anonymous module with a dependency on Rangy.
19         define(["./rangy-core"], factory);
20     } else if (typeof module != "undefined" && typeof exports == "object") {
21         // Node/CommonJS style
22         module.exports = factory( require("rangy") );
23     } else {
24         // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
25         factory(root.rangy);
26     }
27 })(function(rangy) {
28     rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
29         var UNDEF = "undefined";
30         var util = api.util;
31
32         // encodeURIComponent and decodeURIComponent are required for cookie handling
33         if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) {
34             module.fail("encodeURIComponent and/or decodeURIComponent method is missing");
35         }
36
37         // Checksum for checking whether range can be serialized
38         var crc32 = (function() {
39             function utf8encode(str) {
40                 var utf8CharCodes = [];
41
42                 for (var i = 0, len = str.length, c; i < len; ++i) {
43                     c = str.charCodeAt(i);
44                     if (c < 128) {
45                         utf8CharCodes.push(c);
46                     } else if (c < 2048) {
47                         utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);
48                     } else {
49                         utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);
50                     }
51                 }
52                 return utf8CharCodes;
53             }
54
55             var cachedCrcTable = null;
56
57             function buildCRCTable() {
58                 var table = [];
59                 for (var i = 0, j, crc; i < 256; ++i) {
60                     crc = i;
61                     j = 8;
62                     while (j--) {
63                         if ((crc & 1) == 1) {
64                             crc = (crc >>> 1) ^ 0xEDB88320;
65                         } else {
66                             crc >>>= 1;
67                         }
68                     }
69                     table[i] = crc >>> 0;
70                 }
71                 return table;
72             }
73
74             function getCrcTable() {
75                 if (!cachedCrcTable) {
76                     cachedCrcTable = buildCRCTable();
77                 }
78                 return cachedCrcTable;
79             }
80
81             return function(str) {
82                 var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable();
83                 for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) {
84                     y = (crc ^ utf8CharCodes[i]) & 0xFF;
85                     crc = (crc >>> 8) ^ crcTable[y];
86                 }
87                 return (crc ^ -1) >>> 0;
88             };
89         })();
90
91         var dom = api.dom;
92
93         function escapeTextForHtml(str) {
94             return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
95         }
96
97         function nodeToInfoString(node, infoParts) {
98             infoParts = infoParts || [];
99             var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;
100             var nodeInfo = [nodeType, node.nodeName, childCount].join(":");
101             var start = "", end = "";
102             switch (nodeType) {
103                 case 3: // Text node
104                     start = escapeTextForHtml(node.nodeValue);
105                     break;
106                 case 8: // Comment
107                     start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->";
108                     break;
109                 default:
110                     start = "<" + nodeInfo + ">";
111                     end = "</>";
112                     break;
113             }
114             if (start) {
115                 infoParts.push(start);
116             }
117             for (var i = 0; i < childCount; ++i) {
118                 nodeToInfoString(children[i], infoParts);
119             }
120             if (end) {
121                 infoParts.push(end);
122             }
123             return infoParts;
124         }
125
126         // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all
127         // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around
128         // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's
129         // innerHTML whenever the user changes an input within the element.
130         function getElementChecksum(el) {
131             var info = nodeToInfoString(el).join("");
132             return crc32(info).toString(16);
133         }
134
135         function serializePosition(node, offset, rootNode) {
136             var pathParts = [], n = node;
137             rootNode = rootNode || dom.getDocument(node).documentElement;
138             while (n && n != rootNode) {
139                 pathParts.push(dom.getNodeIndex(n, true));
140                 n = n.parentNode;
141             }
142             return pathParts.join("/") + ":" + offset;
143         }
144
145         function deserializePosition(serialized, rootNode, doc) {
146             if (!rootNode) {
147                 rootNode = (doc || document).documentElement;
148             }
149             var parts = serialized.split(":");
150             var node = rootNode;
151             var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex;
152
153             while (i--) {
154                 nodeIndex = parseInt(nodeIndices[i], 10);
155                 if (nodeIndex < node.childNodes.length) {
156                     node = node.childNodes[nodeIndex];
157                 } else {
158                     throw module.createError("deserializePosition() failed: node " + dom.inspectNode(node) +
159                             " has no child with index " + nodeIndex + ", " + i);
160                 }
161             }
162
163             return new dom.DomPosition(node, parseInt(parts[1], 10));
164         }
165
166         function serializeRange(range, omitChecksum, rootNode) {
167             rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;
168             if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) {
169                 throw module.createError("serializeRange(): range " + range.inspect() +
170                     " is not wholly contained within specified root node " + dom.inspectNode(rootNode));
171             }
172             var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," +
173                 serializePosition(range.endContainer, range.endOffset, rootNode);
174             if (!omitChecksum) {
175                 serialized += "{" + getElementChecksum(rootNode) + "}";
176             }
177             return serialized;
178         }
179
180         var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/;
181
182         function deserializeRange(serialized, rootNode, doc) {
183             if (rootNode) {
184                 doc = doc || dom.getDocument(rootNode);
185             } else {
186                 doc = doc || document;
187                 rootNode = doc.documentElement;
188             }
189             var result = deserializeRegex.exec(serialized);
190             var checksum = result[4];
191             if (checksum) {
192                 var rootNodeChecksum = getElementChecksum(rootNode);
193                 if (checksum !== rootNodeChecksum) {
194                     throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
195                         ") and target root node (" + rootNodeChecksum + ") do not match");
196                 }
197             }
198             var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);
199             var range = api.createRange(doc);
200             range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
201             return range;
202         }
203
204         function canDeserializeRange(serialized, rootNode, doc) {
205             if (!rootNode) {
206                 rootNode = (doc || document).documentElement;
207             }
208             var result = deserializeRegex.exec(serialized);
209             var checksum = result[3];
210             return !checksum || checksum === getElementChecksum(rootNode);
211         }
212
213         function serializeSelection(selection, omitChecksum, rootNode) {
214             selection = api.getSelection(selection);
215             var ranges = selection.getAllRanges(), serializedRanges = [];
216             for (var i = 0, len = ranges.length; i < len; ++i) {
217                 serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode);
218             }
219             return serializedRanges.join("|");
220         }
221
222         function deserializeSelection(serialized, rootNode, win) {
223             if (rootNode) {
224                 win = win || dom.getWindow(rootNode);
225             } else {
226                 win = win || window;
227                 rootNode = win.document.documentElement;
228             }
229             var serializedRanges = serialized.split("|");
230             var sel = api.getSelection(win);
231             var ranges = [];
232
233             for (var i = 0, len = serializedRanges.length; i < len; ++i) {
234                 ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document);
235             }
236             sel.setRanges(ranges);
237
238             return sel;
239         }
240
241         function canDeserializeSelection(serialized, rootNode, win) {
242             var doc;
243             if (rootNode) {
244                 doc = win ? win.document : dom.getDocument(rootNode);
245             } else {
246                 win = win || window;
247                 rootNode = win.document.documentElement;
248             }
249             var serializedRanges = serialized.split("|");
250
251             for (var i = 0, len = serializedRanges.length; i < len; ++i) {
252                 if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) {
253                     return false;
254                 }
255             }
256
257             return true;
258         }
259
260         var cookieName = "rangySerializedSelection";
261
262         function getSerializedSelectionFromCookie(cookie) {
263             var parts = cookie.split(/[;,]/);
264             for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) {
265                 nameVal = parts[i].split("=");
266                 if (nameVal[0].replace(/^\s+/, "") == cookieName) {
267                     val = nameVal[1];
268                     if (val) {
269                         return decodeURIComponent(val.replace(/\s+$/, ""));
270                     }
271                 }
272             }
273             return null;
274         }
275
276         function restoreSelectionFromCookie(win) {
277             win = win || window;
278             var serialized = getSerializedSelectionFromCookie(win.document.cookie);
279             if (serialized) {
280                 deserializeSelection(serialized, win.doc);
281             }
282         }
283
284         function saveSelectionCookie(win, props) {
285             win = win || window;
286             props = (typeof props == "object") ? props : {};
287             var expires = props.expires ? ";expires=" + props.expires.toUTCString() : "";
288             var path = props.path ? ";path=" + props.path : "";
289             var domain = props.domain ? ";domain=" + props.domain : "";
290             var secure = props.secure ? ";secure" : "";
291             var serialized = serializeSelection(api.getSelection(win));
292             win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure;
293         }
294
295         util.extend(api, {
296             serializePosition: serializePosition,
297             deserializePosition: deserializePosition,
298             serializeRange: serializeRange,
299             deserializeRange: deserializeRange,
300             canDeserializeRange: canDeserializeRange,
301             serializeSelection: serializeSelection,
302             deserializeSelection: deserializeSelection,
303             canDeserializeSelection: canDeserializeSelection,
304             restoreSelectionFromCookie: restoreSelectionFromCookie,
305             saveSelectionCookie: saveSelectionCookie,
306             getElementChecksum: getElementChecksum,
307             nodeToInfoString: nodeToInfoString
308         });
309
310         util.crc32 = crc32;
311     });
312     
313     return rangy;
314 }, this);