fckstyle.js
上传用户:dbstep
上传日期:2022-08-06
资源大小:2803k
文件大小:46k
源码类别:

WEB源码(ASP,PHP,...)

开发平台:

ASP/ASPX

  1. /*
  2.  * FCKeditor - The text editor for Internet - http://www.fckeditor.net
  3.  * Copyright (C) 2003-2009 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.  * FCKStyle Class: contains a style definition, and all methods to work with
  22.  * the style in a document.
  23.  */
  24. /**
  25.  * @param {Object} styleDesc A "style descriptor" object, containing the raw
  26.  * style definition in the following format:
  27.  * '<style name>' : {
  28.  * Element : '<element name>',
  29.  * Attributes : {
  30.  * '<att name>' : '<att value>',
  31.  * ...
  32.  * },
  33.  * Styles : {
  34.  * '<style name>' : '<style value>',
  35.  * ...
  36.  * },
  37.  * Overrides : '<element name>'|{
  38.  * Element : '<element name>',
  39.  * Attributes : {
  40.  * '<att name>' : '<att value>'|/<att regex>/
  41.  * },
  42.  * Styles : {
  43.  * '<style name>' : '<style value>'|/<style regex>/
  44.  * },
  45.  * }
  46.  * }
  47.  */
  48. var FCKStyle = function( styleDesc )
  49. {
  50. this.Element = ( styleDesc.Element || 'span' ).toLowerCase() ;
  51. this._StyleDesc = styleDesc ;
  52. }
  53. FCKStyle.prototype =
  54. {
  55. /**
  56.  * Get the style type, based on its element name:
  57.  * - FCK_STYLE_BLOCK  (0): Block Style
  58.  * - FCK_STYLE_INLINE (1): Inline Style
  59.  * - FCK_STYLE_OBJECT (2): Object Style
  60.  */
  61. GetType : function()
  62. {
  63. var type = this.GetType_$ ;
  64. if ( type != undefined )
  65. return type ;
  66. var elementName = this.Element ;
  67. if ( elementName == '#' || FCKListsLib.StyleBlockElements[ elementName ] )
  68. type = FCK_STYLE_BLOCK ;
  69. else if ( FCKListsLib.StyleObjectElements[ elementName ] )
  70. type = FCK_STYLE_OBJECT ;
  71. else
  72. type = FCK_STYLE_INLINE ;
  73. return ( this.GetType_$ = type ) ;
  74. },
  75. /**
  76.  * Apply the style to the current selection.
  77.  */
  78. ApplyToSelection : function( targetWindow )
  79. {
  80. // Create a range for the current selection.
  81. var range = new FCKDomRange( targetWindow ) ;
  82. range.MoveToSelection() ;
  83. this.ApplyToRange( range, true ) ;
  84. },
  85. /**
  86.  * Apply the style to a FCKDomRange.
  87.  */
  88. ApplyToRange : function( range, selectIt, updateRange )
  89. {
  90. // ApplyToRange is not valid for FCK_STYLE_OBJECT types.
  91. // Use ApplyToObject instead.
  92. switch ( this.GetType() )
  93. {
  94. case FCK_STYLE_BLOCK :
  95. this.ApplyToRange = this._ApplyBlockStyle ;
  96. break ;
  97. case FCK_STYLE_INLINE :
  98. this.ApplyToRange = this._ApplyInlineStyle ;
  99. break ;
  100. default :
  101. return ;
  102. }
  103. this.ApplyToRange( range, selectIt, updateRange ) ;
  104. },
  105. /**
  106.  * Apply the style to an object. Valid for FCK_STYLE_BLOCK types only.
  107.  */
  108. ApplyToObject : function( objectElement )
  109. {
  110. if ( !objectElement )
  111. return ;
  112. this.BuildElement( null, objectElement ) ;
  113. },
  114. /**
  115.  * Remove the style from the current selection.
  116.  */
  117. RemoveFromSelection : function( targetWindow )
  118. {
  119. // Create a range for the current selection.
  120. var range = new FCKDomRange( targetWindow ) ;
  121. range.MoveToSelection() ;
  122. this.RemoveFromRange( range, true ) ;
  123. },
  124. /**
  125.  * Remove the style from a FCKDomRange. Block type styles will have no
  126.  * effect.
  127.  */
  128. RemoveFromRange : function( range, selectIt, updateRange )
  129. {
  130. var bookmark ;
  131. // Create the attribute list to be used later for element comparisons.
  132. var styleAttribs = this._GetAttribsForComparison() ;
  133. var styleOverrides = this._GetOverridesForComparison() ;
  134. // If collapsed, we are removing all conflicting styles from the range
  135. // parent tree.
  136. if ( range.CheckIsCollapsed() )
  137. {
  138. // Bookmark the range so we can re-select it after processing.
  139. var bookmark = range.CreateBookmark( true ) ;
  140. // Let's start from the bookmark <span> parent.
  141. var bookmarkStart = range.GetBookmarkNode( bookmark, true ) ;
  142. var path = new FCKElementPath( bookmarkStart.parentNode ) ;
  143. // While looping through the path, we'll be saving references to
  144. // parent elements if the range is in one of their boundaries. In
  145. // this way, we are able to create a copy of those elements when
  146. // removing a style if the range is in a boundary limit (see #1270).
  147. var boundaryElements = [] ;
  148. // Check if the range is in the boundary limits of an element
  149. // (related to #1270).
  150. var isBoundaryRight = !FCKDomTools.GetNextSibling( bookmarkStart ) ;
  151. var isBoundary = isBoundaryRight || !FCKDomTools.GetPreviousSibling( bookmarkStart ) ;
  152. // This is the last element to be removed in the boundary situation
  153. // described at #1270.
  154. var lastBoundaryElement ;
  155. var boundaryLimitIndex = -1 ;
  156. for ( var i = 0 ; i < path.Elements.length ; i++ )
  157. {
  158. var pathElement = path.Elements[i] ;
  159. if ( this.CheckElementRemovable( pathElement ) )
  160. {
  161. if ( isBoundary
  162. && !FCKDomTools.CheckIsEmptyElement( pathElement,
  163. function( el )
  164. {
  165. return ( el != bookmarkStart ) ;
  166. } )
  167. )
  168. {
  169. lastBoundaryElement = pathElement ;
  170. // We'll be continuously including elements in the
  171. // boundaryElements array, but only those added before
  172. // setting lastBoundaryElement must be used later, so
  173. // let's mark the current index here.
  174. boundaryLimitIndex = boundaryElements.length - 1 ;
  175. }
  176. else
  177. {
  178. var pathElementName = pathElement.nodeName.toLowerCase() ;
  179. if ( pathElementName == this.Element )
  180. {
  181. // Remove any attribute that conflict with this style, no
  182. // matter their values.
  183. for ( var att in styleAttribs )
  184. {
  185. if ( FCKDomTools.HasAttribute( pathElement, att ) )
  186. {
  187. switch ( att )
  188. {
  189. case 'style' :
  190. this._RemoveStylesFromElement( pathElement ) ;
  191. break ;
  192. case 'class' :
  193. // The 'class' element value must match (#1318).
  194. if ( FCKDomTools.GetAttributeValue( pathElement, att ) != this.GetFinalAttributeValue( att ) )
  195. continue ;
  196. /*jsl:fallthru*/
  197. default :
  198. FCKDomTools.RemoveAttribute( pathElement, att ) ;
  199. }
  200. }
  201. }
  202. }
  203. // Remove overrides defined to the same element name.
  204. this._RemoveOverrides( pathElement, styleOverrides[ pathElementName ] ) ;
  205. // Remove the element if no more attributes are available and it's an inline style element
  206. if ( this.GetType() == FCK_STYLE_INLINE)
  207. this._RemoveNoAttribElement( pathElement ) ;
  208. }
  209. }
  210. else if ( isBoundary )
  211. boundaryElements.push( pathElement ) ;
  212. // Check if we are still in a boundary (at the same side).
  213. isBoundary = isBoundary && ( ( isBoundaryRight && !FCKDomTools.GetNextSibling( pathElement ) ) || ( !isBoundaryRight && !FCKDomTools.GetPreviousSibling( pathElement ) ) ) ;
  214. // If we are in an element that is not anymore a boundary, or
  215. // we are at the last element, let's move things outside the
  216. // boundary (if available).
  217. if ( lastBoundaryElement && ( !isBoundary || ( i == path.Elements.length - 1 ) ) )
  218. {
  219. // Remove the bookmark node from the DOM.
  220. var currentElement = FCKDomTools.RemoveNode( bookmarkStart ) ;
  221. // Build the collapsed group of elements that are not
  222. // removed by this style, but share the boundary.
  223. // (see comment 1 and 2 at #1270)
  224. for ( var j = 0 ; j <= boundaryLimitIndex ; j++ )
  225. {
  226. var newElement = FCKDomTools.CloneElement( boundaryElements[j] ) ;
  227. newElement.appendChild( currentElement ) ;
  228. currentElement = newElement ;
  229. }
  230. // Re-insert the bookmark node (and the collapsed elements)
  231. // in the DOM, in the new position next to the styled element.
  232. if ( isBoundaryRight )
  233. FCKDomTools.InsertAfterNode( lastBoundaryElement, currentElement ) ;
  234. else
  235. lastBoundaryElement.parentNode.insertBefore( currentElement, lastBoundaryElement ) ;
  236. isBoundary = false ;
  237. lastBoundaryElement = null ;
  238. }
  239. }
  240. // Re-select the original range.
  241. if ( selectIt )
  242. range.SelectBookmark( bookmark ) ;
  243. if ( updateRange )
  244. range.MoveToBookmark( bookmark ) ;
  245. return ;
  246. }
  247. // Expand the range, if inside inline element boundaries.
  248. range.Expand( 'inline_elements' ) ;
  249. // Bookmark the range so we can re-select it after processing.
  250. bookmark = range.CreateBookmark( true ) ;
  251. // The style will be applied within the bookmark boundaries.
  252. var startNode = range.GetBookmarkNode( bookmark, true ) ;
  253. var endNode = range.GetBookmarkNode( bookmark, false ) ;
  254. range.Release( true ) ;
  255. // We need to check the selection boundaries (bookmark spans) to break
  256. // the code in a way that we can properly remove partially selected nodes.
  257. // For example, removing a <b> style from
  258. // <b>This is [some text</b> to show <b>the] problem</b>
  259. // ... where [ and ] represent the selection, must result:
  260. // <b>This is </b>[some text to show the]<b> problem</b>
  261. // The strategy is simple, we just break the partial nodes before the
  262. // removal logic, having something that could be represented this way:
  263. // <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b>
  264. // Let's start checking the start boundary.
  265. var path = new FCKElementPath( startNode ) ;
  266. var pathElements = path.Elements ;
  267. var pathElement ;
  268. for ( var i = 1 ; i < pathElements.length ; i++ )
  269. {
  270. pathElement = pathElements[i] ;
  271. if ( pathElement == path.Block || pathElement == path.BlockLimit )
  272. break ;
  273. // If this element can be removed (even partially).
  274. if ( this.CheckElementRemovable( pathElement ) )
  275. FCKDomTools.BreakParent( startNode, pathElement, range ) ;
  276. }
  277. // Now the end boundary.
  278. path = new FCKElementPath( endNode ) ;
  279. pathElements = path.Elements ;
  280. for ( var i = 1 ; i < pathElements.length ; i++ )
  281. {
  282. pathElement = pathElements[i] ;
  283. if ( pathElement == path.Block || pathElement == path.BlockLimit )
  284. break ;
  285. elementName = pathElement.nodeName.toLowerCase() ;
  286. // If this element can be removed (even partially).
  287. if ( this.CheckElementRemovable( pathElement ) )
  288. FCKDomTools.BreakParent( endNode, pathElement, range ) ;
  289. }
  290. // Navigate through all nodes between the bookmarks.
  291. var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
  292. while ( currentNode )
  293. {
  294. // Cache the next node to be processed. Do it now, because
  295. // currentNode may be removed.
  296. var nextNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
  297. // Remove elements nodes that match with this style rules.
  298. if ( currentNode.nodeType == 1 )
  299. {
  300. var elementName = currentNode.nodeName.toLowerCase() ;
  301. var mayRemove = ( elementName == this.Element ) ;
  302. if ( mayRemove )
  303. {
  304. // Remove any attribute that conflict with this style, no matter
  305. // their values.
  306. for ( var att in styleAttribs )
  307. {
  308. if ( FCKDomTools.HasAttribute( currentNode, att ) )
  309. {
  310. switch ( att )
  311. {
  312. case 'style' :
  313. this._RemoveStylesFromElement( currentNode ) ;
  314. break ;
  315. case 'class' :
  316. // The 'class' element value must match (#1318).
  317. if ( FCKDomTools.GetAttributeValue( currentNode, att ) != this.GetFinalAttributeValue( att ) )
  318. continue ;
  319. /*jsl:fallthru*/
  320. default :
  321. FCKDomTools.RemoveAttribute( currentNode, att ) ;
  322. }
  323. }
  324. }
  325. }
  326. else
  327. mayRemove = !!styleOverrides[ elementName ] ;
  328. if ( mayRemove )
  329. {
  330. // Remove overrides defined to the same element name.
  331. this._RemoveOverrides( currentNode, styleOverrides[ elementName ] ) ;
  332. // Remove the element if no more attributes are available.
  333. this._RemoveNoAttribElement( currentNode ) ;
  334. }
  335. }
  336. // If we have reached the end of the selection, stop looping.
  337. if ( nextNode == endNode )
  338. break ;
  339. currentNode = nextNode ;
  340. }
  341. this._FixBookmarkStart( startNode ) ;
  342. // Re-select the original range.
  343. if ( selectIt )
  344. range.SelectBookmark( bookmark ) ;
  345. if ( updateRange )
  346. range.MoveToBookmark( bookmark ) ;
  347. },
  348. /**
  349.  * Checks if an element, or any of its attributes, is removable by the
  350.  * current style definition.
  351.  */
  352. CheckElementRemovable : function( element, fullMatch )
  353. {
  354. if ( !element )
  355. return false ;
  356. var elementName = element.nodeName.toLowerCase() ;
  357. // If the element name is the same as the style name.
  358. if ( elementName == this.Element )
  359. {
  360. // If no attributes are defined in the element.
  361. if ( !fullMatch && !FCKDomTools.HasAttributes( element ) )
  362. return true ;
  363. // If any attribute conflicts with the style attributes.
  364. var attribs = this._GetAttribsForComparison() ;
  365. var allMatched = ( attribs._length == 0 ) ;
  366. for ( var att in attribs )
  367. {
  368. if ( att == '_length' )
  369. continue ;
  370. if ( this._CompareAttributeValues( att, FCKDomTools.GetAttributeValue( element, att ), ( this.GetFinalAttributeValue( att ) || '' ) ) )
  371. {
  372. allMatched = true ;
  373. if ( !fullMatch )
  374. break ;
  375. }
  376. else
  377. {
  378. allMatched = false ;
  379. if ( fullMatch )
  380. return false ;
  381. }
  382. }
  383. if ( allMatched )
  384. return true ;
  385. }
  386. // Check if the element can be somehow overriden.
  387. var override = this._GetOverridesForComparison()[ elementName ] ;
  388. if ( override )
  389. {
  390. // If no attributes have been defined, remove the element.
  391. if ( !( attribs = override.Attributes ) ) // Only one "="
  392. return true ;
  393. for ( var i = 0 ; i < attribs.length ; i++ )
  394. {
  395. var attName = attribs[i][0] ;
  396. if ( FCKDomTools.HasAttribute( element, attName ) )
  397. {
  398. var attValue = attribs[i][1] ;
  399. // Remove the attribute if:
  400. //    - The override definition value is null ;
  401. //    - The override definition valie is a string that
  402. //      matches the attribute value exactly.
  403. //    - The override definition value is a regex that
  404. //      has matches in the attribute value.
  405. if ( attValue == null ||
  406. ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) ||
  407. attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) )
  408. return true ;
  409. }
  410. }
  411. }
  412. return false ;
  413. },
  414. /**
  415.  * Get the style state for an element path. Returns "true" if the element
  416.  * is active in the path.
  417.  */
  418. CheckActive : function( elementPath )
  419. {
  420. switch ( this.GetType() )
  421. {
  422. case FCK_STYLE_BLOCK :
  423. return this.CheckElementRemovable( elementPath.Block || elementPath.BlockLimit, true ) ;
  424. case FCK_STYLE_INLINE :
  425. var elements = elementPath.Elements ;
  426. for ( var i = 0 ; i < elements.length ; i++ )
  427. {
  428. var element = elements[i] ;
  429. if ( element == elementPath.Block || element == elementPath.BlockLimit )
  430. continue ;
  431. if ( this.CheckElementRemovable( element, true ) )
  432. return true ;
  433. }
  434. }
  435. return false ;
  436. },
  437. /**
  438.  * Removes an inline style from inside an element tree. The element node
  439.  * itself is not checked or removed, only the child tree inside of it.
  440.  */
  441. RemoveFromElement : function( element )
  442. {
  443. var attribs = this._GetAttribsForComparison() ;
  444. var overrides = this._GetOverridesForComparison() ;
  445. // Get all elements with the same name.
  446. var innerElements = element.getElementsByTagName( this.Element ) ;
  447. for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
  448. {
  449. var innerElement = innerElements[i] ;
  450. // Remove any attribute that conflict with this style, no matter
  451. // their values.
  452. for ( var att in attribs )
  453. {
  454. if ( FCKDomTools.HasAttribute( innerElement, att ) )
  455. {
  456. switch ( att )
  457. {
  458. case 'style' :
  459. this._RemoveStylesFromElement( innerElement ) ;
  460. break ;
  461. case 'class' :
  462. // The 'class' element value must match (#1318).
  463. if ( FCKDomTools.GetAttributeValue( innerElement, att ) != this.GetFinalAttributeValue( att ) )
  464. continue ;
  465. /*jsl:fallthru*/
  466. default :
  467. FCKDomTools.RemoveAttribute( innerElement, att ) ;
  468. }
  469. }
  470. }
  471. // Remove overrides defined to the same element name.
  472. this._RemoveOverrides( innerElement, overrides[ this.Element ] ) ;
  473. // Remove the element if no more attributes are available.
  474. this._RemoveNoAttribElement( innerElement ) ;
  475. }
  476. // Now remove any other element with different name that is
  477. // defined to be overriden.
  478. for ( var overrideElement in overrides )
  479. {
  480. if ( overrideElement != this.Element )
  481. {
  482. // Get all elements.
  483. innerElements = element.getElementsByTagName( overrideElement ) ;
  484. for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
  485. {
  486. var innerElement = innerElements[i] ;
  487. this._RemoveOverrides( innerElement, overrides[ overrideElement ] ) ;
  488. this._RemoveNoAttribElement( innerElement ) ;
  489. }
  490. }
  491. }
  492. },
  493. _RemoveStylesFromElement : function( element )
  494. {
  495. var elementStyle = element.style.cssText ;
  496. var pattern = this.GetFinalStyleValue() ;
  497. if ( elementStyle.length > 0 && pattern.length == 0 )
  498. return ;
  499. pattern = '(^|;)\s*(' +
  500. pattern.replace( /s*([^ ]+):.*?(;|$)/g, '$1|' ).replace( /|$/, '' ) +
  501. '):[^;]+' ;
  502. var regex = new RegExp( pattern, 'gi' ) ;
  503. elementStyle = elementStyle.replace( regex, '' ).Trim() ;
  504. if ( elementStyle.length == 0 || elementStyle == ';' )
  505. FCKDomTools.RemoveAttribute( element, 'style' ) ;
  506. else
  507. element.style.cssText = elementStyle.replace( regex, '' ) ;
  508. },
  509. /**
  510.  * Remove all attributes that are defined to be overriden,
  511.  */
  512. _RemoveOverrides : function( element, override )
  513. {
  514. var attributes = override && override.Attributes ;
  515. if ( attributes )
  516. {
  517. for ( var i = 0 ; i < attributes.length ; i++ )
  518. {
  519. var attName = attributes[i][0] ;
  520. if ( FCKDomTools.HasAttribute( element, attName ) )
  521. {
  522. var attValue = attributes[i][1] ;
  523. // Remove the attribute if:
  524. //    - The override definition value is null ;
  525. //    - The override definition valie is a string that
  526. //      matches the attribute value exactly.
  527. //    - The override definition value is a regex that
  528. //      has matches in the attribute value.
  529. if ( attValue == null ||
  530. ( attValue.test && attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) ) ||
  531. ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) )
  532. FCKDomTools.RemoveAttribute( element, attName ) ;
  533. }
  534. }
  535. }
  536. },
  537. /**
  538.  * If the element has no more attributes, remove it.
  539.  */
  540. _RemoveNoAttribElement : function( element )
  541. {
  542. // If no more attributes remained in the element, remove it,
  543. // leaving its children.
  544. if ( !FCKDomTools.HasAttributes( element ) )
  545. {
  546. // Removing elements may open points where merging is possible,
  547. // so let's cache the first and last nodes for later checking.
  548. var firstChild = element.firstChild ;
  549. var lastChild = element.lastChild ;
  550. FCKDomTools.RemoveNode( element, true ) ;
  551. // Check the cached nodes for merging.
  552. this._MergeSiblings( firstChild ) ;
  553. if ( firstChild != lastChild )
  554. this._MergeSiblings( lastChild ) ;
  555. }
  556. },
  557. /**
  558.  * Creates a DOM element for this style object.
  559.  */
  560. BuildElement : function( targetDoc, element )
  561. {
  562. // Create the element.
  563. var el = element || targetDoc.createElement( this.Element ) ;
  564. // Assign all defined attributes.
  565. var attribs = this._StyleDesc.Attributes ;
  566. var attValue ;
  567. if ( attribs )
  568. {
  569. for ( var att in attribs )
  570. {
  571. attValue = this.GetFinalAttributeValue( att ) ;
  572. if ( att.toLowerCase() == 'class' )
  573. el.className = attValue ;
  574. else
  575. el.setAttribute( att, attValue ) ;
  576. }
  577. }
  578. // Assign the style attribute.
  579. if ( this._GetStyleText().length > 0 )
  580. el.style.cssText = this.GetFinalStyleValue() ;
  581. return el ;
  582. },
  583. _CompareAttributeValues : function( attName, valueA, valueB )
  584. {
  585. if ( attName == 'style' && valueA && valueB )
  586. {
  587. valueA = valueA.replace( /;$/, '' ).toLowerCase() ;
  588. valueB = valueB.replace( /;$/, '' ).toLowerCase() ;
  589. }
  590. // Return true if they match or if valueA is null and valueB is an empty string
  591. return ( valueA == valueB || ( ( valueA === null || valueA === '' ) && ( valueB === null || valueB === '' ) ) )
  592. },
  593. GetFinalAttributeValue : function( attName )
  594. {
  595. var attValue = this._StyleDesc.Attributes ;
  596. var attValue = attValue ? attValue[ attName ] : null ;
  597. if ( !attValue && attName == 'style' )
  598. return this.GetFinalStyleValue() ;
  599. if ( attValue && this._Variables )
  600. // Using custom Replace() to guarantee the correct scope.
  601. attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
  602. return attValue ;
  603. },
  604. GetFinalStyleValue : function()
  605. {
  606. var attValue = this._GetStyleText() ;
  607. if ( attValue.length > 0 && this._Variables )
  608. {
  609. // Using custom Replace() to guarantee the correct scope.
  610. attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
  611. attValue = FCKTools.NormalizeCssText( attValue ) ;
  612. }
  613. return attValue ;
  614. },
  615. _GetVariableReplace : function()
  616. {
  617. // The second group in the regex is the variable name.
  618. return this._Variables[ arguments[2] ] || arguments[0] ;
  619. },
  620. /**
  621.  * Set the value of a variable attribute or style, to be used when
  622.  * appliying the style.
  623.  */
  624. SetVariable : function( name, value )
  625. {
  626. var variables = this._Variables ;
  627. if ( !variables )
  628. variables = this._Variables = {} ;
  629. this._Variables[ name ] = value ;
  630. },
  631. /**
  632.  * Converting from a PRE block to a non-PRE block in formatting operations.
  633.  */
  634. _FromPre : function( doc, block, newBlock )
  635. {
  636. var innerHTML = block.innerHTML ;
  637. // Trim the first and last linebreaks immediately after and before <pre>, </pre>,
  638. // if they exist.
  639. // This is done because the linebreaks are not rendered.
  640. innerHTML = innerHTML.replace( /(rn|r)/g, 'n' ) ;
  641. innerHTML = innerHTML.replace( /^[ t]*n/, '' ) ;
  642. innerHTML = innerHTML.replace( /n$/, '' ) ;
  643. // 1. Convert spaces or tabs at the beginning or at the end to &nbsp;
  644. innerHTML = innerHTML.replace( /^[ t]+|[ t]+$/g, function( match, offset, s )
  645. {
  646. if ( match.length == 1 ) // one space, preserve it
  647. return '&nbsp;' ;
  648. else if ( offset == 0 ) // beginning of block
  649. return new Array( match.length ).join( '&nbsp;' ) + ' ' ;
  650. else // end of block
  651. return ' ' + new Array( match.length ).join( '&nbsp;' ) ;
  652. } ) ;
  653. // 2. Convert n to <BR>.
  654. // 3. Convert contiguous (i.e. non-singular) spaces or tabs to &nbsp;
  655. var htmlIterator = new FCKHtmlIterator( innerHTML ) ;
  656. var results = [] ;
  657. htmlIterator.Each( function( isTag, value )
  658. {
  659. if ( !isTag )
  660. {
  661. value = value.replace( /n/g, '<br>' ) ;
  662. value = value.replace( /[ t]{2,}/g,
  663. function ( match )
  664. {
  665. return new Array( match.length ).join( '&nbsp;' ) + ' ' ;
  666. } ) ;
  667. }
  668. results.push( value ) ;
  669. } ) ;
  670. newBlock.innerHTML = results.join( '' ) ;
  671. return newBlock ;
  672. },
  673. /**
  674.  * Converting from a non-PRE block to a PRE block in formatting operations.
  675.  */
  676. _ToPre : function( doc, block, newBlock )
  677. {
  678. // Handle converting from a regular block to a <pre> block.
  679. var innerHTML = block.innerHTML.Trim() ;
  680. // 1. Delete ANSI whitespaces immediately before and after <BR> because
  681. //    they are not visible.
  682. // 2. Mark down any <BR /> nodes here so they can be turned into n in
  683. //    the next step and avoid being compressed.
  684. innerHTML = innerHTML.replace( /[ trn]*(<br[^>]*>)[ trn]*/gi, '<br />' ) ;
  685. // 3. Compress other ANSI whitespaces since they're only visible as one
  686. //    single space previously.
  687. // 4. Convert &nbsp; to spaces since &nbsp; is no longer needed in <PRE>.
  688. // 5. Convert any <BR /> to n. This must not be done earlier because
  689. //    the n would then get compressed.
  690. var htmlIterator = new FCKHtmlIterator( innerHTML ) ;
  691. var results = [] ;
  692. htmlIterator.Each( function( isTag, value )
  693. {
  694. if ( !isTag )
  695. value = value.replace( /([ tnr]+|&nbsp;)/g, ' ' ) ;
  696. else if ( isTag && value == '<br />' )
  697. value = 'n' ;
  698. results.push( value ) ;
  699. } ) ;
  700. // Assigning innerHTML to <PRE> in IE causes all linebreaks to be
  701. // reduced to spaces.
  702. // Assigning outerHTML to <PRE> in IE doesn't work if the <PRE> isn't
  703. // contained in another node since the node reference is changed after
  704. // outerHTML assignment.
  705. // So, we need some hacks to workaround IE bugs here.
  706. if ( FCKBrowserInfo.IsIE )
  707. {
  708. var temp = doc.createElement( 'div' ) ;
  709. temp.appendChild( newBlock ) ;
  710. newBlock.outerHTML = '<pre>n' + results.join( '' ) + '</pre>' ;
  711. newBlock = temp.removeChild( temp.firstChild ) ;
  712. }
  713. else
  714. newBlock.innerHTML = results.join( '' ) ;
  715. return newBlock ;
  716. },
  717. /**
  718.  * Merge a <pre> block with a previous <pre> block, if available.
  719.  */
  720. _CheckAndMergePre : function( previousBlock, preBlock )
  721. {
  722. // Check if the previous block and the current block are next
  723. // to each other.
  724. if ( previousBlock != FCKDomTools.GetPreviousSourceElement( preBlock, true ) )
  725. return ;
  726. // Merge the previous <pre> block contents into the current <pre>
  727. // block.
  728. //
  729. // Another thing to be careful here is that currentBlock might contain
  730. // a 'n' at the beginning, and previousBlock might contain a 'n'
  731. // towards the end. These new lines are not normally displayed but they
  732. // become visible after merging.
  733. var innerHTML = previousBlock.innerHTML.replace( /n$/, '' ) + 'nn' +
  734. preBlock.innerHTML.replace( /^n/, '' ) ;
  735. // Buggy IE normalizes innerHTML from <pre>, breaking whitespaces.
  736. if ( FCKBrowserInfo.IsIE )
  737. preBlock.outerHTML = '<pre>' + innerHTML + '</pre>' ;
  738. else
  739. preBlock.innerHTML = innerHTML ;
  740. // Remove the previous <pre> block.
  741. //
  742. // The preBlock must not be moved or deleted from the DOM tree. This
  743. // guarantees the FCKDomRangeIterator in _ApplyBlockStyle would not
  744. // get lost at the next iteration.
  745. FCKDomTools.RemoveNode( previousBlock ) ;
  746. },
  747. _CheckAndSplitPre : function( newBlock )
  748. {
  749. var lastNewBlock ;
  750. var cursor = newBlock.firstChild ;
  751. // We are not splitting <br><br> at the beginning of the block, so
  752. // we'll start from the second child.
  753. cursor = cursor && cursor.nextSibling ;
  754. while ( cursor )
  755. {
  756. var next = cursor.nextSibling ;
  757. // If we have two <BR>s, and they're not at the beginning or the end,
  758. // then we'll split up the contents following them into another block.
  759. // Stop processing if we are at the last child couple.
  760. if ( next && next.nextSibling && cursor.nodeName.IEquals( 'br' ) && next.nodeName.IEquals( 'br' ) )
  761. {
  762. // Remove the first <br>.
  763. FCKDomTools.RemoveNode( cursor ) ;
  764. // Move to the node after the second <br>.
  765. cursor = next.nextSibling ;
  766. // Remove the second <br>.
  767. FCKDomTools.RemoveNode( next ) ;
  768. // Create the block that will hold the child nodes from now on.
  769. lastNewBlock = FCKDomTools.InsertAfterNode( lastNewBlock || newBlock, FCKDomTools.CloneElement( newBlock ) ) ;
  770. continue ;
  771. }
  772. // If we split it, then start moving the nodes to the new block.
  773. if ( lastNewBlock )
  774. {
  775. cursor = cursor.previousSibling ;
  776. FCKDomTools.MoveNode(cursor.nextSibling, lastNewBlock ) ;
  777. }
  778. cursor = cursor.nextSibling ;
  779. }
  780. },
  781. /**
  782.  * Apply an inline style to a FCKDomRange.
  783.  *
  784.  * TODO
  785.  * - Implement the "#" style handling.
  786.  * - Properly handle block containers like <div> and <blockquote>.
  787.  */
  788. _ApplyBlockStyle : function( range, selectIt, updateRange )
  789. {
  790. // Bookmark the range so we can re-select it after processing.
  791. var bookmark ;
  792. if ( selectIt )
  793. bookmark = range.CreateBookmark() ;
  794. var iterator = new FCKDomRangeIterator( range ) ;
  795. iterator.EnforceRealBlocks = true ;
  796. var block ;
  797. var doc = range.Window.document ;
  798. var previousPreBlock ;
  799. while( ( block = iterator.GetNextParagraph() ) ) // Only one =
  800. {
  801. // Create the new node right before the current one.
  802. var newBlock = this.BuildElement( doc ) ;
  803. // Check if we are changing from/to <pre>.
  804. var newBlockIsPre = newBlock.nodeName.IEquals( 'pre' ) ;
  805. var blockIsPre = block.nodeName.IEquals( 'pre' ) ;
  806. var toPre = newBlockIsPre && !blockIsPre ;
  807. var fromPre = !newBlockIsPre && blockIsPre ;
  808. // Move everything from the current node to the new one.
  809. if ( toPre )
  810. newBlock = this._ToPre( doc, block, newBlock ) ;
  811. else if ( fromPre )
  812. newBlock = this._FromPre( doc, block, newBlock ) ;
  813. else // Convering from a regular block to another regular block.
  814. FCKDomTools.MoveChildren( block, newBlock ) ;
  815. // Replace the current block.
  816. block.parentNode.insertBefore( newBlock, block ) ;
  817. FCKDomTools.RemoveNode( block ) ;
  818. // Complete other tasks after inserting the node in the DOM.
  819. if ( newBlockIsPre )
  820. {
  821. if ( previousPreBlock )
  822. this._CheckAndMergePre( previousPreBlock, newBlock ) ; // Merge successive <pre> blocks.
  823. previousPreBlock = newBlock ;
  824. }
  825. else if ( fromPre )
  826. this._CheckAndSplitPre( newBlock ) ; // Split <br><br> in successive <pre>s.
  827. }
  828. // Re-select the original range.
  829. if ( selectIt )
  830. range.SelectBookmark( bookmark ) ;
  831. if ( updateRange )
  832. range.MoveToBookmark( bookmark ) ;
  833. },
  834. /**
  835.  * Apply an inline style to a FCKDomRange.
  836.  *
  837.  * TODO
  838.  * - Merge elements, when applying styles to similar elements that enclose
  839.  *    the entire selection, outputing:
  840.  *        <span style="color: #ff0000; background-color: #ffffff">XYZ</span>
  841.  *    instead of:
  842.  *        <span style="color: #ff0000;"><span style="background-color: #ffffff">XYZ</span></span>
  843.  */
  844. _ApplyInlineStyle : function( range, selectIt, updateRange )
  845. {
  846. var doc = range.Window.document ;
  847. if ( range.CheckIsCollapsed() )
  848. {
  849. // Create the element to be inserted in the DOM.
  850. var collapsedElement = this.BuildElement( doc ) ;
  851. range.InsertNode( collapsedElement ) ;
  852. range.MoveToPosition( collapsedElement, 2 ) ;
  853. range.Select() ;
  854. return ;
  855. }
  856. // The general idea here is navigating through all nodes inside the
  857. // current selection, working on distinct range blocks, defined by the
  858. // DTD compatibility between the style element and the nodes inside the
  859. // ranges.
  860. //
  861. // For example, suppose we have the following selection (where [ and ]
  862. // are the boundaries), and we apply a <b> style there:
  863. //
  864. // <p>Here we [have <b>some</b> text.<p>
  865. // <p>And some here] here.</p>
  866. //
  867. // Two different ranges will be detected:
  868. //
  869. // "have <b>some</b> text."
  870. // "And some here"
  871. //
  872. // Both ranges will be extracted, moved to a <b> element, and
  873. // re-inserted, resulting in the following output:
  874. //
  875. // <p>Here we [<b>have some text.</b><p>
  876. // <p><b>And some here</b>] here.</p>
  877. //
  878. // Note that the <b> element at <b>some</b> is also removed because it
  879. // is not needed anymore.
  880. var elementName = this.Element ;
  881. // Get the DTD definition for the element. Defaults to "span".
  882. var elementDTD = FCK.DTD[ elementName ] || FCK.DTD.span ;
  883. // Create the attribute list to be used later for element comparisons.
  884. var styleAttribs = this._GetAttribsForComparison() ;
  885. var styleNode ;
  886. // Expand the range, if inside inline element boundaries.
  887. range.Expand( 'inline_elements' ) ;
  888. // Bookmark the range so we can re-select it after processing.
  889. var bookmark = range.CreateBookmark( true ) ;
  890. // The style will be applied within the bookmark boundaries.
  891. var startNode = range.GetBookmarkNode( bookmark, true ) ;
  892. var endNode = range.GetBookmarkNode( bookmark, false ) ;
  893. // We'll be reusing the range to apply the styles. So, release it here
  894. // to indicate that it has not been initialized.
  895. range.Release( true ) ;
  896. // Let's start the nodes lookup from the node right after the bookmark
  897. // span.
  898. var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
  899. while ( currentNode )
  900. {
  901. var applyStyle = false ;
  902. var nodeType = currentNode.nodeType ;
  903. var nodeName = nodeType == 1 ? currentNode.nodeName.toLowerCase() : null ;
  904. // Check if the current node can be a child of the style element.
  905. if ( !nodeName || elementDTD[ nodeName ] )
  906. {
  907. // Check if the style element can be a child of the current
  908. // node parent or if the element is not defined in the DTD.
  909. if ( ( FCK.DTD[ currentNode.parentNode.nodeName.toLowerCase() ] || FCK.DTD.span )[ elementName ] || !FCK.DTD[ elementName ] )
  910. {
  911. // This node will be part of our range, so if it has not
  912. // been started, place its start right before the node.
  913. if ( !range.CheckHasRange() )
  914. range.SetStart( currentNode, 3 ) ;
  915. // Non element nodes, or empty elements can be added
  916. // completely to the range.
  917. if ( nodeType != 1 || currentNode.childNodes.length == 0 )
  918. {
  919. var includedNode = currentNode ;
  920. var parentNode = includedNode.parentNode ;
  921. // This node is about to be included completelly, but,
  922. // if this is the last node in its parent, we must also
  923. // check if the parent itself can be added completelly
  924. // to the range.
  925. while ( includedNode == parentNode.lastChild
  926. && elementDTD[ parentNode.nodeName.toLowerCase() ] )
  927. {
  928. includedNode = parentNode ;
  929. }
  930. range.SetEnd( includedNode, 4 ) ;
  931. // If the included node is the last node in its parent
  932. // and its parent can't be inside the style node, apply
  933. // the style immediately.
  934. if ( includedNode == includedNode.parentNode.lastChild && !elementDTD[ includedNode.parentNode.nodeName.toLowerCase() ] )
  935. applyStyle = true ;
  936. }
  937. else
  938. {
  939. // Element nodes will not be added directly. We need to
  940. // check their children because the selection could end
  941. // inside the node, so let's place the range end right
  942. // before the element.
  943. range.SetEnd( currentNode, 3 ) ;
  944. }
  945. }
  946. else
  947. applyStyle = true ;
  948. }
  949. else
  950. applyStyle = true ;
  951. // Get the next node to be processed.
  952. currentNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
  953. // If we have reached the end of the selection, just apply the
  954. // style ot the range, and stop looping.
  955. if ( currentNode == endNode )
  956. {
  957. currentNode = null ;
  958. applyStyle = true ;
  959. }
  960. // Apply the style if we have something to which apply it.
  961. if ( applyStyle && range.CheckHasRange() && !range.CheckIsCollapsed() )
  962. {
  963. // Build the style element, based on the style object definition.
  964. styleNode = this.BuildElement( doc ) ;
  965. // Move the contents of the range to the style element.
  966. range.ExtractContents().AppendTo( styleNode ) ;
  967. // If it is not empty.
  968. if ( styleNode.innerHTML.RTrim().length > 0 )
  969. {
  970. // Insert it in the range position (it is collapsed after
  971. // ExtractContents.
  972. range.InsertNode( styleNode ) ;
  973. // Here we do some cleanup, removing all duplicated
  974. // elements from the style element.
  975. this.RemoveFromElement( styleNode ) ;
  976. // Let's merge our new style with its neighbors, if possible.
  977. this._MergeSiblings( styleNode, this._GetAttribsForComparison() ) ;
  978. // As the style system breaks text nodes constantly, let's normalize
  979. // things for performance.
  980. // With IE, some paragraphs get broken when calling normalize()
  981. // repeatedly. Also, for IE, we must normalize body, not documentElement.
  982. // IE is also known for having a "crash effect" with normalize().
  983. // We should try to normalize with IE too in some way, somewhere.
  984. if ( !FCKBrowserInfo.IsIE )
  985. styleNode.normalize() ;
  986. }
  987. // Style applied, let's release the range, so it gets marked to
  988. // re-initialization in the next loop.
  989. range.Release( true ) ;
  990. }
  991. }
  992. this._FixBookmarkStart( startNode ) ;
  993. // Re-select the original range.
  994. if ( selectIt )
  995. range.SelectBookmark( bookmark ) ;
  996. if ( updateRange )
  997. range.MoveToBookmark( bookmark ) ;
  998. },
  999. _FixBookmarkStart : function( startNode )
  1000. {
  1001. // After appliying or removing an inline style, the start boundary of
  1002. // the selection must be placed inside all inline elements it is
  1003. // bordering.
  1004. var startSibling ;
  1005. while ( ( startSibling = startNode.nextSibling ) ) // Only one "=".
  1006. {
  1007. if ( startSibling.nodeType == 1
  1008. && FCKListsLib.InlineNonEmptyElements[ startSibling.nodeName.toLowerCase() ] )
  1009. {
  1010. // If it is an empty inline element, we can safely remove it.
  1011. if ( !startSibling.firstChild )
  1012. FCKDomTools.RemoveNode( startSibling ) ;
  1013. else
  1014. FCKDomTools.MoveNode( startNode, startSibling, true ) ;
  1015. continue ;
  1016. }
  1017. // Empty text nodes can be safely removed to not disturb.
  1018. if ( startSibling.nodeType == 3 && startSibling.length == 0 )
  1019. {
  1020. FCKDomTools.RemoveNode( startSibling ) ;
  1021. continue ;
  1022. }
  1023. break ;
  1024. }
  1025. },
  1026. /**
  1027.  * Merge an element with its similar siblings.
  1028.  * "attribs" is and object computed with _CreateAttribsForComparison.
  1029.  */
  1030. _MergeSiblings : function( element, attribs )
  1031. {
  1032. if ( !element || element.nodeType != 1 || !FCKListsLib.InlineNonEmptyElements[ element.nodeName.toLowerCase() ] )
  1033. return ;
  1034. this._MergeNextSibling( element, attribs ) ;
  1035. this._MergePreviousSibling( element, attribs ) ;
  1036. },
  1037. /**
  1038.  * Merge an element with its similar siblings after it.
  1039.  * "attribs" is and object computed with _CreateAttribsForComparison.
  1040.  */
  1041. _MergeNextSibling : function( element, attribs )
  1042. {
  1043. // Check the next sibling.
  1044. var sibling = element.nextSibling ;
  1045. // Check if the next sibling is a bookmark element. In this case, jump it.
  1046. var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
  1047. if ( hasBookmark )
  1048. sibling = sibling.nextSibling ;
  1049. if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
  1050. {
  1051. if ( !attribs )
  1052. attribs = this._CreateElementAttribsForComparison( element ) ;
  1053. if ( this._CheckAttributesMatch( sibling, attribs ) )
  1054. {
  1055. // Save the last child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
  1056. var innerSibling = element.lastChild ;
  1057. if ( hasBookmark )
  1058. FCKDomTools.MoveNode( element.nextSibling, element ) ;
  1059. // Move contents from the sibling.
  1060. FCKDomTools.MoveChildren( sibling, element ) ;
  1061. FCKDomTools.RemoveNode( sibling ) ;
  1062. // Now check the last inner child (see two comments above).
  1063. if ( innerSibling )
  1064. this._MergeNextSibling( innerSibling ) ;
  1065. }
  1066. }
  1067. },
  1068. /**
  1069.  * Merge an element with its similar siblings before it.
  1070.  * "attribs" is and object computed with _CreateAttribsForComparison.
  1071.  */
  1072. _MergePreviousSibling : function( element, attribs )
  1073. {
  1074. // Check the previous sibling.
  1075. var sibling = element.previousSibling ;
  1076. // Check if the previous sibling is a bookmark element. In this case, jump it.
  1077. var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
  1078. if ( hasBookmark )
  1079. sibling = sibling.previousSibling ;
  1080. if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
  1081. {
  1082. if ( !attribs )
  1083. attribs = this._CreateElementAttribsForComparison( element ) ;
  1084. if ( this._CheckAttributesMatch( sibling, attribs ) )
  1085. {
  1086. // Save the first child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
  1087. var innerSibling = element.firstChild ;
  1088. if ( hasBookmark )
  1089. FCKDomTools.MoveNode( element.previousSibling, element, true ) ;
  1090. // Move contents to the sibling.
  1091. FCKDomTools.MoveChildren( sibling, element, true ) ;
  1092. FCKDomTools.RemoveNode( sibling ) ;
  1093. // Now check the first inner child (see two comments above).
  1094. if ( innerSibling )
  1095. this._MergePreviousSibling( innerSibling ) ;
  1096. }
  1097. }
  1098. },
  1099. /**
  1100.  * Build the cssText based on the styles definition.
  1101.  */
  1102. _GetStyleText : function()
  1103. {
  1104. var stylesDef = this._StyleDesc.Styles ;
  1105. // Builds the StyleText.
  1106. var stylesText = ( this._StyleDesc.Attributes ? this._StyleDesc.Attributes['style'] || '' : '' ) ;
  1107. if ( stylesText.length > 0 )
  1108. stylesText += ';' ;
  1109. for ( var style in stylesDef )
  1110. stylesText += style + ':' + stylesDef[style] + ';' ;
  1111. // Browsers make some changes to the style when applying them. So, here
  1112. // we normalize it to the browser format. We'll not do that if there
  1113. // are variables inside the style.
  1114. if ( stylesText.length > 0 && !( /#(/.test( stylesText ) ) )
  1115. {
  1116. stylesText = FCKTools.NormalizeCssText( stylesText ) ;
  1117. }
  1118. return (this._GetStyleText = function() { return stylesText ; })() ;
  1119. },
  1120. /**
  1121.  * Get the the collection used to compare the attributes defined in this
  1122.  * style with attributes in an element. All information in it is lowercased.
  1123.  */
  1124. _GetAttribsForComparison : function()
  1125. {
  1126. // If we have already computed it, just return it.
  1127. var attribs = this._GetAttribsForComparison_$ ;
  1128. if ( attribs )
  1129. return attribs ;
  1130. attribs = new Object() ;
  1131. // Loop through all defined attributes.
  1132. var styleAttribs = this._StyleDesc.Attributes ;
  1133. if ( styleAttribs )
  1134. {
  1135. for ( var styleAtt in styleAttribs )
  1136. {
  1137. attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase() ;
  1138. }
  1139. }
  1140. // Includes the style definitions.
  1141. if ( this._GetStyleText().length > 0 )
  1142. {
  1143. attribs['style'] = this._GetStyleText().toLowerCase() ;
  1144. }
  1145. // Appends the "length" information to the object.
  1146. FCKTools.AppendLengthProperty( attribs, '_length' ) ;
  1147. // Return it, saving it to the next request.
  1148. return ( this._GetAttribsForComparison_$ = attribs ) ;
  1149. },
  1150. /**
  1151.  * Get the the collection used to compare the elements and attributes,
  1152.  * defined in this style overrides, with other element. All information in
  1153.  * it is lowercased.
  1154.  */
  1155. _GetOverridesForComparison : function()
  1156. {
  1157. // If we have already computed it, just return it.
  1158. var overrides = this._GetOverridesForComparison_$ ;
  1159. if ( overrides )
  1160. return overrides ;
  1161. overrides = new Object() ;
  1162. var overridesDesc = this._StyleDesc.Overrides ;
  1163. if ( overridesDesc )
  1164. {
  1165. // The override description can be a string, object or array.
  1166. // Internally, well handle arrays only, so transform it if needed.
  1167. if ( !FCKTools.IsArray( overridesDesc ) )
  1168. overridesDesc = [ overridesDesc ] ;
  1169. // Loop through all override definitions.
  1170. for ( var i = 0 ; i < overridesDesc.length ; i++ )
  1171. {
  1172. var override = overridesDesc[i] ;
  1173. var elementName ;
  1174. var overrideEl ;
  1175. var attrs ;
  1176. // If can be a string with the element name.
  1177. if ( typeof override == 'string' )
  1178. elementName = override.toLowerCase() ;
  1179. // Or an object.
  1180. else
  1181. {
  1182. elementName = override.Element ? override.Element.toLowerCase() : this.Element ;
  1183. attrs = override.Attributes ;
  1184. }
  1185. // We can have more than one override definition for the same
  1186. // element name, so we attempt to simply append information to
  1187. // it if it already exists.
  1188. overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} ) ;
  1189. if ( attrs )
  1190. {
  1191. // The returning attributes list is an array, because we
  1192. // could have different override definitions for the same
  1193. // attribute name.
  1194. var overrideAttrs = ( overrideEl.Attributes = overrideEl.Attributes || new Array() ) ;
  1195. for ( var attName in attrs )
  1196. {
  1197. // Each item in the attributes array is also an array,
  1198. // where [0] is the attribute name and [1] is the
  1199. // override value.
  1200. overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] ) ;
  1201. }
  1202. }
  1203. }
  1204. }
  1205. return ( this._GetOverridesForComparison_$ = overrides ) ;
  1206. },
  1207. /*
  1208.  * Create and object containing all attributes specified in an element,
  1209.  * added by a "_length" property. All values are lowercased.
  1210.  */
  1211. _CreateElementAttribsForComparison : function( element )
  1212. {
  1213. var attribs = new Object() ;
  1214. var attribsCount = 0 ;
  1215. for ( var i = 0 ; i < element.attributes.length ; i++ )
  1216. {
  1217. var att = element.attributes[i] ;
  1218. if ( att.specified )
  1219. {
  1220. attribs[ att.nodeName.toLowerCase() ] = FCKDomTools.GetAttributeValue( element, att ).toLowerCase() ;
  1221. attribsCount++ ;
  1222. }
  1223. }
  1224. attribs._length = attribsCount ;
  1225. return attribs ;
  1226. },
  1227. /**
  1228.  * Checks is the element attributes have a perfect match with the style
  1229.  * attributes.
  1230.  */
  1231. _CheckAttributesMatch : function( element, styleAttribs )
  1232. {
  1233. // Loop through all specified attributes. The same number of
  1234. // attributes must be found and their values must match to
  1235. // declare them as equal.
  1236. var elementAttrbs = element.attributes ;
  1237. var matchCount = 0 ;
  1238. for ( var i = 0 ; i < elementAttrbs.length ; i++ )
  1239. {
  1240. var att = elementAttrbs[i] ;
  1241. if ( att.specified )
  1242. {
  1243. var attName = att.nodeName.toLowerCase() ;
  1244. var styleAtt = styleAttribs[ attName ] ;
  1245. // The attribute is not defined in the style.
  1246. if ( !styleAtt )
  1247. break ;
  1248. // The values are different.
  1249. if ( styleAtt != FCKDomTools.GetAttributeValue( element, att ).toLowerCase() )
  1250. break ;
  1251. matchCount++ ;
  1252. }
  1253. }
  1254. return ( matchCount == styleAttribs._length ) ;
  1255. }
  1256. } ;