1/*
2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
4 *
5 * == BEGIN LICENSE ==
6 *
7 * Licensed under the terms of any of the following licenses at your
8 * choice:
9 *
10 *  - GNU General Public License Version 2 or later (the "GPL")
11 *    http://www.gnu.org/licenses/gpl.html
12 *
13 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14 *    http://www.gnu.org/licenses/lgpl.html
15 *
16 *  - Mozilla Public License Version 1.1 or later (the "MPL")
17 *    http://www.mozilla.org/MPL/MPL-1.1.html
18 *
19 * == END LICENSE ==
20 *
21 * Utility functions to work with the DOM.
22 */
23
24var FCKDomTools =
25{
26	MoveChildren : function( source, target )
27	{
28		if ( source == target )
29			return ;
30
31		var eChild ;
32		while ( (eChild = source.firstChild) )
33			target.appendChild( source.removeChild( eChild ) ) ;
34	},
35
36	// Remove blank spaces from the beginning and the end of the contents of a node.
37	TrimNode : function( node, ignoreEndBRs )
38	{
39		this.LTrimNode( node ) ;
40		this.RTrimNode( node, ignoreEndBRs ) ;
41	},
42
43	LTrimNode : function( node )
44	{
45		var eChildNode ;
46
47		while ( (eChildNode = node.firstChild) )
48		{
49			if ( eChildNode.nodeType == 3 )
50			{
51				var sTrimmed = eChildNode.nodeValue.LTrim() ;
52				var iOriginalLength = eChildNode.nodeValue.length ;
53
54				if ( sTrimmed.length == 0 )
55				{
56					node.removeChild( eChildNode ) ;
57					continue ;
58				}
59				else if ( sTrimmed.length < iOriginalLength )
60				{
61					eChildNode.splitText( iOriginalLength - sTrimmed.length ) ;
62					node.removeChild( node.firstChild ) ;
63				}
64			}
65			break ;
66		}
67	},
68
69	RTrimNode : function( node, ignoreEndBRs )
70	{
71		var eChildNode ;
72
73		while ( (eChildNode = node.lastChild) )
74		{
75			switch ( eChildNode.nodeType )
76			{
77				case 1 :
78					if ( eChildNode.nodeName.toUpperCase() == 'BR' && ( ignoreEndBRs || eChildNode.getAttribute( 'type', 2 ) == '_moz' ) )
79					{
80						node.removeChild( eChildNode ) ;
81						continue ;
82					}
83					break ;
84
85				case 3 :
86					var sTrimmed = eChildNode.nodeValue.RTrim() ;
87					var iOriginalLength = eChildNode.nodeValue.length ;
88
89					if ( sTrimmed.length == 0 )
90					{
91						// If the trimmed text node is empty, just remove it.
92
93						// Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#81).
94						eChildNode.parentNode.removeChild( eChildNode ) ;
95						continue ;
96					}
97					else if ( sTrimmed.length < iOriginalLength )
98					{
99						// If the trimmed text lenght is less than the original
100						// lenght, strip all spaces from the end by splitting
101						// the text and removing the resulting useless node.
102
103						eChildNode.splitText( sTrimmed.length ) ;
104						// Use "node.lastChild.parentNode" instead of "node" to avoid IE bug (#81).
105						node.lastChild.parentNode.removeChild( node.lastChild ) ;
106					}
107			}
108			break ;
109		}
110	},
111
112	RemoveNode : function( node, excludeChildren )
113	{
114		if ( excludeChildren )
115		{
116			// Move all children before the node.
117			var eChild ;
118			while ( (eChild = node.firstChild) )
119				node.parentNode.insertBefore( node.removeChild( eChild ), node ) ;
120		}
121
122		return node.parentNode.removeChild( node ) ;
123	},
124
125	GetFirstChild : function( node, childNames )
126	{
127		// If childNames is a string, transform it in a Array.
128		if ( typeof ( childNames ) == 'string' )
129			childNames = [ childNames ] ;
130
131		var eChild = node.firstChild ;
132		while( eChild )
133		{
134			if ( eChild.nodeType == 1 && eChild.tagName.Equals.apply( eChild.tagName, childNames ) )
135				return eChild ;
136
137			eChild = eChild.nextSibling ;
138		}
139
140		return null ;
141	},
142
143	GetLastChild : function( node, childNames )
144	{
145		// If childNames is a string, transform it in a Array.
146		if ( typeof ( childNames ) == 'string' )
147			childNames = [ childNames ] ;
148
149		var eChild = node.lastChild ;
150		while( eChild )
151		{
152			if ( eChild.nodeType == 1 && ( !childNames || eChild.tagName.Equals( childNames ) ) )
153				return eChild ;
154
155			eChild = eChild.previousSibling ;
156		}
157
158		return null ;
159	},
160
161	/*
162	 * Gets the previous element (nodeType=1) in the source order. Returns
163	 * "null" If no element is found.
164	 *		@param {Object} currentNode The node to start searching from.
165	 *		@param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be
166	 *				handled. If set to "true", only white spaces text nodes
167	 *				will be ignored, while non white space text nodes will stop
168	 *				the search, returning null. If "false" or ommitted, all
169	 *				text nodes are ignored.
170	 *		@param {string[]} stopSearchElements An array of element names that
171	 *				will cause the search to stop when found, returning null.
172	 *				May be ommitted (or null).
173	 *		@param {string[]} ignoreElements An array of element names that
174	 *				must be ignored during the search.
175	 */
176	GetPreviousSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements )
177	{
178		if ( !currentNode )
179			return null ;
180
181		if ( stopSearchElements && currentNode.nodeType == 1 && currentNode.nodeName.IEquals( stopSearchElements ) )
182			return null ;
183
184		if ( currentNode.previousSibling )
185			currentNode = currentNode.previousSibling ;
186		else
187			return this.GetPreviousSourceElement( currentNode.parentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
188
189		while ( currentNode )
190		{
191			if ( currentNode.nodeType == 1 )
192			{
193				if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) )
194					break ;
195
196				if ( !ignoreElements || !currentNode.nodeName.IEquals( ignoreElements ) )
197					return currentNode ;
198			}
199			else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 )
200				break ;
201
202			if ( currentNode.lastChild )
203				currentNode = currentNode.lastChild ;
204			else
205				return this.GetPreviousSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
206		}
207
208		return null ;
209	},
210
211	/*
212	 * Gets the next element (nodeType=1) in the source order. Returns
213	 * "null" If no element is found.
214	 *		@param {Object} currentNode The node to start searching from.
215	 *		@param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be
216	 *				handled. If set to "true", only white spaces text nodes
217	 *				will be ignored, while non white space text nodes will stop
218	 *				the search, returning null. If "false" or ommitted, all
219	 *				text nodes are ignored.
220	 *		@param {string[]} stopSearchElements An array of element names that
221	 *				will cause the search to stop when found, returning null.
222	 *				May be ommitted (or null).
223	 *		@param {string[]} ignoreElements An array of element names that
224	 *				must be ignored during the search.
225	 */
226	GetNextSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements )
227	{
228		if ( !currentNode )
229			return null ;
230
231		if ( currentNode.nextSibling )
232			currentNode = currentNode.nextSibling ;
233		else
234			return this.GetNextSourceElement( currentNode.parentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
235
236		while ( currentNode )
237		{
238			if ( currentNode.nodeType == 1 )
239			{
240				if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) )
241					break ;
242
243				if ( !ignoreElements || !currentNode.nodeName.IEquals( ignoreElements ) )
244					return currentNode ;
245			}
246			else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 )
247				break ;
248
249			if ( currentNode.firstChild )
250				currentNode = currentNode.firstChild ;
251			else
252				return this.GetNextSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
253		}
254
255		return null ;
256	},
257
258	// Inserts a element after a existing one.
259	InsertAfterNode : function( existingNode, newNode )
260	{
261		return existingNode.parentNode.insertBefore( newNode, existingNode.nextSibling ) ;
262	},
263
264	GetParents : function( node )
265	{
266		var parents = new Array() ;
267
268		while ( node )
269		{
270			parents.splice( 0, 0, node ) ;
271			node = node.parentNode ;
272		}
273
274		return parents ;
275	},
276
277	GetIndexOf : function( node )
278	{
279		var currentNode = node.parentNode ? node.parentNode.firstChild : null ;
280		var currentIndex = -1 ;
281
282		while ( currentNode )
283		{
284			currentIndex++ ;
285
286			if ( currentNode == node )
287				return currentIndex ;
288
289			currentNode = currentNode.nextSibling ;
290		}
291
292		return -1 ;
293	}
294} ;