1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2<!--
3 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
4 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
5 *
6 * == BEGIN LICENSE ==
7 *
8 * Licensed under the terms of any of the following licenses at your
9 * choice:
10 *
11 *  - GNU General Public License Version 2 or later (the "GPL")
12 *    http://www.gnu.org/licenses/gpl.html
13 *
14 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
15 *    http://www.gnu.org/licenses/lgpl.html
16 *
17 *  - Mozilla Public License Version 1.1 or later (the "MPL")
18 *    http://www.mozilla.org/MPL/MPL-1.1.html
19 *
20 * == END LICENSE ==
21 *
22 * "Find" and "Replace" dialog box window.
23-->
24<html xmlns="http://www.w3.org/1999/xhtml">
25<head>
26	<title></title>
27	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
28	<meta content="noindex, nofollow" name="robots" />
29	<script src="common/fck_dialog_common.js" type="text/javascript"></script>
30	<script type="text/javascript">
31
32var oEditor = window.parent.InnerDialogLoaded() ;
33var FCKLang = oEditor.FCKLang ;
34
35window.parent.AddTab( 'Find', FCKLang.DlgFindTitle ) ;
36window.parent.AddTab( 'Replace', FCKLang.DlgReplaceTitle ) ;
37var idMap = {} ;
38
39function OnDialogTabChange( tabCode )
40{
41	ShowE( 'divFind', ( tabCode == 'Find' ) ) ;
42	ShowE( 'divReplace', ( tabCode == 'Replace' ) ) ;
43	idMap['FindText'] = 'txtFind' + tabCode ;
44	idMap['CheckCase'] = 'chkCase' + tabCode ;
45	idMap['CheckWord'] = 'chkWord' + tabCode ;
46}
47
48function OnLoad()
49{
50	// First of all, translate the dialog box texts.
51	oEditor.FCKLanguageManager.TranslatePage( document ) ;
52
53	// Place the cursor at the start of document.
54	// This will be the starting point of our search.
55	var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
56	range.SetStart( oEditor.FCK.EditorDocument.body, 1 ) ;
57	range.SetEnd( oEditor.FCK.EditorDocument.body, 1 ) ;
58	range.Collapse( true ) ;
59	range.Select() ;
60
61	// Show the appropriate tab at startup.
62	if ( window.parent.name.search( 'Replace' ) == -1 )
63		window.parent.SetSelectedTab( 'Find' ) ;
64	else
65		window.parent.SetSelectedTab( 'Replace' ) ;
66}
67
68function btnStat(frm)
69{
70	document.getElementById('btnReplace').disabled =
71		document.getElementById('btnReplaceAll').disabled =
72			document.getElementById('btnFind').disabled =
73				( document.getElementById(idMap["FindText"]).value.length == 0 ) ;
74}
75
76function GetSelection()
77{
78	var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
79	range.MoveToSelection() ;
80	return range.CreateBookmark2() ;
81}
82
83function GetSearchString()
84{
85	return document.getElementById(idMap['FindText']).value ;
86}
87
88function GetReplaceString()
89{
90	return document.getElementById("txtReplace").value ;
91}
92
93function GetCheckCase()
94{
95	return !! ( document.getElementById(idMap['CheckCase']).checked ) ;
96}
97
98function GetMatchWord()
99{
100	return !! ( document.getElementById(idMap['CheckWord']).checked ) ;
101}
102
103// Get the data pointed to by a bookmark.
104function GetData( bookmark )
105{
106	var currentNode = oEditor.FCK.EditorDocument.documentElement;
107	for( var i = 0 ; i < bookmark.length ; i++ )
108	{
109		if ( currentNode.childNodes.length > bookmark[i] )
110			currentNode = currentNode.childNodes.item( bookmark[i] ) ;
111		else if ( currentNode.nodeType == 3 )	// text node
112		{
113			var c = currentNode.nodeValue.charAt( bookmark[i] ) ;
114			if ( i == bookmark.length - 1 )
115				return c != "" ? c : null ;
116			else
117				return null ;
118		}
119		else
120			return null;
121	}
122	return currentNode ;
123}
124
125// With this function, we can treat the bookmark as an iterator for DFS.
126function NextPosition( bookmark )
127{
128	// See if there's anything further down the tree.
129	var next = bookmark.concat( [0] ) ;
130
131	if ( GetData( next ) != null )
132		return next ;
133
134	// Nothing down there? See if there's anything next to me.
135	var next = bookmark.slice( 0, bookmark.length - 1 ).concat( [ bookmark[ bookmark.length - 1 ] + 1 ] ) ;
136	if ( GetData( next ) != null )
137		return next ;
138
139	// Nothing even next to me? See if there's anything next to my ancestors.
140	for ( var i = bookmark.length - 1 ; i > 0 ; i-- )
141	{
142		var next = bookmark.slice( 0, i - 1 ).concat( [ bookmark[ i - 1 ] + 1 ] ) ;
143		if ( GetData( next ) != null )
144			return next ;
145	}
146
147	// There's absolutely nothing left to walk, return null.
148	return null ;
149}
150
151// Is this character a unicode whitespace?
152// Reference: http://unicode.org/Public/UNIDATA/PropList.txt
153function CheckIsWhitespace( c )
154{
155	var code = c.charCodeAt( 0 );
156	if ( code >= 9 && code <= 0xd )
157		return true;
158	if ( code >= 0x2000 && code <= 0x200a )
159		return true;
160	switch ( code )
161	{
162		case 0x20:
163		case 0x85:
164		case 0xa0:
165		case 0x1680:
166		case 0x180e:
167		case 0x2028:
168		case 0x2029:
169		case 0x202f:
170		case 0x205f:
171		case 0x3000:
172			return true;
173		default:
174			return false;
175	}
176}
177
178// Knuth-Morris-Pratt Algorithm for stream input
179KMP_NOMATCH = 0 ;
180KMP_ADVANCED = 1 ;
181KMP_MATCHED = 2 ;
182function KmpMatch( pattern, ignoreCase )
183{
184	var overlap = [ -1 ] ;
185	for ( var i = 0 ; i < pattern.length ; i++ )
186	{
187		overlap.push( overlap[i] + 1 ) ;
188		while ( overlap[ i + 1 ] > 0 && pattern.charAt( i ) != pattern.charAt( overlap[ i + 1 ] - 1 ) )
189			overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1 ;
190	}
191	this._Overlap = overlap ;
192	this._State = 0 ;
193	this._IgnoreCase = ( ignoreCase === true ) ;
194	if ( ignoreCase )
195		this.Pattern = pattern.toLowerCase();
196	else
197		this.Pattern = pattern ;
198}
199KmpMatch.prototype = {
200	"FeedCharacter" : function( c )
201	{
202		if ( this._IgnoreCase )
203			c = c.toLowerCase();
204
205		while ( true )
206		{
207			if ( c == this.Pattern.charAt( this._State ) )
208			{
209				this._State++ ;
210				if ( this._State == this.Pattern.length )
211				{
212					// found a match, start over, don't care about partial matches involving the current match
213					this._State = 0;
214					return KMP_MATCHED;
215				}
216				return KMP_ADVANCED;
217			}
218			else if ( this._State == 0 )
219				return KMP_NOMATCH;
220			else
221				this._State = this._Overlap[ this._State ];
222		}
223
224		return null ;
225	},
226	"Reset" : function()
227	{
228		this._State = 0 ;
229	}
230};
231
232function _Find()
233{
234	// Start from the end of the current selection.
235	var matcher = new KmpMatch( GetSearchString(), ! GetCheckCase() ) ;
236	var cursor = GetSelection().End ;
237	var matchState = KMP_NOMATCH ;
238	var matchBookmark = null ;
239
240	// Match finding.
241	while ( true )
242	{
243		// Perform KMP stream matching.
244		//	- Reset KMP matcher if we encountered a block element.
245		var data = GetData( cursor ) ;
246		if ( data )
247		{
248			if ( data.tagName )
249			{
250				if ( oEditor.FCKListsLib.BlockElements[ data.tagName.toLowerCase() ] )
251					matcher.Reset();
252			}
253			else if ( data.charAt != undefined )
254			{
255				matchState = matcher.FeedCharacter(data) ;
256
257				if ( matchState == KMP_NOMATCH )
258					matchBookmark = null ;
259				else if ( matchState == KMP_ADVANCED && matchBookmark == null )
260					matchBookmark = { Start : cursor.concat( [] ) } ;
261				else if ( matchState == KMP_MATCHED )
262				{
263					if ( matchBookmark == null )
264						matchBookmark = { Start : cursor.concat( [] ) } ;
265					matchBookmark.End = cursor.concat( [] ) ;
266					matchBookmark.End[ matchBookmark.End.length - 1 ]++;
267
268					// Wait, do we have to match a whole word?
269					if ( GetMatchWord() )
270					{
271						var startOk = false ;
272						var endOk = false ;
273						var start = matchBookmark.Start ;
274						var end = matchBookmark.End ;
275						if ( start[ start.length - 1 ] == 0 )
276							startOk = true ;
277						else
278						{
279							var cursorBeforeStart = start.slice( 0, start.length - 1 ) ;
280							cursorBeforeStart.push( start[ start.length - 1 ] - 1 ) ;
281							var dataBeforeStart = GetData( cursorBeforeStart ) ;
282							if ( dataBeforeStart == null || dataBeforeStart.charAt == undefined )
283								startOk = true ;
284							else if ( CheckIsWhitespace( dataBeforeStart ) )
285								startOk = true ;
286						}
287
288						// this is already one character beyond the last char, no need to move
289						var cursorAfterEnd = end ;
290						var dataAfterEnd = GetData( cursorAfterEnd );
291						if ( dataAfterEnd == null || dataAfterEnd.charAt == undefined )
292							endOk = true ;
293						else if ( CheckIsWhitespace( dataAfterEnd ) )
294							endOk = true ;
295
296						if ( startOk && endOk )
297							break ;
298						else
299							matcher.Reset() ;
300					}
301					else
302						break ;
303				}
304			}
305		}
306
307		// Perform DFS across the document, until we've reached the end.
308		cursor = NextPosition( cursor ) ;
309		if ( cursor == null )
310			break;
311	}
312
313	// If we've found a match, select the match.
314	if ( matchState == KMP_MATCHED )
315	{
316		var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
317		range.MoveToBookmark2( matchBookmark ) ;
318		range.Select() ;
319		var focus = range._Range.endContainer ;
320		while ( focus && focus.nodeType != 1 )
321			focus = focus.parentNode ;
322		if ( focus )
323		focus.scrollIntoView( false ) ;
324		return true;
325	}
326	else
327		return false;
328}
329
330function Find()
331{
332	var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
333	range.MoveToSelection() ;
334	range.Collapse( false ) ;
335	range.Select() ;
336
337	if ( ! _Find() )
338		alert( FCKLang.DlgFindNotFoundMsg ) ;
339}
340
341function Replace()
342{
343	var selection = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
344	selection.MoveToSelection() ;
345
346	if ( selection.CheckIsCollapsed() )
347	{
348		if (! _Find() )
349			alert( FCKLang.DlgFindNotFoundMsg ) ;
350	}
351	else
352	{
353		oEditor.FCKUndo.SaveUndoStep() ;
354		selection.DeleteContents() ;
355		selection.InsertNode( oEditor.FCK.EditorDocument.createTextNode( GetReplaceString() ) ) ;
356		selection.Collapse( false ) ;
357		selection.Select() ;
358	}
359}
360
361function ReplaceAll()
362{
363	oEditor.FCKUndo.SaveUndoStep() ;
364	var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
365
366	var replaceCount = 0 ;
367
368	while ( _Find() )
369	{
370		range.MoveToSelection() ;
371		range.DeleteContents() ;
372		range.InsertNode( oEditor.FCK.EditorDocument.createTextNode( GetReplaceString() ) ) ;
373		range.Collapse( false ) ;
374		range.Select() ;
375		replaceCount++ ;
376	}
377	if ( replaceCount == 0 )
378		alert( FCKLang.DlgFindNotFoundMsg ) ;
379	window.parent.Cancel() ;
380}
381	</script>
382</head>
383<body onload="OnLoad()" style="overflow: hidden">
384	<div id="divFind" style="display: none">
385		<table cellspacing="3" cellpadding="2" width="100%" border="0">
386			<tr>
387				<td nowrap="nowrap">
388					<label for="txtFindFind" fcklang="DlgReplaceFindLbl">
389						Find what:</label>
390				</td>
391				<td width="100%">
392					<input id="txtFindFind" onkeyup="btnStat(this.form)" style="width: 100%" tabindex="1"
393						type="text" />
394				</td>
395				<td>
396					<input id="btnFind" style="width: 80px" disabled="disabled" onclick="Find();"
397						type="button" value="Find" fcklang="DlgFindFindBtn" />
398				</td>
399			</tr>
400			<tr>
401				<td valign="bottom" colspan="3">
402					&nbsp;<input id="chkCaseFind" tabindex="3" type="checkbox" /><label for="chkCaseFind" fcklang="DlgReplaceCaseChk">Match
403						case</label>
404					<br />
405					&nbsp;<input id="chkWordFind" tabindex="4" type="checkbox" /><label for="chkWordFind" fcklang="DlgReplaceWordChk">Match
406						whole word</label>
407				</td>
408			</tr>
409		</table>
410	</div>
411	<div id="divReplace" style="display:none">
412		<table cellspacing="3" cellpadding="2" width="100%" border="0">
413			<tr>
414				<td nowrap="nowrap">
415					<label for="txtFindReplace" fcklang="DlgReplaceFindLbl">
416						Find what:</label>
417				</td>
418				<td width="100%">
419					<input id="txtFindReplace" onkeyup="btnStat(this.form)" style="width: 100%" tabindex="1"
420						type="text" />
421				</td>
422				<td>
423					<input id="btnReplace" style="width: 80px" disabled="disabled" onclick="Replace();"
424						type="button" value="Replace" fcklang="DlgReplaceReplaceBtn" />
425				</td>
426			</tr>
427			<tr>
428				<td valign="top" nowrap="nowrap">
429					<label for="txtReplace" fcklang="DlgReplaceReplaceLbl">
430						Replace with:</label>
431				</td>
432				<td valign="top">
433					<input id="txtReplace" style="width: 100%" tabindex="2" type="text" />
434				</td>
435				<td>
436					<input id="btnReplaceAll" style="width: 80px" disabled="disabled" onclick="ReplaceAll()" type="button"
437						value="Replace All" fcklang="DlgReplaceReplAllBtn" />
438				</td>
439			</tr>
440			<tr>
441				<td valign="bottom" colspan="3">
442					&nbsp;<input id="chkCaseReplace" tabindex="3" type="checkbox" /><label for="chkCaseReplace" fcklang="DlgReplaceCaseChk">Match
443						case</label>
444					<br />
445					&nbsp;<input id="chkWordReplace" tabindex="4" type="checkbox" /><label for="chkWordReplace" fcklang="DlgReplaceWordChk">Match
446						whole word</label>
447				</td>
448			</tr>
449		</table>
450	</div>
451</body>
452</html>
453