| 50 | | function ReplaceTextNodes( parentNode, regex, replaceValue, replaceAll, hasFound ) |
| 51 | | { |
| 52 | | for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) |
| 53 | | { |
| 54 | | var oNode = parentNode.childNodes[i] ; |
| 55 | | if ( oNode.nodeType == 3 ) |
| 56 | | { |
| 57 | | var sReplaced = oNode.nodeValue.replace( regex, replaceValue ) ; |
| 58 | | if ( oNode.nodeValue != sReplaced ) |
| | 48 | function GetSelection() |
| | 49 | { |
| | 50 | var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ; |
| | 51 | range.MoveToSelection() ; |
| | 52 | return range.CreateBookmark2() ; |
| | 53 | } |
| | 54 | |
| | 55 | function GetSearchString() |
| | 56 | { |
| | 57 | return document.getElementById("txtFind").value ; |
| | 58 | } |
| | 59 | |
| | 60 | function GetReplaceString() |
| | 61 | { |
| | 62 | return document.getElementById("txtReplace").value ; |
| | 63 | } |
| | 64 | |
| | 65 | function GetCheckCase() |
| | 66 | { |
| | 67 | return !! ( document.getElementById("chkCase").checked ) ; |
| | 68 | } |
| | 69 | |
| | 70 | function GetMatchWord() |
| | 71 | { |
| | 72 | return !! ( document.getElementById("chkWord").checked ) ; |
| | 73 | } |
| | 74 | |
| | 75 | // Get the data pointed to by a bookmark. |
| | 76 | function GetData( bookmark ) |
| | 77 | { |
| | 78 | var currentNode = oEditor.FCK.EditorDocument.documentElement; |
| | 79 | for( var i = 0 ; i < bookmark.length ; i++ ) |
| | 80 | { |
| | 81 | if ( currentNode.childNodes.length > bookmark[i] ) |
| | 82 | currentNode = currentNode.childNodes.item( bookmark[i] ) ; |
| | 83 | else if ( currentNode.nodeType == 3 ) // text node |
| | 84 | { |
| | 85 | var c = currentNode.nodeValue.charAt( bookmark[i] ) ; |
| | 86 | if ( i == bookmark.length - 1 ) |
| | 87 | return c != "" ? c : null ; |
| | 88 | else |
| | 89 | return null ; |
| | 90 | } |
| | 91 | else |
| | 92 | return null; |
| | 93 | } |
| | 94 | return currentNode ; |
| | 95 | } |
| | 96 | |
| | 97 | // With this function, we can treat the bookmark as an iterator for DFS. |
| | 98 | function NextPosition( bookmark ) |
| | 99 | { |
| | 100 | // See if there's anything further down the tree. |
| | 101 | var next = bookmark.concat( [0] ) ; |
| | 102 | |
| | 103 | if ( GetData( next ) != null ) |
| | 104 | return next ; |
| | 105 | |
| | 106 | // Nothing down there? See if there's anything next to me. |
| | 107 | var next = bookmark.slice( 0, bookmark.length - 1 ).concat( [ bookmark[ bookmark.length - 1 ] + 1 ] ) ; |
| | 108 | if ( GetData( next ) != null ) |
| | 109 | return next ; |
| | 110 | |
| | 111 | // Nothing even next to me? See if there's anything next to my ancestors. |
| | 112 | for ( var i = bookmark.length - 1 ; i > 0 ; i-- ) |
| | 113 | { |
| | 114 | var next = bookmark.slice( 0, i - 1 ).concat( [ bookmark[ i - 1 ] + 1 ] ) ; |
| | 115 | if ( GetData( next ) != null ) |
| | 116 | return next ; |
| | 117 | } |
| | 118 | |
| | 119 | // There's absolutely nothing left to walk, return null. |
| | 120 | return null ; |
| | 121 | } |
| | 122 | |
| | 123 | // Is this character a unicode whitespace? |
| | 124 | // Reference: http://unicode.org/Public/UNIDATA/PropList.txt |
| | 125 | function CheckIsWhitespace( c ) |
| | 126 | { |
| | 127 | var code = c.charCodeAt( 0 ); |
| | 128 | if ( code >= 9 && code <= 0xd ) |
| | 129 | return true; |
| | 130 | if ( code >= 0x2000 && code <= 0x200a ) |
| | 131 | return true; |
| | 132 | switch ( code ) |
| | 133 | { |
| | 134 | case 0x20: |
| | 135 | case 0x85: |
| | 136 | case 0xa0: |
| | 137 | case 0x1680: |
| | 138 | case 0x180e: |
| | 139 | case 0x2028: |
| | 140 | case 0x2029: |
| | 141 | case 0x202f: |
| | 142 | case 0x205f: |
| | 143 | case 3000: |
| | 144 | return true; |
| | 145 | default: |
| | 146 | return false; |
| | 147 | } |
| | 148 | } |
| | 149 | |
| | 150 | // Knuth-Morris-Pratt Algorithm for stream input |
| | 151 | KMP_NOMATCH = 0 ; |
| | 152 | KMP_ADVANCED = 1 ; |
| | 153 | KMP_MATCHED = 2 ; |
| | 154 | function KmpMatch( pattern, ignoreCase ) |
| | 155 | { |
| | 156 | var overlap = [ -1 ] ; |
| | 157 | for ( var i = 0 ; i < pattern.length ; i++ ) |
| | 158 | { |
| | 159 | overlap.push( overlap[i] + 1 ) ; |
| | 160 | while ( overlap[ i + 1 ] > 0 && pattern.charAt( i ) != pattern.charAt( overlap[ i + 1 ] - 1 ) ) |
| | 161 | overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1 ; |
| | 162 | } |
| | 163 | this._Overlap = overlap ; |
| | 164 | this._State = 0 ; |
| | 165 | this._IgnoreCase = ( ignoreCase === true ) ; |
| | 166 | if ( ignoreCase ) |
| | 167 | this.Pattern = pattern.toLowerCase(); |
| | 168 | else |
| | 169 | this.Pattern = pattern ; |
| | 170 | } |
| | 171 | KmpMatch.prototype = { |
| | 172 | "FeedCharacter" : function( c ) |
| | 173 | { |
| | 174 | if ( this._IgnoreCase ) |
| | 175 | c = c.toLowerCase(); |
| | 176 | |
| | 177 | while ( true ) |
| | 178 | { |
| | 179 | if ( c == this.Pattern[ this._State ] ) |
| 65 | | } |
| 66 | | |
| 67 | | hasFound = ReplaceTextNodes( oNode, regex, replaceValue, replaceAll, hasFound ) ; |
| 68 | | if ( ! replaceAll && hasFound ) |
| 69 | | return true ; |
| 70 | | } |
| 71 | | |
| 72 | | return hasFound ; |
| 73 | | } |
| 74 | | |
| 75 | | function GetRegexExpr() |
| 76 | | { |
| 77 | | var sExpr = EscapeRegexString( document.getElementById('txtFind').value ) ; |
| 78 | | |
| 79 | | if ( document.getElementById('chkWord').checked ) |
| 80 | | sExpr = '\\b' + sExpr + '\\b' ; |
| 81 | | |
| 82 | | return sExpr ; |
| 83 | | } |
| 84 | | |
| 85 | | function GetCase() |
| 86 | | { |
| 87 | | return ( document.getElementById('chkCase').checked ? '' : 'i' ) ; |
| 88 | | } |
| 89 | | |
| 90 | | function GetReplacement() |
| 91 | | { |
| 92 | | return document.getElementById('txtReplace').value.replace( /\$/g, '$$$$' ) ; |
| 93 | | } |
| 94 | | |
| 95 | | function EscapeRegexString( str ) |
| 96 | | { |
| 97 | | return str.replace( /[\\\^\$\*\+\?\{\}\.\(\)\!\|\[\]\-]/g, '\\$&' ) ; |
| | 190 | else if ( this._State == 0 ) |
| | 191 | return KMP_NOMATCH; |
| | 192 | else |
| | 193 | this._State = this._Overlap[ this._State ]; |
| | 194 | } |
| | 195 | }, |
| | 196 | "Reset" : function() |
| | 197 | { |
| | 198 | this._State = 0 ; |
| | 199 | } |
| | 200 | }; |
| | 201 | |
| | 202 | function Find() |
| | 203 | { |
| | 204 | // Start from the end of the current selection. |
| | 205 | var matcher = new KmpMatch( GetSearchString(), ! GetCheckCase() ) ; |
| | 206 | var cursor = GetSelection().End ; |
| | 207 | var matchState = KMP_NOMATCH ; |
| | 208 | var matchBookmark = null ; |
| | 209 | |
| | 210 | // Match finding. |
| | 211 | while ( true ) |
| | 212 | { |
| | 213 | // Perform KMP stream matching. |
| | 214 | // - Reset KMP matcher if we encountered a block element. |
| | 215 | var data = GetData( cursor ) ; |
| | 216 | if ( data == null) |
| | 217 | { |
| | 218 | // do nothing, skip other cases |
| | 219 | } |
| | 220 | else if ( data.tagName ) |
| | 221 | { |
| | 222 | if ( oEditor.FCKListsLib.BlockElements[ data.tagName.toLowerCase() ] ) |
| | 223 | matcher.Reset(); |
| | 224 | } |
| | 225 | else if ( data.charAt != undefined ) |
| | 226 | { |
| | 227 | matchState = matcher.FeedCharacter(data) ; |
| | 228 | |
| | 229 | if ( matchState == KMP_NOMATCH ) |
| | 230 | matchBookmark = null ; |
| | 231 | else if ( matchState == KMP_ADVANCED && matchBookmark == null ) |
| | 232 | matchBookmark = { "Start" : cursor.concat( [] ) } ; |
| | 233 | else if ( matchState == KMP_MATCHED ) |
| | 234 | { |
| | 235 | matchBookmark.End = cursor.concat( [] ) ; |
| | 236 | matchBookmark.End[ matchBookmark.End.length - 1 ]++; |
| | 237 | |
| | 238 | // Wait, do we have to match a whole word? |
| | 239 | if ( GetMatchWord() ) |
| | 240 | { |
| | 241 | var startOk = false ; |
| | 242 | var endOk = false ; |
| | 243 | var start = matchBookmark.Start ; |
| | 244 | var end = matchBookmark.End ; |
| | 245 | if ( start[ start.length - 1 ] == 0 ) |
| | 246 | startOk = true ; |
| | 247 | else |
| | 248 | { |
| | 249 | var cursorBeforeStart = start.slice( 0, start.length - 1 ) ; |
| | 250 | cursorBeforeStart.push( start[ start.length - 1 ] - 1 ) ; |
| | 251 | var dataBeforeStart = GetData( cursorBeforeStart ) ; |
| | 252 | if ( dataBeforeStart == null || dataBeforeStart.charAt == undefined ) |
| | 253 | startOk = true ; |
| | 254 | else if ( CheckIsWhitespace( dataBeforeStart ) ) |
| | 255 | startOk = true ; |
| | 256 | } |
| | 257 | |
| | 258 | // this is already one character beyond the last char, no need to move |
| | 259 | var cursorAfterEnd = end ; |
| | 260 | var dataAfterEnd = GetData( cursorAfterEnd ); |
| | 261 | if ( dataAfterEnd == null || dataAfterEnd.charAt == undefined ) |
| | 262 | endOk = true ; |
| | 263 | else if ( CheckIsWhitespace( dataAfterEnd ) ) |
| | 264 | endOk = true ; |
| | 265 | |
| | 266 | if ( startOk && endOk ) |
| | 267 | break ; |
| | 268 | else |
| | 269 | matcher.Reset() ; |
| | 270 | } |
| | 271 | else |
| | 272 | break ; |
| | 273 | } |
| | 274 | } |
| | 275 | |
| | 276 | // Perform DFS across the document, until we've reached the end. |
| | 277 | cursor = NextPosition( cursor ) ; |
| | 278 | if ( cursor == null ) |
| | 279 | break; |
| | 280 | } |
| | 281 | |
| | 282 | // If we've found a match, select the match. |
| | 283 | if ( matchState == KMP_MATCHED ) |
| | 284 | { |
| | 285 | var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ; |
| | 286 | range.MoveToBookmark2( matchBookmark ) ; |
| | 287 | range.Select() ; |
| | 288 | return true; |
| | 289 | } |
| | 290 | else |
| | 291 | return false; |
| 102 | | var oRegex = new RegExp( GetRegexExpr(), GetCase() ) ; |
| 103 | | if ( !ReplaceTextNodes( oEditor.FCK.EditorDocument.body, oRegex, GetReplacement(), false, false ) ) |
| | 296 | var selection = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ; |
| | 297 | selection.MoveToSelection() ; |
| | 298 | |
| | 299 | if ( selection.CheckIsCollapsed() ) |
| | 300 | { |
| | 301 | if (! Find() ) |
| | 302 | alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ; |
| | 303 | } |
| | 304 | else |
| | 305 | { |
| | 306 | oEditor.FCKUndo.SaveUndoStep() ; |
| | 307 | selection.DeleteContents() ; |
| | 308 | selection.InsertNode( oEditor.FCK.EditorDocument.createTextNode( GetReplaceString() ) ) ; |
| | 309 | selection.Collapse( false ) ; |
| | 310 | selection.Select() ; |
| | 311 | oEditor.FCK.EditorDocument.body.normalize() ; |
| | 312 | } |
| | 313 | } |
| | 314 | |
| | 315 | function ReplaceAll() |
| | 316 | { |
| | 317 | oEditor.FCKUndo.SaveUndoStep() ; |
| | 318 | |
| | 319 | var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ; |
| | 320 | range.SetStart( oEditor.FCK.EditorDocument.body, 1 ) ; |
| | 321 | range.SetEnd( oEditor.FCK.EditorDocument.body, 1 ) ; |
| | 322 | range.Collapse( true ) ; |
| | 323 | range.Select() ; |
| | 324 | var replaceCount = 0 ; |
| | 325 | |
| | 326 | while ( Find() ) |
| | 327 | { |
| | 328 | range.MoveToSelection() ; |
| | 329 | range.DeleteContents() ; |
| | 330 | range.InsertNode( oEditor.FCK.EditorDocument.createTextNode( GetReplaceString() ) ) ; |
| | 331 | range.Collapse( false ) ; |
| | 332 | range.Select() ; |
| | 333 | replaceCount++ ; |
| | 334 | } |
| | 335 | if ( replaceCount == 0 ) |