fckstyle.js
上传用户:ah_jiwei
上传日期:2022-07-24
资源大小:54044k
文件大小:38k
源码类别:

数据库编程

开发平台:

Visual C++

  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.  * 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 )
  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 ) ;
  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 )
  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. default :
  197. FCKDomTools.RemoveAttribute( pathElement, att ) ;
  198. }
  199. }
  200. }
  201. }
  202. // Remove overrides defined to the same element name.
  203. this._RemoveOverrides( pathElement, styleOverrides[ pathElementName ] ) ;
  204. // Remove the element if no more attributes are available.
  205. this._RemoveNoAttribElement( pathElement ) ;
  206. }
  207. }
  208. else if ( isBoundary )
  209. boundaryElements.push( pathElement ) ;
  210. // Check if we are still in a boundary (at the same side).
  211. isBoundary = isBoundary && ( ( isBoundaryRight && !FCKDomTools.GetNextSibling( pathElement ) ) || ( !isBoundaryRight && !FCKDomTools.GetPreviousSibling( pathElement ) ) ) ;
  212. // If we are in an element that is not anymore a boundary, or
  213. // we are at the last element, let's move things outside the
  214. // boundary (if available).
  215. if ( lastBoundaryElement && ( !isBoundary || ( i == path.Elements.length - 1 ) ) )
  216. {
  217. // Remove the bookmark node from the DOM.
  218. var currentElement = FCKDomTools.RemoveNode( bookmarkStart ) ;
  219. // Build the collapsed group of elements that are not
  220. // removed by this style, but share the boundary.
  221. // (see comment 1 and 2 at #1270)
  222. for ( var j = 0 ; j <= boundaryLimitIndex ; j++ )
  223. {
  224. var newElement = FCKDomTools.CloneElement( boundaryElements[j] ) ;
  225. newElement.appendChild( currentElement ) ;
  226. currentElement = newElement ;
  227. }
  228. // Re-insert the bookmark node (and the collapsed elements)
  229. // in the DOM, in the new position next to the styled element.
  230. if ( isBoundaryRight )
  231. FCKDomTools.InsertAfterNode( lastBoundaryElement, currentElement ) ;
  232. else
  233. lastBoundaryElement.parentNode.insertBefore( currentElement, lastBoundaryElement ) ;
  234. isBoundary = false ;
  235. lastBoundaryElement = null ;
  236. }
  237. }
  238. // Re-select the original range.
  239. if ( selectIt )
  240. range.SelectBookmark( bookmark ) ;
  241. return ;
  242. }
  243. // Expand the range, if inside inline element boundaries.
  244. range.Expand( 'inline_elements' ) ;
  245. // Bookmark the range so we can re-select it after processing.
  246. var bookmark = range.CreateBookmark( true ) ;
  247. // The style will be applied within the bookmark boundaries.
  248. var startNode = range.GetBookmarkNode( bookmark, true ) ;
  249. var endNode = range.GetBookmarkNode( bookmark, false ) ;
  250. range.Release( true ) ;
  251. // We need to check the selection boundaries (bookmark spans) to break
  252. // the code in a way that we can properly remove partially selected nodes.
  253. // For example, removing a <b> style from
  254. // <b>This is [some text</b> to show <b>the] problem</b>
  255. // ... where [ and ] represent the selection, must result:
  256. // <b>This is </b>[some text to show the]<b> problem</b>
  257. // The strategy is simple, we just break the partial nodes before the
  258. // removal logic, having something that could be represented this way:
  259. // <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b>
  260. // Let's start checking the start boundary.
  261. var path = new FCKElementPath( startNode ) ;
  262. var pathElements = path.Elements ;
  263. var pathElement ;
  264. for ( var i = 1 ; i < pathElements.length ; i++ )
  265. {
  266. pathElement = pathElements[i] ;
  267. if ( pathElement == path.Block || pathElement == path.BlockLimit )
  268. break ;
  269. // If this element can be removed (even partially).
  270. if ( this.CheckElementRemovable( pathElement ) )
  271. FCKDomTools.BreakParent( startNode, pathElement, range ) ;
  272. }
  273. // Now the end boundary.
  274. path = new FCKElementPath( endNode ) ;
  275. pathElements = path.Elements ;
  276. for ( var i = 1 ; i < pathElements.length ; i++ )
  277. {
  278. pathElement = pathElements[i] ;
  279. if ( pathElement == path.Block || pathElement == path.BlockLimit )
  280. break ;
  281. elementName = pathElement.nodeName.toLowerCase() ;
  282. // If this element can be removed (even partially).
  283. if ( this.CheckElementRemovable( pathElement ) )
  284. FCKDomTools.BreakParent( endNode, pathElement, range ) ;
  285. }
  286. // Navigate through all nodes between the bookmarks.
  287. var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
  288. while ( currentNode )
  289. {
  290. // Cache the next node to be processed. Do it now, because
  291. // currentNode may be removed.
  292. var nextNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
  293. // Remove elements nodes that match with this style rules.
  294. if ( currentNode.nodeType == 1 )
  295. {
  296. var elementName = currentNode.nodeName.toLowerCase() ;
  297. var mayRemove = ( elementName == this.Element ) ;
  298. if ( mayRemove )
  299. {
  300. // Remove any attribute that conflict with this style, no matter
  301. // their values.
  302. for ( var att in styleAttribs )
  303. {
  304. if ( FCKDomTools.HasAttribute( currentNode, att ) )
  305. {
  306. switch ( att )
  307. {
  308. case 'style' :
  309. this._RemoveStylesFromElement( currentNode ) ;
  310. break ;
  311. case 'class' :
  312. // The 'class' element value must match (#1318).
  313. if ( FCKDomTools.GetAttributeValue( currentNode, att ) != this.GetFinalAttributeValue( att ) )
  314. continue ;
  315. default :
  316. FCKDomTools.RemoveAttribute( currentNode, att ) ;
  317. }
  318. }
  319. }
  320. }
  321. else
  322. mayRemove = !!styleOverrides[ elementName ] ;
  323. if ( mayRemove )
  324. {
  325. // Remove overrides defined to the same element name.
  326. this._RemoveOverrides( currentNode, styleOverrides[ elementName ] ) ;
  327. // Remove the element if no more attributes are available.
  328. this._RemoveNoAttribElement( currentNode ) ;
  329. }
  330. }
  331. // If we have reached the end of the selection, stop looping.
  332. if ( nextNode == endNode )
  333. break ;
  334. currentNode = nextNode ;
  335. }
  336. this._FixBookmarkStart( startNode ) ;
  337. // Re-select the original range.
  338. if ( selectIt )
  339. range.SelectBookmark( bookmark ) ;
  340. },
  341. /**
  342.  * Checks if an element, or any of its attributes, is removable by the
  343.  * current style definition.
  344.  */
  345. CheckElementRemovable : function( element, fullMatch )
  346. {
  347. if ( !element )
  348. return false ;
  349. var elementName = element.nodeName.toLowerCase() ;
  350. // If the element name is the same as the style name.
  351. if ( elementName == this.Element )
  352. {
  353. // If no attributes are defined in the element.
  354. if ( !fullMatch && !FCKDomTools.HasAttributes( element ) )
  355. return true ;
  356. // If any attribute conflicts with the style attributes.
  357. var attribs = this._GetAttribsForComparison() ;
  358. var allMatched = ( attribs._length == 0 ) ;
  359. for ( var att in attribs )
  360. {
  361. if ( att == '_length' )
  362. continue ;
  363. if ( this._CompareAttributeValues( att, FCKDomTools.GetAttributeValue( element, att ), ( this.GetFinalAttributeValue( att ) || '' ) ) )
  364. {
  365. allMatched = true ;
  366. if ( !fullMatch )
  367. break ;
  368. }
  369. else
  370. {
  371. allMatched = false ;
  372. if ( fullMatch )
  373. return false ;
  374. }
  375. }
  376. if ( allMatched )
  377. return true ;
  378. }
  379. // Check if the element can be somehow overriden.
  380. var override = this._GetOverridesForComparison()[ elementName ] ;
  381. if ( override )
  382. {
  383. // If no attributes have been defined, remove the element.
  384. if ( !( attribs = override.Attributes ) ) // Only one "="
  385. return true ;
  386. for ( var i = 0 ; i < attribs.length ; i++ )
  387. {
  388. var attName = attribs[i][0] ;
  389. if ( FCKDomTools.HasAttribute( element, attName ) )
  390. {
  391. var attValue = attribs[i][1] ;
  392. // Remove the attribute if:
  393. //    - The override definition value is null ;
  394. //    - The override definition valie is a string that
  395. //      matches the attribute value exactly.
  396. //    - The override definition value is a regex that
  397. //      has matches in the attribute value.
  398. if ( attValue == null ||
  399. ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) ||
  400. attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) )
  401. return true ;
  402. }
  403. }
  404. }
  405. return false ;
  406. },
  407. /**
  408.  * Get the style state for an element path. Returns "true" if the element
  409.  * is active in the path.
  410.  */
  411. CheckActive : function( elementPath )
  412. {
  413. switch ( this.GetType() )
  414. {
  415. case FCK_STYLE_BLOCK :
  416. return this.CheckElementRemovable( elementPath.Block || elementPath.BlockLimit ) ;
  417. case FCK_STYLE_INLINE :
  418. var elements = elementPath.Elements ;
  419. for ( var i = 0 ; i < elements.length ; i++ )
  420. {
  421. var element = elements[i] ;
  422. if ( element == elementPath.Block || element == elementPath.BlockLimit )
  423. continue ;
  424. if ( this.CheckElementRemovable( element, true ) )
  425. return true ;
  426. }
  427. }
  428. return false ;
  429. },
  430. /**
  431.  * Removes an inline style from inside an element tree. The element node
  432.  * itself is not checked or removed, only the child tree inside of it.
  433.  */
  434. RemoveFromElement : function( element )
  435. {
  436. var attribs = this._GetAttribsForComparison() ;
  437. var overrides = this._GetOverridesForComparison() ;
  438. // Get all elements with the same name.
  439. var innerElements = element.getElementsByTagName( this.Element ) ;
  440. for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
  441. {
  442. var innerElement = innerElements[i] ;
  443. // Remove any attribute that conflict with this style, no matter
  444. // their values.
  445. for ( var att in attribs )
  446. {
  447. if ( FCKDomTools.HasAttribute( innerElement, att ) )
  448. {
  449. switch ( att )
  450. {
  451. case 'style' :
  452. this._RemoveStylesFromElement( innerElement ) ;
  453. break ;
  454. case 'class' :
  455. // The 'class' element value must match (#1318).
  456. if ( FCKDomTools.GetAttributeValue( innerElement, att ) != this.GetFinalAttributeValue( att ) )
  457. continue ;
  458. default :
  459. FCKDomTools.RemoveAttribute( innerElement, att ) ;
  460. }
  461. }
  462. }
  463. // Remove overrides defined to the same element name.
  464. this._RemoveOverrides( innerElement, overrides[ this.Element ] ) ;
  465. // Remove the element if no more attributes are available.
  466. this._RemoveNoAttribElement( innerElement ) ;
  467. }
  468. // Now remove any other element with different name that is
  469. // defined to be overriden.
  470. for ( var overrideElement in overrides )
  471. {
  472. if ( overrideElement != this.Element )
  473. {
  474. // Get all elements.
  475. innerElements = element.getElementsByTagName( overrideElement ) ;
  476. for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
  477. {
  478. var innerElement = innerElements[i] ;
  479. this._RemoveOverrides( innerElement, overrides[ overrideElement ] ) ;
  480. this._RemoveNoAttribElement( innerElement ) ;
  481. }
  482. }
  483. }
  484. },
  485. _RemoveStylesFromElement : function( element )
  486. {
  487. var elementStyle = element.style.cssText ;
  488. var pattern = this.GetFinalStyleValue() ;
  489. if ( elementStyle.length > 0 && pattern.length == 0 )
  490. return ;
  491. pattern = '(^|;)\s*(' +
  492. pattern.replace( /s*([^ ]+):.*?(;|$)/g, '$1|' ).replace( /|$/, '' ) +
  493. '):[^;]+' ;
  494. var regex = new RegExp( pattern, 'gi' ) ;
  495. elementStyle = elementStyle.replace( regex, '' ).Trim() ;
  496. if ( elementStyle.length == 0 || elementStyle == ';' )
  497. FCKDomTools.RemoveAttribute( element, 'style' ) ;
  498. else
  499. element.style.cssText = elementStyle.replace( regex, '' ) ;
  500. },
  501. /**
  502.  * Remove all attributes that are defined to be overriden,
  503.  */
  504. _RemoveOverrides : function( element, override )
  505. {
  506. var attributes = override && override.Attributes ;
  507. if ( attributes )
  508. {
  509. for ( var i = 0 ; i < attributes.length ; i++ )
  510. {
  511. var attName = attributes[i][0] ;
  512. if ( FCKDomTools.HasAttribute( element, attName ) )
  513. {
  514. var attValue = attributes[i][1] ;
  515. // Remove the attribute if:
  516. //    - The override definition value is null ;
  517. //    - The override definition valie is a string that
  518. //      matches the attribute value exactly.
  519. //    - The override definition value is a regex that
  520. //      has matches in the attribute value.
  521. if ( attValue == null ||
  522. ( attValue.test && attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) ) ||
  523. ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) )
  524. FCKDomTools.RemoveAttribute( element, attName ) ;
  525. }
  526. }
  527. }
  528. },
  529. /**
  530.  * If the element has no more attributes, remove it.
  531.  */
  532. _RemoveNoAttribElement : function( element )
  533. {
  534. // If no more attributes remained in the element, remove it,
  535. // leaving its children.
  536. if ( !FCKDomTools.HasAttributes( element ) )
  537. {
  538. // Removing elements may open points where merging is possible,
  539. // so let's cache the first and last nodes for later checking.
  540. var firstChild = element.firstChild ;
  541. var lastChild = element.lastChild ;
  542. FCKDomTools.RemoveNode( element, true ) ;
  543. // Check the cached nodes for merging.
  544. this._MergeSiblings( firstChild ) ;
  545. if ( firstChild != lastChild )
  546. this._MergeSiblings( lastChild ) ;
  547. }
  548. },
  549. /**
  550.  * Creates a DOM element for this style object.
  551.  */
  552. BuildElement : function( targetDoc, element )
  553. {
  554. // Create the element.
  555. var el = element || targetDoc.createElement( this.Element ) ;
  556. // Assign all defined attributes.
  557. var attribs = this._StyleDesc.Attributes ;
  558. var attValue ;
  559. if ( attribs )
  560. {
  561. for ( var att in attribs )
  562. {
  563. attValue = this.GetFinalAttributeValue( att ) ;
  564. if ( att.toLowerCase() == 'class' )
  565. el.className = attValue ;
  566. else
  567. el.setAttribute( att, attValue ) ;
  568. }
  569. }
  570. // Assign the style attribute.
  571. if ( this._GetStyleText().length > 0 )
  572. el.style.cssText = this.GetFinalStyleValue() ;
  573. return el ;
  574. },
  575. _CompareAttributeValues : function( attName, valueA, valueB )
  576. {
  577. if ( attName == 'style' && valueA && valueB )
  578. {
  579. valueA = valueA.replace( /;$/, '' ).toLowerCase() ;
  580. valueB = valueB.replace( /;$/, '' ).toLowerCase() ;
  581. }
  582. return ( valueA == valueB )
  583. },
  584. GetFinalAttributeValue : function( attName )
  585. {
  586. var attValue = this._StyleDesc.Attributes ;
  587. var attValue = attValue ? attValue[ attName ] : null ;
  588. if ( !attValue && attName == 'style' )
  589. return this.GetFinalStyleValue() ;
  590. if ( attValue && this._Variables )
  591. // Using custom Replace() to guarantee the correct scope.
  592. attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
  593. return attValue ;
  594. },
  595. GetFinalStyleValue : function()
  596. {
  597. var attValue = this._GetStyleText() ;
  598. if ( attValue.length > 0 && this._Variables )
  599. {
  600. // Using custom Replace() to guarantee the correct scope.
  601. attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
  602. attValue = FCKTools.NormalizeCssText( attValue ) ;
  603. }
  604. return attValue ;
  605. },
  606. _GetVariableReplace : function()
  607. {
  608. // The second group in the regex is the variable name.
  609. return this._Variables[ arguments[2] ] || arguments[0] ;
  610. },
  611. /**
  612.  * Set the value of a variable attribute or style, to be used when
  613.  * appliying the style.
  614.  */
  615. SetVariable : function( name, value )
  616. {
  617. var variables = this._Variables ;
  618. if ( !variables )
  619. variables = this._Variables = {} ;
  620. this._Variables[ name ] = value ;
  621. },
  622. /**
  623.  * Apply an inline style to a FCKDomRange.
  624.  *
  625.  * TODO
  626.  * - Implement the "#" style handling.
  627.  * - Properly handle block containers like <div> and <blockquote>.
  628.  */
  629. _ApplyBlockStyle : function( range, selectIt )
  630. {
  631. // Bookmark the range so we can re-select it after processing.
  632. var bookmark ;
  633. if ( selectIt )
  634. bookmark = range.CreateBookmark( true ) ;
  635. var iterator = new FCKDomRangeIterator( range ) ;
  636. iterator.EnforceRealBlocks = true ;
  637. var block ;
  638. while( ( block = iterator.GetNextParagraph() ) ) // Only one =
  639. {
  640. // Create the new node right before the current one.
  641. var newBlock = block.parentNode.insertBefore( this.BuildElement( range.Window.document ), block ) ;
  642. // Move everything from the current node to the new one.
  643. FCKDomTools.MoveChildren( block, newBlock ) ;
  644. // Delete the current node.
  645. FCKDomTools.RemoveNode( block ) ;
  646. }
  647. // Re-select the original range.
  648. if ( selectIt )
  649. range.SelectBookmark( bookmark ) ;
  650. },
  651. /**
  652.  * Apply an inline style to a FCKDomRange.
  653.  *
  654.  * TODO
  655.  * - Merge elements, when applying styles to similar elements that enclose
  656.  *    the entire selection, outputing:
  657.  *        <span style="color: #ff0000; background-color: #ffffff">XYZ</span>
  658.  *    instead of:
  659.  *        <span style="color: #ff0000;"><span style="background-color: #ffffff">XYZ</span></span>
  660.  */
  661. _ApplyInlineStyle : function( range, selectIt )
  662. {
  663. var doc = range.Window.document ;
  664. if ( range.CheckIsCollapsed() )
  665. {
  666. // Create the element to be inserted in the DOM.
  667. var collapsedElement = this.BuildElement( doc ) ;
  668. range.InsertNode( collapsedElement ) ;
  669. range.MoveToPosition( collapsedElement, 2 ) ;
  670. range.Select() ;
  671. return ;
  672. }
  673. // The general idea here is navigating through all nodes inside the
  674. // current selection, working on distinct range blocks, defined by the
  675. // DTD compatibility between the style element and the nodes inside the
  676. // ranges.
  677. //
  678. // For example, suppose we have the following selection (where [ and ]
  679. // are the boundaries), and we apply a <b> style there:
  680. //
  681. // <p>Here we [have <b>some</b> text.<p>
  682. // <p>And some here] here.</p>
  683. //
  684. // Two different ranges will be detected:
  685. //
  686. // "have <b>some</b> text."
  687. // "And some here"
  688. //
  689. // Both ranges will be extracted, moved to a <b> element, and
  690. // re-inserted, resulting in the following output:
  691. //
  692. // <p>Here we [<b>have some text.</b><p>
  693. // <p><b>And some here</b>] here.</p>
  694. //
  695. // Note that the <b> element at <b>some</b> is also removed because it
  696. // is not needed anymore.
  697. var elementName = this.Element ;
  698. // Get the DTD definition for the element. Defaults to "span".
  699. var elementDTD = FCK.DTD[ elementName ] || FCK.DTD.span ;
  700. // Create the attribute list to be used later for element comparisons.
  701. var styleAttribs = this._GetAttribsForComparison() ;
  702. var styleNode ;
  703. // Expand the range, if inside inline element boundaries.
  704. range.Expand( 'inline_elements' ) ;
  705. // Bookmark the range so we can re-select it after processing.
  706. var bookmark = range.CreateBookmark( true ) ;
  707. // The style will be applied within the bookmark boundaries.
  708. var startNode = range.GetBookmarkNode( bookmark, true ) ;
  709. var endNode = range.GetBookmarkNode( bookmark, false ) ;
  710. // We'll be reusing the range to apply the styles. So, release it here
  711. // to indicate that it has not been initialized.
  712. range.Release( true ) ;
  713. // Let's start the nodes lookup from the node right after the bookmark
  714. // span.
  715. var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
  716. while ( currentNode )
  717. {
  718. var applyStyle = false ;
  719. var nodeType = currentNode.nodeType ;
  720. var nodeName = nodeType == 1 ? currentNode.nodeName.toLowerCase() : null ;
  721. // Check if the current node can be a child of the style element.
  722. if ( !nodeName || elementDTD[ nodeName ] )
  723. {
  724. // Check if the style element can be a child of the current
  725. // node parent.
  726. if ( ( FCK.DTD[ currentNode.parentNode.nodeName.toLowerCase() ] || FCK.DTD.span )[ elementName ] )
  727. {
  728. // This node will be part of our range, so if it has not
  729. // been started, place its start right before the node.
  730. if ( !range.CheckHasRange() )
  731. range.SetStart( currentNode, 3 ) ;
  732. // Non element nodes, or empty elements can be added
  733. // completely to the range.
  734. if ( nodeType != 1 || currentNode.childNodes.length == 0 )
  735. {
  736. var includedNode = currentNode ;
  737. var parentNode = includedNode.parentNode ;
  738. // This node is about to be included completelly, but,
  739. // if this is the last node in its parent, we must also
  740. // check if the parent itself can be added completelly
  741. // to the range.
  742. while ( includedNode == parentNode.lastChild
  743. && elementDTD[ parentNode.nodeName.toLowerCase() ] )
  744. {
  745. includedNode = parentNode ;
  746. }
  747. range.SetEnd( includedNode, 4 ) ;
  748. // If the included node is the last node in its parent
  749. // and its parent can't be inside the style node, apply
  750. // the style immediately.
  751. if ( includedNode == includedNode.parentNode.lastChild && !elementDTD[ includedNode.parentNode.nodeName.toLowerCase() ] )
  752. applyStyle = true ;
  753. }
  754. else
  755. {
  756. // Element nodes will not be added directly. We need to
  757. // check their children because the selection could end
  758. // inside the node, so let's place the range end right
  759. // before the element.
  760. range.SetEnd( currentNode, 3 ) ;
  761. }
  762. }
  763. else
  764. applyStyle = true ;
  765. }
  766. else
  767. applyStyle = true ;
  768. // Get the next node to be processed.
  769. currentNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
  770. // If we have reached the end of the selection, just apply the
  771. // style ot the range, and stop looping.
  772. if ( currentNode == endNode )
  773. {
  774. currentNode = null ;
  775. applyStyle = true ;
  776. }
  777. // Apply the style if we have something to which apply it.
  778. if ( applyStyle && range.CheckHasRange() && !range.CheckIsCollapsed() )
  779. {
  780. // Build the style element, based on the style object definition.
  781. styleNode = this.BuildElement( doc ) ;
  782. // Move the contents of the range to the style element.
  783. range.ExtractContents().AppendTo( styleNode ) ;
  784. // If it is not empty.
  785. if ( styleNode.innerHTML.RTrim().length > 0 )
  786. {
  787. // Insert it in the range position (it is collapsed after
  788. // ExtractContents.
  789. range.InsertNode( styleNode ) ;
  790. // Here we do some cleanup, removing all duplicated
  791. // elements from the style element.
  792. this.RemoveFromElement( styleNode ) ;
  793. // Let's merge our new style with its neighbors, if possible.
  794. this._MergeSiblings( styleNode, this._GetAttribsForComparison() ) ;
  795. // As the style system breaks text nodes constantly, let's normalize
  796. // things for performance.
  797. // With IE, some paragraphs get broken when calling normalize()
  798. // repeatedly. Also, for IE, we must normalize body, not documentElement.
  799. // IE is also known for having a "crash effect" with normalize().
  800. // We should try to normalize with IE too in some way, somewhere.
  801. if ( !FCKBrowserInfo.IsIE )
  802. styleNode.normalize() ;
  803. }
  804. // Style applied, let's release the range, so it gets marked to
  805. // re-initialization in the next loop.
  806. range.Release( true ) ;
  807. }
  808. }
  809. this._FixBookmarkStart( startNode ) ;
  810. // Re-select the original range.
  811. if ( selectIt )
  812. range.SelectBookmark( bookmark ) ;
  813. },
  814. _FixBookmarkStart : function( startNode )
  815. {
  816. // After appliying or removing an inline style, the start boundary of
  817. // the selection must be placed inside all inline elements it is
  818. // bordering.
  819. var startSibling ;
  820. while ( ( startSibling = startNode.nextSibling ) ) // Only one "=".
  821. {
  822. if ( startSibling.nodeType == 1
  823. && FCKListsLib.InlineNonEmptyElements[ startSibling.nodeName.toLowerCase() ] )
  824. {
  825. // If it is an empty inline element, we can safely remove it.
  826. if ( !startSibling.firstChild )
  827. FCKDomTools.RemoveNode( startSibling ) ;
  828. else
  829. FCKDomTools.MoveNode( startNode, startSibling, true ) ;
  830. continue ;
  831. }
  832. // Empty text nodes can be safely removed to not disturb.
  833. if ( startSibling.nodeType == 3 && startSibling.length == 0 )
  834. {
  835. FCKDomTools.RemoveNode( startSibling ) ;
  836. continue ;
  837. }
  838. break ;
  839. }
  840. },
  841. /**
  842.  * Merge an element with its similar siblings.
  843.  * "attribs" is and object computed with _CreateAttribsForComparison.
  844.  */
  845. _MergeSiblings : function( element, attribs )
  846. {
  847. if ( !element || element.nodeType != 1 || !FCKListsLib.InlineNonEmptyElements[ element.nodeName.toLowerCase() ] )
  848. return ;
  849. this._MergeNextSibling( element, attribs ) ;
  850. this._MergePreviousSibling( element, attribs ) ;
  851. },
  852. /**
  853.  * Merge an element with its similar siblings after it.
  854.  * "attribs" is and object computed with _CreateAttribsForComparison.
  855.  */
  856. _MergeNextSibling : function( element, attribs )
  857. {
  858. // Check the next sibling.
  859. var sibling = element.nextSibling ;
  860. // Check if the next sibling is a bookmark element. In this case, jump it.
  861. var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
  862. if ( hasBookmark )
  863. sibling = sibling.nextSibling ;
  864. if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
  865. {
  866. if ( !attribs )
  867. attribs = this._CreateElementAttribsForComparison( element ) ;
  868. if ( this._CheckAttributesMatch( sibling, attribs ) )
  869. {
  870. // Save the last child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
  871. var innerSibling = element.lastChild ;
  872. if ( hasBookmark )
  873. FCKDomTools.MoveNode( element.nextSibling, element ) ;
  874. // Move contents from the sibling.
  875. FCKDomTools.MoveChildren( sibling, element ) ;
  876. FCKDomTools.RemoveNode( sibling ) ;
  877. // Now check the last inner child (see two comments above).
  878. if ( innerSibling )
  879. this._MergeNextSibling( innerSibling ) ;
  880. }
  881. }
  882. },
  883. /**
  884.  * Merge an element with its similar siblings before it.
  885.  * "attribs" is and object computed with _CreateAttribsForComparison.
  886.  */
  887. _MergePreviousSibling : function( element, attribs )
  888. {
  889. // Check the previous sibling.
  890. var sibling = element.previousSibling ;
  891. // Check if the previous sibling is a bookmark element. In this case, jump it.
  892. var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
  893. if ( hasBookmark )
  894. sibling = sibling.previousSibling ;
  895. if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
  896. {
  897. if ( !attribs )
  898. attribs = this._CreateElementAttribsForComparison( element ) ;
  899. if ( this._CheckAttributesMatch( sibling, attribs ) )
  900. {
  901. // Save the first child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
  902. var innerSibling = element.firstChild ;
  903. if ( hasBookmark )
  904. FCKDomTools.MoveNode( element.previousSibling, element, true ) ;
  905. // Move contents to the sibling.
  906. FCKDomTools.MoveChildren( sibling, element, true ) ;
  907. FCKDomTools.RemoveNode( sibling ) ;
  908. // Now check the first inner child (see two comments above).
  909. if ( innerSibling )
  910. this._MergePreviousSibling( innerSibling ) ;
  911. }
  912. }
  913. },
  914. /**
  915.  * Build the cssText based on the styles definition.
  916.  */
  917. _GetStyleText : function()
  918. {
  919. var stylesDef = this._StyleDesc.Styles ;
  920. // Builds the StyleText.
  921. var stylesText = ( this._StyleDesc.Attributes ? this._StyleDesc.Attributes['style'] || '' : '' ) ;
  922. if ( stylesText.length > 0 )
  923. stylesText += ';' ;
  924. for ( var style in stylesDef )
  925. stylesText += style + ':' + stylesDef[style] + ';' ;
  926. // Browsers make some changes to the style when applying them. So, here
  927. // we normalize it to the browser format. We'll not do that if there
  928. // are variables inside the style.
  929. if ( stylesText.length > 0 && !( /#(/.test( stylesText ) ) )
  930. {
  931. stylesText = FCKTools.NormalizeCssText( stylesText ) ;
  932. }
  933. return (this._GetStyleText = function() { return stylesText ; })() ;
  934. },
  935. /**
  936.  * Get the the collection used to compare the attributes defined in this
  937.  * style with attributes in an element. All information in it is lowercased.
  938.  */
  939. _GetAttribsForComparison : function()
  940. {
  941. // If we have already computed it, just return it.
  942. var attribs = this._GetAttribsForComparison_$ ;
  943. if ( attribs )
  944. return attribs ;
  945. attribs = new Object() ;
  946. // Loop through all defined attributes.
  947. var styleAttribs = this._StyleDesc.Attributes ;
  948. if ( styleAttribs )
  949. {
  950. for ( var styleAtt in styleAttribs )
  951. {
  952. attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase() ;
  953. }
  954. }
  955. // Includes the style definitions.
  956. if ( this._GetStyleText().length > 0 )
  957. {
  958. attribs['style'] = this._GetStyleText().toLowerCase() ;
  959. }
  960. // Appends the "length" information to the object.
  961. FCKTools.AppendLengthProperty( attribs, '_length' ) ;
  962. // Return it, saving it to the next request.
  963. return ( this._GetAttribsForComparison_$ = attribs ) ;
  964. },
  965. /**
  966.  * Get the the collection used to compare the elements and attributes,
  967.  * defined in this style overrides, with other element. All information in
  968.  * it is lowercased.
  969.  */
  970. _GetOverridesForComparison : function()
  971. {
  972. // If we have already computed it, just return it.
  973. var overrides = this._GetOverridesForComparison_$ ;
  974. if ( overrides )
  975. return overrides ;
  976. overrides = new Object() ;
  977. var overridesDesc = this._StyleDesc.Overrides ;
  978. if ( overridesDesc )
  979. {
  980. // The override description can be a string, object or array.
  981. // Internally, well handle arrays only, so transform it if needed.
  982. if ( !FCKTools.IsArray( overridesDesc ) )
  983. overridesDesc = [ overridesDesc ] ;
  984. // Loop through all override definitions.
  985. for ( var i = 0 ; i < overridesDesc.length ; i++ )
  986. {
  987. var override = overridesDesc[i] ;
  988. var elementName ;
  989. var overrideEl ;
  990. var attrs ;
  991. // If can be a string with the element name.
  992. if ( typeof override == 'string' )
  993. elementName = override.toLowerCase() ;
  994. // Or an object.
  995. else
  996. {
  997. elementName = override.Element ? override.Element.toLowerCase() : this.Element ;
  998. attrs = override.Attributes ;
  999. }
  1000. // We can have more than one override definition for the same
  1001. // element name, so we attempt to simply append information to
  1002. // it if it already exists.
  1003. overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} ) ;
  1004. if ( attrs )
  1005. {
  1006. // The returning attributes list is an array, because we
  1007. // could have different override definitions for the same
  1008. // attribute name.
  1009. var overrideAttrs = ( overrideEl.Attributes = overrideEl.Attributes || new Array() ) ;
  1010. for ( var attName in attrs )
  1011. {
  1012. // Each item in the attributes array is also an array,
  1013. // where [0] is the attribute name and [1] is the
  1014. // override value.
  1015. overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] ) ;
  1016. }
  1017. }
  1018. }
  1019. }
  1020. return ( this._GetOverridesForComparison_$ = overrides ) ;
  1021. },
  1022. /*
  1023.  * Create and object containing all attributes specified in an element,
  1024.  * added by a "_length" property. All values are lowercased.
  1025.  */
  1026. _CreateElementAttribsForComparison : function( element )
  1027. {
  1028. var attribs = new Object() ;
  1029. var attribsCount = 0 ;
  1030. for ( var i = 0 ; i < element.attributes.length ; i++ )
  1031. {
  1032. var att = element.attributes[i] ;
  1033. if ( att.specified )
  1034. {
  1035. attribs[ att.nodeName.toLowerCase() ] = FCKDomTools.GetAttributeValue( element, att ).toLowerCase() ;
  1036. attribsCount++ ;
  1037. }
  1038. }
  1039. attribs._length = attribsCount ;
  1040. return attribs ;
  1041. },
  1042. /**
  1043.  * Checks is the element attributes have a perfect match with the style
  1044.  * attributes.
  1045.  */
  1046. _CheckAttributesMatch : function( element, styleAttribs )
  1047. {
  1048. // Loop through all specified attributes. The same number of
  1049. // attributes must be found and their values must match to
  1050. // declare them as equal.
  1051. var elementAttrbs = element.attributes ;
  1052. var matchCount = 0 ;
  1053. for ( var i = 0 ; i < elementAttrbs.length ; i++ )
  1054. {
  1055. var att = elementAttrbs[i] ;
  1056. if ( att.specified )
  1057. {
  1058. var attName = att.nodeName.toLowerCase() ;
  1059. var styleAtt = styleAttribs[ attName ] ;
  1060. // The attribute is not defined in the style.
  1061. if ( !styleAtt )
  1062. break ;
  1063. // The values are different.
  1064. if ( styleAtt != FCKDomTools.GetAttributeValue( element, att ).toLowerCase() )
  1065. break ;
  1066. matchCount++ ;
  1067. }
  1068. }
  1069. return ( matchCount == styleAttribs._length ) ;
  1070. }
  1071. } ;