Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentNode.js |
— | — | @@ -1,244 +0,0 @@ |
2 | | -/** |
3 | | - * Creates an es.DocumentNode object. |
4 | | - * |
5 | | - * @class |
6 | | - * @abstract |
7 | | - * @constructor |
8 | | - * @param {es.DocumentNode[]} nodes List of document nodes to initially add |
9 | | - */ |
10 | | -es.DocumentNode = function( nodes ) { |
11 | | - this.children = es.isArray( nodes ) ? nodes : []; |
12 | | -}; |
13 | | - |
14 | | -/* Abstract Methods */ |
15 | | - |
16 | | -/** |
17 | | - * Gets the element length. |
18 | | - * |
19 | | - * @abstract |
20 | | - * @method |
21 | | - * @returns {Integer} Length of element and it's content |
22 | | - */ |
23 | | -es.DocumentNode.prototype.getElementLength = function() { |
24 | | - throw 'DocumentNode.getElementLength not implemented in this subclass: ' + this.constructor; |
25 | | -}; |
26 | | - |
27 | | -es.DocumentNode.prototype.getContentLength = function() { |
28 | | - throw 'DocumentNode.getContentLength not implemented in this subclass: ' + this.constructor; |
29 | | -}; |
30 | | - |
31 | | -/* Methods */ |
32 | | - |
33 | | -/** |
34 | | - * Gets a list of child nodes. |
35 | | - * |
36 | | - * @abstract |
37 | | - * @method |
38 | | - * @returns {es.DocumentNode[]} List of document nodes |
39 | | - */ |
40 | | -es.DocumentNode.prototype.getChildren = function() { |
41 | | - return this.children; |
42 | | -}; |
43 | | - |
44 | | -/** |
45 | | - * Gets the range within this node that a given child node covers. |
46 | | - * |
47 | | - * @method |
48 | | - * @param {es.ModelNode} node Node to get range for |
49 | | - * @param {Boolean} [shallow] Do not iterate into child nodes of child nodes |
50 | | - * @returns {es.Range|null} Range of node or null if node was not found |
51 | | - */ |
52 | | -es.DocumentNode.prototype.getRangeFromNode = function( node, shallow ) { |
53 | | - if ( this.children.length ) { |
54 | | - for ( var i = 0, length = this.children.length, left = 0; i < length; i++ ) { |
55 | | - if ( this.children[i] === node ) { |
56 | | - return new es.Range( left, left + this.children[i].getElementLength() ); |
57 | | - } |
58 | | - if ( !shallow && this.children[i].getChildren().length ) { |
59 | | - var range = this.children[i].getRangeFromNode( node ); |
60 | | - if ( range !== null ) { |
61 | | - // Include opening of parent |
62 | | - left++; |
63 | | - return es.Range.newFromTranslatedRange( range, left ); |
64 | | - } |
65 | | - } |
66 | | - left += this.children[i].getElementLength(); |
67 | | - } |
68 | | - } |
69 | | - return null; |
70 | | -}; |
71 | | - |
72 | | -/** |
73 | | - * Gets the content offset of a node. |
74 | | - * |
75 | | - * This method is pretty expensive. If you need to get different slices of the same content, get |
76 | | - * the content first, then slice it up locally. |
77 | | - * |
78 | | - * TODO: Rewrite this method to not use recursion, because the function call overhead is expensive |
79 | | - * |
80 | | - * @method |
81 | | - * @param {es.DocumentModelNode} node Node to get offset of |
82 | | - * @param {Boolean} [shallow] Do not iterate into child nodes of child nodes |
83 | | - * @returns {Integer} Offset of node or -1 of node was not found |
84 | | - */ |
85 | | -es.DocumentNode.prototype.getOffsetFromNode = function( node, shallow ) { |
86 | | - if ( this.children.length ) { |
87 | | - var offset = 0; |
88 | | - for ( var i = 0, length = this.children.length; i < length; i++ ) { |
89 | | - if ( this.children[i] === node ) { |
90 | | - return offset; |
91 | | - } |
92 | | - if ( !shallow && this.children[i].getChildren().length ) { |
93 | | - var childOffset = this.getOffsetFromNode.call( this.children[i], node ); |
94 | | - if ( childOffset !== -1 ) { |
95 | | - return offset + 1 + childOffset; |
96 | | - } |
97 | | - } |
98 | | - offset += this.children[i].getElementLength(); |
99 | | - } |
100 | | - } |
101 | | - return -1; |
102 | | -}; |
103 | | - |
104 | | -/** |
105 | | - * Gets the node at a given offset. |
106 | | - * |
107 | | - * This method is pretty expensive. If you need to get different slices of the same content, get |
108 | | - * the content first, then slice it up locally. |
109 | | - * |
110 | | - * TODO: Rewrite this method to not use recursion, because the function call overhead is expensive |
111 | | - * |
112 | | - * @method |
113 | | - * @param {Integer} offset Offset get node for |
114 | | - * @param {Boolean} [shallow] Do not iterate into child nodes of child nodes |
115 | | - * @returns {es.DocumentModelNode|null} Node at offset, or null if non was found |
116 | | - */ |
117 | | -es.DocumentNode.prototype.getNodeFromOffset = function( offset, shallow ) { |
118 | | - if ( this.children.length ) { |
119 | | - var nodeOffset = 0, |
120 | | - nodeLength; |
121 | | - for ( var i = 0, length = this.children.length; i < length; i++ ) { |
122 | | - nodeLength = this.children[i].getElementLength(); |
123 | | - if ( offset >= nodeOffset && offset < nodeOffset + nodeLength ) { |
124 | | - if ( !shallow && this.children[i].getChildren().length ) { |
125 | | - return this.getNodeFromOffset.call( this.children[i], offset - nodeOffset - 1 ); |
126 | | - } else { |
127 | | - return this.children[i]; |
128 | | - } |
129 | | - } |
130 | | - nodeOffset += nodeLength; |
131 | | - } |
132 | | - } |
133 | | - return null; |
134 | | -}; |
135 | | - |
136 | | -/** |
137 | | - * Gets a list of nodes and their sub-ranges which are covered by a given range. |
138 | | - * |
139 | | - * @method |
140 | | - * @param {es.Range} range Range to select nodes within |
141 | | - * @param {Boolean} [shallow] Do not recurse into child nodes of child nodes |
142 | | - * @returns {Array} List of objects with 'node' and 'range' properties describing nodes which are |
143 | | - * covered by the range and the range within the node that is covered |
144 | | - */ |
145 | | -es.DocumentNode.prototype.selectNodes = function( range, shallow ) { |
146 | | - if ( typeof range === 'undefined' ) { |
147 | | - range = new es.Range( 0, this.model.getContentLength() ); |
148 | | - } else { |
149 | | - range.normalize(); |
150 | | - } |
151 | | - var nodes = [], |
152 | | - i, |
153 | | - left, |
154 | | - right, |
155 | | - start = range.start, |
156 | | - end = range.end, |
157 | | - startInside, |
158 | | - endInside; |
159 | | - |
160 | | - if ( start < 0 ) { |
161 | | - throw 'The start offset of the range is negative'; |
162 | | - } |
163 | | - |
164 | | - if ( this.children.length === 0 ) { |
165 | | - // Special case: this node doesn't have any children |
166 | | - // The return value is simply the range itself, if it is not out of bounds |
167 | | - if ( end > this.getContentLength() ) { |
168 | | - throw 'The end offset of the range is past the end of the node'; |
169 | | - } |
170 | | - return [{ 'node': this, 'range': new es.Range( start, end ) }]; |
171 | | - } |
172 | | - |
173 | | - // This node has children, loop over them |
174 | | - left = 1; // First offset inside the first child. Offset 0 is before the first child |
175 | | - for ( i = 0; i < this.children.length; i++ ) { |
176 | | - // left <= any offset inside this.children[i] <= right |
177 | | - right = left + this.children[i].getContentLength(); |
178 | | - |
179 | | - if ( start == end && ( start == left - 1 || start == right + 1 ) ) { |
180 | | - // Empty range outside of any node |
181 | | - return []; |
182 | | - } |
183 | | - if ( start == left - 1 && end == right + 1 ) { |
184 | | - // The range covers the entire node, including its opening and closing elements |
185 | | - return [ { 'node': this.children[i] } ]; |
186 | | - } |
187 | | - if ( start == left - 1 ) { |
188 | | - // start is between this.children[i-1] and this.children[i], move it to left for convenience |
189 | | - // We don't need to check for start < end here because we already have start != end and |
190 | | - // start <= end |
191 | | - start = left; |
192 | | - } |
193 | | - if ( end == right + 1 ) { |
194 | | - // end is between this.children[i] and this.children[i+1], move it to right for convenience |
195 | | - // We don't need to check for start < end here because we already have start != end and |
196 | | - // start <= end |
197 | | - end = right; |
198 | | - } |
199 | | - |
200 | | - startInside = start >= left && start <= right; // is the start inside this.children[i]? |
201 | | - endInside = end >= left && end <= right; // is the end inside this.children[i]? |
202 | | - |
203 | | - if ( startInside && endInside ) { |
204 | | - // The range is entirely inside this.children[i] |
205 | | - if ( shallow ) { |
206 | | - nodes = [{ 'node': this.children[i], 'range': new es.Range( start - left, end - left ) }]; |
207 | | - } else { |
208 | | - // Recurse into this.children[i] |
209 | | - nodes = this.children[i].selectNodes( new es.Range( start - left, end - left ) ); |
210 | | - } |
211 | | - // Since the start and end are both inside this.children[i], we know for sure that we're done, so |
212 | | - // return |
213 | | - return nodes; |
214 | | - } else if ( startInside ) { |
215 | | - // The start is inside this.children[i] but the end isn't |
216 | | - // Add a range from the start of the range to the end of this.children[i] |
217 | | - nodes.push( { 'node': this.children[i], 'range': new es.Range( start - left, right - left ) } ); |
218 | | - } else if ( endInside ) { |
219 | | - // The end is inside this.children[i] but the start isn't |
220 | | - // Add a range from the start of this.children[i] to the end of the range |
221 | | - nodes.push( { 'node': this.children[i], 'range': new es.Range( 0, end - left ) } ); |
222 | | - // We've found the end, so we're done |
223 | | - return nodes; |
224 | | - } else if ( nodes.length > 0 ) { |
225 | | - // Neither the start nor the end is inside this.children[i], but nodes is non-empty, |
226 | | - // so this.children[i] must be between the start and the end |
227 | | - // Add the entire node, so no range property |
228 | | - nodes.push( { 'node': this.children[i] } ); |
229 | | - } |
230 | | - |
231 | | - // Move left to the start of this.children[i+1] for the next iteration |
232 | | - // +2 because we need to jump over the offset between this.children[i] and this.children[i+1] |
233 | | - left = right + 2; |
234 | | - } |
235 | | - |
236 | | - // If we got here, that means that at least some part of the range is out of bounds |
237 | | - // This is an error |
238 | | - if ( nodes.length === 0 ) { |
239 | | - throw 'The start offset of the range is past the end of the node'; |
240 | | - } else { |
241 | | - // Apparently the start was inside this node, but the end wasn't |
242 | | - throw 'The end offset of the range is past the end of the node'; |
243 | | - } |
244 | | - return nodes; |
245 | | -}; |