Show
Ignore:
Timestamp:
2007-07-05 10:15:32 (18 months ago)
Author:
martinkou
Message:

Fixed #802 : The program logic behind the replace dialog has been rewritten to be more closely resemble MS Word.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • FCKeditor/trunk/editor/dialog/fck_replace.html

    r132 r408  
    3535        // First of all, translate the dialog box texts 
    3636        oEditor.FCKLanguageManager.TranslatePage( document ) ; 
    37  
     37         
    3838        window.parent.SetAutoSize( true ) ; 
    39  
    40         oEditor.FCKUndo.SaveUndoStep() ; 
    4139} 
    4240 
     
    4846} 
    4947 
    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 ) 
     48function GetSelection() 
     49{ 
     50        var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ; 
     51        range.MoveToSelection() ; 
     52        return range.CreateBookmark2() ; 
     53} 
     54 
     55function GetSearchString() 
     56{ 
     57        return document.getElementById("txtFind").value ; 
     58} 
     59 
     60function GetReplaceString() 
     61{ 
     62        return document.getElementById("txtReplace").value ; 
     63} 
     64 
     65function GetCheckCase() 
     66{ 
     67        return !! ( document.getElementById("chkCase").checked ) ; 
     68} 
     69 
     70function GetMatchWord() 
     71{ 
     72        return !! ( document.getElementById("chkWord").checked ) ; 
     73} 
     74 
     75// Get the data pointed to by a bookmark. 
     76function 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. 
     98function 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 
     125function 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 
     151KMP_NOMATCH = 0 ; 
     152KMP_ADVANCED = 1 ; 
     153KMP_MATCHED = 2 ; 
     154function 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} 
     171KmpMatch.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 ] ) 
    59180                        { 
    60                                 oNode.nodeValue = sReplaced ; 
    61                                 if ( ! replaceAll ) 
    62                                         return true ; 
    63                                 hasFound = true ; 
     181                                this._State++ ; 
     182                                if ( this._State == this.Pattern.length ) 
     183                                { 
     184                                        // found a match, start over, don't care about partial matches involving the current match 
     185                                        this._State = 0; 
     186                                        return KMP_MATCHED; 
     187                                } 
     188                                return KMP_ADVANCED; 
    64189                        } 
    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 
     202function 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; 
    98292} 
    99293 
    100294function Replace() 
    101295{ 
    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 
     315function 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 ) 
    104336                alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ; 
    105 } 
    106  
    107 function ReplaceAll() 
    108 { 
    109         var oRegex = new RegExp( GetRegexExpr(), GetCase() + 'g' ) ; 
    110         if ( !ReplaceTextNodes( oEditor.FCK.EditorDocument.body, oRegex, GetReplacement(), true, false ) ) 
    111                 alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ; 
     337        else 
     338                oEditor.FCK.EditorDocument.body.normalize() ; 
    112339        window.parent.Cancel() ; 
    113340}