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 * Handles styles in a give document.
22 */
23
24var FCKStyles = FCK.Styles =
25{
26	_Callbacks : {},
27	_ObjectStyles : {},
28
29	ApplyStyle : function( style )
30	{
31		if ( typeof style == 'string' )
32			style = this.GetStyles()[ style ] ;
33
34		if ( style )
35		{
36			if ( style.GetType() == FCK_STYLE_OBJECT )
37				style.ApplyToObject( FCKSelection.GetSelectedElement() ) ;
38			else
39				style.ApplyToSelection( FCK.EditorWindow ) ;
40
41			FCK.Events.FireEvent( 'OnSelectionChange' ) ;
42		}
43	},
44
45	RemoveStyle : function( style )
46	{
47		if ( typeof style == 'string' )
48			style = this.GetStyles()[ style ] ;
49
50		if ( style )
51		{
52			style.RemoveFromSelection( FCK.EditorWindow ) ;
53			FCK.Events.FireEvent( 'OnSelectionChange' ) ;
54		}
55	},
56
57	/**
58	 * Defines a callback function to be called when the current state of a
59	 * specific style changes.
60	 */
61	AttachStyleStateChange : function( styleName, callback, callbackOwner )
62	{
63		var callbacks = this._Callbacks[ styleName ] ;
64
65		if ( !callbacks )
66			callbacks = this._Callbacks[ styleName ] = [] ;
67
68		callbacks.push( [ callback, callbackOwner ] ) ;
69	},
70
71	CheckSelectionChanges : function()
72	{
73		var startElement = FCKSelection.GetBoundaryParentElement( true ) ;
74
75		if ( !startElement )
76			return ;
77
78		// Walks the start node parents path, checking all styles that are being listened.
79		var path = new FCKElementPath( startElement ) ;
80		var styles = this.GetStyles() ;
81
82		for ( var styleName in styles )
83		{
84			var callbacks = this._Callbacks[ styleName ] ;
85
86			if ( callbacks )
87			{
88				var style = styles[ styleName ] ;
89				var state = style.CheckActive( path ) ;
90
91				if ( style._LastState != state )
92				{
93					style._LastState = state ;
94
95					for ( var i = 0 ; i < callbacks.length ; i++ )
96					{
97						var callback = callbacks[i][0] ;
98						var callbackOwner = callbacks[i][1] ;
99
100						callback.call( callbackOwner || window, styleName, state ) ;
101					}
102				}
103			}
104		}
105	},
106
107	CheckStyleInSelection : function( styleName )
108	{
109		return false ;
110	},
111
112	_GetRemoveFormatTagsRegex : function ()
113	{
114		var regex = new RegExp( '^(?:' + FCKConfig.RemoveFormatTags.replace( /,/g,'|' ) + ')$', 'i' ) ;
115
116		return (this._GetRemoveFormatTagsRegex = function()
117		{
118			return regex ;
119		})
120		&& regex  ;
121	},
122
123	/**
124	 * Remove all styles from the current selection.
125	 * TODO:
126	 *  - This is almost a duplication of FCKStyle.RemoveFromRange. We should
127	 *    try to merge things.
128	 */
129	RemoveAll : function()
130	{
131		var range = new FCKDomRange( FCK.EditorWindow ) ;
132		range.MoveToSelection() ;
133
134		if ( range.CheckIsCollapsed() )
135			return ;
136
137			// Expand the range, if inside inline element boundaries.
138		range.Expand( 'inline_elements' ) ;
139
140		// Get the bookmark nodes.
141		// Bookmark the range so we can re-select it after processing.
142		var bookmark = range.CreateBookmark( true ) ;
143
144		// The style will be applied within the bookmark boundaries.
145		var startNode	= range.GetBookmarkNode( bookmark, true ) ;
146		var endNode		= range.GetBookmarkNode( bookmark, false ) ;
147
148		range.Release( true ) ;
149
150		var tagsRegex = this._GetRemoveFormatTagsRegex() ;
151
152		// We need to check the selection boundaries (bookmark spans) to break
153		// the code in a way that we can properly remove partially selected nodes.
154		// For example, removing a <b> style from
155		//		<b>This is [some text</b> to show <b>the] problem</b>
156		// ... where [ and ] represent the selection, must result:
157		//		<b>This is </b>[some text to show the]<b> problem</b>
158		// The strategy is simple, we just break the partial nodes before the
159		// removal logic, having something that could be represented this way:
160		//		<b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b>
161
162		// Let's start checking the start boundary.
163		var path = new FCKElementPath( startNode ) ;
164		var pathElements = path.Elements ;
165		var pathElement ;
166
167		for ( var i = 1 ; i < pathElements.length ; i++ )
168		{
169			pathElement = pathElements[i] ;
170
171			if ( pathElement == path.Block || pathElement == path.BlockLimit )
172				break ;
173
174			// If this element can be removed (even partially).
175			if ( tagsRegex.test( pathElement.nodeName ) )
176				FCKDomTools.BreakParent( startNode, pathElement, range ) ;
177		}
178
179		// Now the end boundary.
180		path = new FCKElementPath( endNode ) ;
181		pathElements = path.Elements ;
182
183		for ( var i = 1 ; i < pathElements.length ; i++ )
184		{
185			pathElement = pathElements[i] ;
186
187			if ( pathElement == path.Block || pathElement == path.BlockLimit )
188				break ;
189
190			elementName = pathElement.nodeName.toLowerCase() ;
191
192			// If this element can be removed (even partially).
193			if ( tagsRegex.test( pathElement.nodeName ) )
194				FCKDomTools.BreakParent( endNode, pathElement, range ) ;
195		}
196
197		// Navigate through all nodes between the bookmarks.
198		var currentNode = FCKDomTools.GetNextSourceNode( startNode, true, 1 ) ;
199
200		while ( currentNode )
201		{
202			// If we have reached the end of the selection, stop looping.
203			if ( currentNode == endNode )
204				break ;
205
206			// Cache the next node to be processed. Do it now, because
207			// currentNode may be removed.
208			var nextNode = FCKDomTools.GetNextSourceNode( currentNode, false, 1 ) ;
209
210			// Remove elements nodes that match with this style rules.
211			if ( tagsRegex.test( currentNode.nodeName ) )
212				FCKDomTools.RemoveNode( currentNode, true ) ;
213
214			currentNode = nextNode ;
215		}
216
217		range.SelectBookmark( bookmark ) ;
218
219		FCK.Events.FireEvent( 'OnSelectionChange' ) ;
220	},
221
222	GetStyle : function( styleName )
223	{
224		return this.GetStyles()[ styleName ] ;
225	},
226
227	GetStyles : function()
228	{
229		var styles = this._GetStyles ;
230		if ( !styles )
231		{
232			styles = this._GetStyles = FCKTools.Merge(
233				this._LoadStylesCore(),
234				this._LoadStylesCustom(),
235				this._LoadStylesXml() ) ;
236		}
237		return styles ;
238	},
239
240	CheckHasObjectStyle : function( elementName )
241	{
242		return !!this._ObjectStyles[ elementName ] ;
243	},
244
245	_LoadStylesCore : function()
246	{
247		var styles = {};
248		var styleDefs = FCKConfig.CoreStyles ;
249
250		for ( var styleName in styleDefs )
251		{
252			// Core styles are prefixed with _FCK_.
253			var style = styles[ '_FCK_' + styleName ] = new FCKStyle( styleDefs[ styleName ] ) ;
254			style.IsCore = true ;
255		}
256		return styles ;
257	},
258
259	_LoadStylesCustom : function()
260	{
261		var styles = {};
262		var styleDefs = FCKConfig.CustomStyles ;
263
264		if ( styleDefs )
265		{
266			for ( var styleName in styleDefs )
267				styles[ styleName ] = new FCKStyle( styleDefs[ styleName ] ) ;
268		}
269
270		return styles ;
271	},
272
273	_LoadStylesXml : function()
274	{
275		var styles = {};
276
277		var stylesXmlPath = FCKConfig.StylesXmlPath ;
278
279		if ( !stylesXmlPath || stylesXmlPath.length == 0 )
280			return styles ;
281
282		// Load the XML file into a FCKXml object.
283		var xml = new FCKXml() ;
284		xml.LoadUrl( stylesXmlPath ) ;
285
286		var stylesXmlObj = FCKXml.TransformToObject( xml.SelectSingleNode( 'Styles' ) ) ;
287
288		// Get the "Style" nodes defined in the XML file.
289		var styleNodes = stylesXmlObj.$Style ;
290
291		// Add each style to our "Styles" collection.
292		for ( var i = 0 ; i < styleNodes.length ; i++ )
293		{
294			var styleNode = styleNodes[i] ;
295
296			var element = ( styleNode.element || '' ).toLowerCase() ;
297
298			if ( element.length == 0 )
299				throw( 'The element name is required. Error loading "' + stylesXmlPath + '"' ) ;
300
301			var styleDef = {
302				Element : element,
303				Attributes : {},
304				Styles : {},
305				Overrides : []
306			} ;
307
308			// Get the attributes defined for the style (if any).
309			var attNodes = styleNode.$Attribute || [] ;
310
311			// Add the attributes to the style definition object.
312			for ( var j = 0 ; j < attNodes.length ; j++ )
313			{
314				styleDef.Attributes[ attNodes[j].name ] = attNodes[j].value ;
315			}
316
317			// Get the styles defined for the style (if any).
318			var cssStyleNodes = styleNode.$Style || [] ;
319
320			// Add the attributes to the style definition object.
321			for ( j = 0 ; j < cssStyleNodes.length ; j++ )
322			{
323				styleDef.Styles[ cssStyleNodes[j].name ] = cssStyleNodes[j].value ;
324			}
325
326			// Load override definitions.
327			var cssStyleOverrideNodes = styleNode.$Override ;
328			if ( cssStyleOverrideNodes )
329			{
330				for ( j = 0 ; j < cssStyleOverrideNodes.length ; j++ )
331				{
332					var overrideNode = cssStyleOverrideNodes[j] ;
333					var overrideDef =
334					{
335						Element : overrideNode.element
336					} ;
337
338					var overrideAttNode = overrideNode.$Attribute ;
339					if ( overrideAttNode )
340					{
341						overrideDef.Attributes = {} ;
342						for ( var k = 0 ; k < overrideAttNode.length ; k++ )
343						{
344							var overrideAttValue = overrideAttNode[k].value || null ;
345							if ( overrideAttValue )
346							{
347								// Check if the override attribute value is a regular expression.
348								var regexMatch = overrideAttValue && FCKRegexLib.RegExp.exec( overrideAttValue ) ;
349								if ( regexMatch )
350									overrideAttValue = new RegExp( regexMatch[1], regexMatch[2] || '' ) ;
351							}
352							overrideDef.Attributes[ overrideAttNode[k].name ] = overrideAttValue ;
353						}
354					}
355
356					styleDef.Overrides.push( overrideDef ) ;
357				}
358			}
359
360			var style = new FCKStyle( styleDef ) ;
361			style.Name = styleNode.name || element ;
362
363			if ( style.GetType() == FCK_STYLE_OBJECT )
364				this._ObjectStyles[ element ] = true ;
365
366			// Add the style to the "Styles" collection using it's name as the key.
367			styles[ style.Name ] = style ;
368		}
369
370		return styles ;
371	}
372} ;
373