source: gs3-extensions/seaweed-debug/trunk/src/ContainerNormalization.js@ 25160

Last change on this file since 25160 was 25160, checked in by sjm84, 12 years ago

Initial cut at a version of seaweed for debugging purposes. Check it out live into the web/ext folder

File size: 17.3 KB
Line 
1/*
2 * file: ContainerNormalization.js
3 *
4 * @BEGINLICENSE
5 * Copyright 2010 Brook Novak (email : [email protected])
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 * @ENDLICENSE
18 */
19bootstrap.provides("ContainerNormalization");
20
21/*
22 * TODO: MAKE HTML VALID BY PLACING INLINE ANCESTORS WITHIN THE BLOCK-LEVEL CONTAINERS
23 * Inlines cannot contain blocks
24 *
25 *
26 * ARGGG... OK JUST RUBBISH THIS.. For all invalid inlines, remove them from the document.. that will naturally
27 * "normalize" the range. Cannot just move inlines into containers because containers may contain block decendants.
28 *
29 * This will simplify the code a lot and keep the HTML tidy.
30 *
31 * Should design algrorithms to assume valid HTML, ad if invalid then loss of formatting as a result of throwing away
32 * invalid tags doesnt matter.
33 */
34
35/**
36 * Creates containers, adjusts structure within the given range, such that
37 * it garauntees that all block-level elements in the range do not share any ancestor inline elements
38 * that occur between the containers and the common-block-level-ancestor,
39 * with other block-level elements. This property is useful for itemizing container in
40 * a given range.
41 * <br>
42 * This operation is undoable, and is intended for indentation and itemizing ranges.
43 *
44 * @param {Node} startNode The starting dom node of the range to normalize.
45 *
46 * @param {Node} endNode The ending dom node of the range to normalize.
47 * Can be the same as start node or it must occur
48 * after the start dom (in-order left-to-right traversal)
49 *
50 * @param {Node} containerTemplate (Optional) An element which supports inline elements to be
51 * used for inline-groups which need containers. Defaults to
52 * paragraph.
53 *
54 * @return {[Node]} An array containing a list of all the top-level containers in the given range,
55 * in order if traversing the dom tree in-order. Can be empty.
56 *
57 */
58function _getNormalizedContainerRange (startNode, endNode, containerTemplate) {
59
60 var template = containerTemplate || $createElement("p");
61
62 // Determine the master container for the normalization range
63 var masterContainer = _findAncestor(_getCommonAncestor(startNode, endNode, true),
64 docBody,
65 _isBlockLevel,
66 true)
67 || docBody;
68
69 // Deepen start/end range
70 while (startNode.firstChild) {
71 startNode = startNode.firstChild;
72 }
73 while (endNode.lastChild) {
74 endNode = endNode.lastChild;
75 }
76
77 // Check if the master container allows the container template
78 if (masterContainer != docBody && !_isValidRelationship(template, masterContainer)) {
79
80 // Special case - list items
81 if (_nodeName(masterContainer) == "ul" || _nodeName(masterContainer) == "ol") {
82 startNode = _findAncestor(startNode, masterContainer);
83 endNode = _findAncestor(endNode, masterContainer);
84 var containers = [];
85 while (startNode) {
86 if (_nodeName(startNode) == "li")
87 containers.push(startNode);
88 startNode = startNode == endNode ? null : startNode.nextSibling;
89 }
90 return containers;
91
92 }
93
94 // If the master container doesn't allow sub-containers, then the normalized range
95 // becomes a single container... the master container.
96 return [masterContainer]
97 }
98
99 // Extend range to point to begin and start at top-level sub-containers if they exist,
100 // or to the end of inline groups
101 var extendedRangeNode,
102 protectedContainers = [],
103 reinsertProcContainers = [],
104 extendedStart,
105 extendedEnd;
106
107 // Extend start range
108 _visitAllNodes(masterContainer, startNode, false, extendRange);
109
110 // Remove any protect containers
111 if (protectedContainers.length > 0) {
112 for (var i in protectedContainers) {
113 if (protectedContainers[i].parentNode) {
114 protectedContainers[i].parentNode.removeChild(protectedContainers[i]);
115 reinsertProcContainers.push(protectedContainers[i]);
116 }
117 }
118
119 }
120
121 extendedStart = extendedRangeNode;
122 if (!extendedStart) {
123 // Set start through to end of initial inline group
124 extendedStart = masterContainer.firstChild;
125 while (extendedStart.firstChild) {
126 extendedStart = extendedStart.firstChild;
127 }
128 }
129
130 // Extend end range
131 extendedRangeNode = null;
132 protectedContainers = [];
133 _visitAllNodes(masterContainer, endNode, true, extendRange);
134
135 // Remove any protect containers
136 if (protectedContainers.length > 0) {
137 for (var i in protectedContainers) {
138 if (protectedContainers[i].parentNode) {
139 protectedContainers[i].parentNode.removeChild(protectedContainers[i]);
140 reinsertProcContainers.push(protectedContainers[i]);
141 }
142 }
143 }
144
145 extendedEnd = extendedRangeNode;
146 if (!extendedEnd) {
147 // Set start through to end of post inline group
148 extendedEnd = masterContainer.lastChild;
149 while (extendedEnd.lastChild) {
150 extendedEnd = extendedEnd.lastChild;
151 }
152 }
153
154 // Move protected nodes out of the extended range if there are any
155 for (var i in reinsertProcContainers) {
156 docBody.appendChild(reinsertProcContainers[i]);
157 }
158
159 // The range only containers protected nodes
160 if (!extendedEnd || ! extendedStart)
161 return [];
162
163 // Perform normalization operations (two phases)
164 separateBlockPaths(extendedStart, extendedEnd, masterContainer);
165 encapsulateIGroups(extendedStart, extendedEnd, masterContainer, template);
166
167 // Get top-level containers within the original range
168 var tdc = _findAncestor(startNode, masterContainer);
169 endNode = _findAncestor(endNode, masterContainer);
170
171 var range = [];
172
173 do {
174 var node = tdc;
175 while (node) {
176 if (_isBlockLevel(node)) {
177 range.push(node);
178 break;
179 }
180 node = node.firstChild;
181 }
182 tdc = tdc == endNode ? null : tdc.nextSibling;
183 } while (tdc);
184
185 return range;
186
187 /**
188 * Helper function for extending the range backward/forward.
189 * Sets the extendedRangeNode local iff a top-level block container is found within the master container
190 *
191 * @param {Node} domNode Provided by de.visit function
192 */
193 function extendRange(domNode) {
194 if (domNode == masterContainer)
195 return;
196
197 // Find top-level block element from the master-container - inclusive of self
198 var node = domNode, blNode = null;
199 while (node != masterContainer) {
200 if (_isBlockLevel(node))
201 blNode = node;
202
203 // If this sub-tree is protected then note the protected node root
204 if (node.parentNode == masterContainer && de.doc.getProtectedNodeContainer(node) == node) {
205 protectedContainers.push(node);
206 blNode = null; // Ignore this since this subtree will be repositioned in the document
207 }
208
209 node = node.parentNode;
210 }
211
212 if (blNode) {
213 // If this node has a top-level block element from the master-container
214 // then the extended start has been located
215 extendedRangeNode = blNode;
216 return false;
217 }
218 }
219
220
221 /**
222 * Ensures that all first occuring block-level elements from the master container own their own path
223 * up to the master container.
224 *
225 * @param {Node} startNode The start node in the range. This must not have a block-level element
226 * in the path from itself up to the master container (exclusive).
227 *
228 * @param {Node} endNode The end node in the range.
229 *
230 * @param {Node} masterContainer The master container for all sub containers
231 *
232 */
233 function separateBlockPaths(startNode, endNode, masterContainer) {
234
235 var scanPoint = startNode,
236 seenEndPoint = false;
237
238 // Scan through range for all top-level block elements from the master container
239 while (scanPoint && !seenEndPoint) {
240
241 _visitAllNodes(masterContainer, scanPoint, true, function(domNode) {
242
243 scanPoint = null;
244 seenEndPoint |= domNode == endNode;
245
246 if (domNode == masterContainer)
247 return;
248
249 // If this dom node is a block element then ensure that it owns its own path
250 // up to the master container.
251 if (_isBlockLevel(domNode)) {
252
253 // Note: this block level element is top-level from the master container... i.e. there is no
254 // block-level elements between this node up to the master container
255
256 var pivotNode = domNode;
257 while (pivotNode.parentNode != masterContainer) {
258
259 // Separate previous siblings into duplicate inline node
260 if (pivotNode.previousSibling) {
261
262 // Clone shared inline element
263 var clone = pivotNode.parentNode.cloneNode(false);
264
265 // Migrate previous siblings into duped inline element
266 while (pivotNode.previousSibling) {
267
268 var psib = pivotNode.previousSibling;
269
270 _execOp(_Operation.REMOVE_NODE, psib);
271 _execOp(_Operation.INSERT_NODE, psib, clone, 0);
272
273 }
274
275 // Insert the cloned inline element back into the document
276 _execOp(_Operation.INSERT_NODE, clone, pivotNode.parentNode.parentNode, _indexInParent(pivotNode.parentNode));
277
278 }
279
280 // Separate next siblings into duplicate inline node
281 if (pivotNode.nextSibling) {
282
283 // Clone shared inline element
284 var clone = pivotNode.parentNode.cloneNode(false)
285
286 // Migrate next siblings into duped inline element
287 while (pivotNode.nextSibling) {
288 var nsib = pivotNode.nextSibling;
289 _execOp(_Operation.REMOVE_NODE, nsib);
290 _execOp(_Operation.INSERT_NODE, nsib, clone);
291 }
292
293 // Insert the cloned inline element back into the document
294 _execOp(_Operation.INSERT_NODE, clone, pivotNode.parentNode.parentNode, _indexInParent(pivotNode.parentNode) + 1);
295
296 }
297
298 pivotNode = pivotNode.parentNode;
299
300 } // End separating path to master container
301
302 // This dom node now owns its own path to the master container.
303 // Setup to scan down next path
304 scanPoint = pivotNode.nextSibling; // NB: Pivot is an immediate child of master container
305
306 // Check if the end node lies within this block-level element
307 _visitAllNodes(domNode, domNode, true, function(subDomNode) {
308 if (subDomNode == endNode)
309 seenEndPoint = true;
310 return !seenEndPoint;
311 });
312
313 return false;
314 }
315
316 return !seenEndPoint;
317
318 });
319
320 } // End Loop: separating paths to block-level elements in given range
321
322 } // End function separateBlockPaths
323
324
325 /**
326 * For all sub-trees within the given range of the master container (i.e. Immediate children of the master continer),
327 * all sub-trees containing only inline elements/text nodes are migrated into a containerTemplate clone
328 * within the master container.
329 *
330 *
331 * @param {Node} startNode The start node to encapsulate from. This should not be within an inline sub-tree
332 * which has a previous sub-tree which contains only inline elements.
333 *
334 * @param {Node} endNode The end node to encapsulate to. This should not be within an inline sub-tree
335 * which has a following sub-tree which contains only inline elements.
336 *
337 * @param {Node} masterContainer The master container
338 *
339 * @param {Node} containerTemplate The container to clone for migrating inline groups into
340 *
341 */
342 function encapsulateIGroups(startNode, endNode, masterContainer, containerTemplate) {
343
344 // Adjust start and end nodes to their root nodes within the master container
345 startNode = _findAncestor(startNode, masterContainer);
346 endNode = _findAncestor(endNode, masterContainer);
347
348 var subTreeNode = startNode,
349 inlineGroupStart = null;
350
351 // Visit all sub-trees in the master container within the range
352 // (i.e. immediate children in the master contianer).
353 while (subTreeNode) {
354
355 var domNode = subTreeNode,
356 containsBlock = false;
357
358 while (domNode) {
359 if (_isBlockLevel(domNode)) {
360 containsBlock = true;
361 break;
362 }
363 // Just check down left-most-path since all block level elements in
364 // the given range have their own path to the master container
365 domNode = domNode.firstChild;
366 }
367
368 if (containsBlock) {
369
370 if (inlineGroupStart) {
371 encapsulate(inlineGroupStart, subTreeNode);
372 inlineGroupStart = null;
373 }
374
375 } else {
376 // Mark start of inline group - avoid including whitespace nodes which should not be encapsulated.
377 // For example, whitespace in between list items
378 if (!inlineGroupStart) {
379 if (!(subTreeNode.nodeType == Node.TEXT_NODE && !_doesTextSupportNonWS(subTreeNode)))
380 inlineGroupStart = subTreeNode;
381 }
382 }
383
384 subTreeNode = subTreeNode == endNode ? null : subTreeNode.nextSibling;
385 }
386
387 if (inlineGroupStart)
388 encapsulate(inlineGroupStart, null);
389
390 /**
391 * Encapsulates a run of inline siblings within the master container.
392 *
393 * @param {Node} start The sibling to start encapsulating from.
394 *
395 * @param {Node} endEx The exclusive end sibling.
396 * Can be null for encapsulating all siblings from start node.
397 */
398 function encapsulate(start, endEx) {
399
400 var igContainer = containerTemplate.cloneNode(false),
401 igSubTree = start;
402
403 // Insert the new inline-group container into the master container
404 debug.assert(_isValidRelationship(igContainer, masterContainer));
405 _execOp(_Operation.INSERT_NODE, igContainer, masterContainer, _indexInParent(start));
406
407 // Migrate inline run into the new container
408 while (igSubTree != endEx) {
409 var nextSib = igSubTree.nextSibling;
410 _execOp(_Operation.REMOVE_NODE, igSubTree);
411 _execOp(_Operation.INSERT_NODE, igSubTree, igContainer);
412 igSubTree = nextSib;
413 }
414 }
415
416 } // End function encapsulateIGroups
417
418
419} // End getNormalizedContainerRange
420
421
422
423
424
Note: See TracBrowser for help on using the repository browser.