Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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