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-2009 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 dialog	= window.parent ;
33var oEditor = dialog.InnerDialogLoaded() ;
34var dialogArguments = dialog.Args() ;
35
36var FCKLang = oEditor.FCKLang ;
37var FCKDomTools = oEditor.FCKDomTools ;
38var FCKDomRange = oEditor.FCKDomRange ;
39var FCKListsLib = oEditor.FCKListsLib ;
40var FCKTools = oEditor.FCKTools ;
41var EditorDocument = oEditor.FCK.EditorDocument ;
42var HighlightStyle = oEditor.FCKStyles.GetStyle( '_FCK_SelectionHighlight' )  ;
43
44dialog.AddTab( 'Find', FCKLang.DlgFindTitle ) ;
45dialog.AddTab( 'Replace', FCKLang.DlgReplaceTitle ) ;
46var idMap = {} ;
47
48function OnDialogTabChange( tabCode )
49{
50	ShowE( 'divFind', ( tabCode == 'Find' ) ) ;
51	ShowE( 'divReplace', ( tabCode == 'Replace' ) ) ;
52	idMap['FindText'] = 'txtFind' + tabCode ;
53	idMap['CheckCase'] = 'chkCase' + tabCode ;
54	idMap['CheckWord'] = 'chkWord' + tabCode ;
55
56	if ( tabCode == 'Replace' )
57		dialog.SetAutoSize( true ) ;
58}
59
60GetNextNonEmptyTextNode = function( node, stopNode )
61{
62	while ( ( node = FCKDomTools.GetNextSourceNode( node, false, 3, stopNode ) ) && node && node.length < 1 )
63		1 ;
64	return node ;
65}
66
67CharacterCursor = function( arg )
68{
69	if ( arg.nodeType && arg.nodeType == 9 )
70	{
71		this._textNode = GetNextNonEmptyTextNode( arg.body, arg.documentElement ) ;
72		this._offset = 0 ;
73		this._doc = arg ;
74	}
75	else
76	{
77		this._textNode = arguments[0] ;
78		this._offset = arguments[1] ;
79		this._doc = FCKTools.GetElementDocument( arguments[0] ) ;
80	}
81}
82CharacterCursor.prototype =
83{
84	GetCharacter : function()
85	{
86		return ( this._textNode && this._textNode.nodeValue.charAt( this._offset ) ) || null ;
87	},
88
89	// Non-normalized.
90	GetTextNode : function()
91	{
92		return this._textNode ;
93	},
94
95	// Non-normalized.
96	GetIndex : function()
97	{
98		return this._offset ;
99	},
100
101	// Return value means whehther we've crossed a line break or a paragraph boundary.
102	MoveNext : function()
103	{
104		if ( this._offset < this._textNode.length - 1 )
105		{
106			this._offset++ ;
107			return false ;
108		}
109
110		var crossed = false ;
111		var curNode = this._textNode ;
112		while ( ( curNode = FCKDomTools.GetNextSourceNode( curNode ) )
113				&& curNode && ( curNode.nodeType != 3 || curNode.length < 1 ) )
114		{
115			var tag = curNode.nodeName.toLowerCase() ;
116			if ( FCKListsLib.BlockElements[tag] || tag == 'br' )
117				crossed = true ;
118		}
119
120		this._textNode = curNode ;
121		this._offset = 0 ;
122		return crossed ;
123	},
124
125	// Return value means whehther we've crossed a line break or a paragraph boundary.
126	MoveBack : function()
127	{
128		if ( this._offset > 0 && this._textNode.length > 0 )
129		{
130			this._offset = Math.min( this._offset - 1, this._textNode.length - 1 ) ;
131			return false ;
132		}
133
134		var crossed = false ;
135		var curNode = this._textNode ;
136		while ( ( curNode = FCKDomTools.GetPreviousSourceNode( curNode ) )
137				&& curNode && ( curNode.nodeType != 3 || curNode.length < 1 ) )
138		{
139			var tag = curNode.nodeName.toLowerCase() ;
140			if ( FCKListsLib.BlockElements[tag] || tag == 'br' )
141				crossed = true ;
142		}
143
144		this._textNode = curNode ;
145		this._offset = curNode && curNode.length - 1 ;
146		return crossed ;
147	},
148
149	Clone : function()
150	{
151		return new CharacterCursor( this._textNode, this._offset ) ;
152	}
153} ;
154
155CharacterRange = function( initCursor, maxLength )
156{
157	this._cursors = initCursor.push ? initCursor : [initCursor] ;
158	this._maxLength = maxLength ;
159	this._highlightRange = null ;
160}
161CharacterRange.prototype =
162{
163	ToDomRange : function()
164	{
165		var firstCursor = this._cursors[0] ;
166		var lastCursor = this._cursors[ this._cursors.length - 1 ] ;
167		var domRange = new FCKDomRange( FCKTools.GetElementWindow( firstCursor.GetTextNode() ) ) ;
168		var w3cRange = domRange._Range = domRange.CreateRange() ;
169		w3cRange.setStart( firstCursor.GetTextNode(), firstCursor.GetIndex() ) ;
170		w3cRange.setEnd( lastCursor.GetTextNode(), lastCursor.GetIndex() + 1 ) ;
171		domRange._UpdateElementInfo() ;
172		return domRange ;
173	},
174
175	Highlight : function()
176	{
177		if ( this._cursors.length < 1 )
178			return ;
179
180		var domRange = this.ToDomRange() ;
181		HighlightStyle.ApplyToRange( domRange, false, true ) ;
182		this._highlightRange = domRange ;
183
184		var charRange = CharacterRange.CreateFromDomRange( domRange ) ;
185		var focusNode = domRange.StartNode ;
186		if ( focusNode.nodeType != 1 )
187			focusNode = focusNode.parentNode ;
188		FCKDomTools.ScrollIntoView( focusNode, false ) ;
189		this._cursors = charRange._cursors ;
190	},
191
192	RemoveHighlight : function()
193	{
194		if ( this._highlightRange )
195		{
196			HighlightStyle.RemoveFromRange( this._highlightRange, false, true ) ;
197			var charRange = CharacterRange.CreateFromDomRange( this._highlightRange ) ;
198			this._cursors = charRange._cursors ;
199			this._highlightRange = null ;
200		}
201	},
202
203	GetHighlightDomRange : function()
204	{
205		return this._highlightRange;
206	},
207
208	MoveNext : function()
209	{
210		var next = this._cursors[ this._cursors.length - 1 ].Clone() ;
211		var retval = next.MoveNext() ;
212		if ( retval )
213			this._cursors = [] ;
214		this._cursors.push( next ) ;
215		if ( this._cursors.length > this._maxLength )
216			this._cursors.shift() ;
217		return retval ;
218	},
219
220	MoveBack : function()
221	{
222		var prev = this._cursors[0].Clone() ;
223		var retval = prev.MoveBack() ;
224		if ( retval )
225			this._cursors = [] ;
226		this._cursors.unshift( prev ) ;
227		if ( this._cursors.length > this._maxLength )
228			this._cursors.pop() ;
229		return retval ;
230	},
231
232	GetEndCharacter : function()
233	{
234		if ( this._cursors.length < 1 )
235			return null ;
236		var retval = this._cursors[ this._cursors.length - 1 ].GetCharacter() ;
237		return retval ;
238	},
239
240	GetNextRange : function( len )
241	{
242		if ( this._cursors.length == 0 )
243			return null ;
244		var cur = this._cursors[ this._cursors.length - 1 ].Clone() ;
245		cur.MoveNext() ;
246		return new CharacterRange( cur, len ) ;
247	},
248
249	GetCursors : function()
250	{
251		return this._cursors ;
252	}
253} ;
254
255CharacterRange.CreateFromDomRange = function( domRange )
256{
257	var w3cRange = domRange._Range ;
258	var startContainer = w3cRange.startContainer ;
259	var endContainer = w3cRange.endContainer ;
260	var startTextNode, startIndex, endTextNode, endIndex ;
261
262	if ( startContainer.nodeType == 3 )
263	{
264		startTextNode = startContainer ;
265		startIndex = w3cRange.startOffset ;
266	}
267	else if ( domRange.StartNode.nodeType == 3 )
268	{
269		startTextNode = domRange.StartNode ;
270		startIndex = 0 ;
271	}
272	else
273	{
274		startTextNode = GetNextNonEmptyTextNode( domRange.StartNode, domRange.StartNode.parentNode ) ;
275		if ( !startTextNode )
276			return null ;
277		startIndex = 0 ;
278	}
279
280	if ( endContainer.nodeType == 3 && w3cRange.endOffset > 0 )
281	{
282		endTextNode = endContainer ;
283		endIndex = w3cRange.endOffset - 1 ;
284	}
285	else
286	{
287		endTextNode = domRange.EndNode ;
288		while ( endTextNode.nodeType != 3 )
289			endTextNode = endTextNode.lastChild ;
290		endIndex = endTextNode.length - 1 ;
291	}
292
293	var cursors = [] ;
294	var current = new CharacterCursor( startTextNode, startIndex ) ;
295	cursors.push( current ) ;
296	if ( !( current.GetTextNode() == endTextNode && current.GetIndex() == endIndex ) && !domRange.CheckIsEmpty() )
297	{
298		do
299		{
300			current = current.Clone() ;
301			current.MoveNext() ;
302			cursors.push( current ) ;
303		}
304		while ( !( current.GetTextNode() == endTextNode && current.GetIndex() == endIndex ) ) ;
305	}
306
307	return new CharacterRange( cursors, cursors.length ) ;
308}
309
310// Knuth-Morris-Pratt Algorithm for stream input
311KMP_NOMATCH = 0 ;
312KMP_ADVANCED = 1 ;
313KMP_MATCHED = 2 ;
314KmpMatch = function( pattern, ignoreCase )
315{
316	var overlap = [ -1 ] ;
317	for ( var i = 0 ; i < pattern.length ; i++ )
318	{
319		overlap.push( overlap[i] + 1 ) ;
320		while ( overlap[ i + 1 ] > 0 && pattern.charAt( i ) != pattern.charAt( overlap[ i + 1 ] - 1 ) )
321			overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1 ;
322	}
323	this._Overlap = overlap ;
324	this._State = 0 ;
325	this._IgnoreCase = ( ignoreCase === true ) ;
326	if ( ignoreCase )
327		this.Pattern = pattern.toLowerCase();
328	else
329		this.Pattern = pattern ;
330}
331KmpMatch.prototype = {
332	FeedCharacter : function( c )
333	{
334		if ( this._IgnoreCase )
335			c = c.toLowerCase();
336
337		while ( true )
338		{
339			if ( c == this.Pattern.charAt( this._State ) )
340			{
341				this._State++ ;
342				if ( this._State == this.Pattern.length )
343				{
344					// found a match, start over, don't care about partial matches involving the current match
345					this._State = 0;
346					return KMP_MATCHED;
347				}
348				return KMP_ADVANCED ;
349			}
350			else if ( this._State == 0 )
351				return KMP_NOMATCH;
352			else
353				this._State = this._Overlap[ this._State ];
354		}
355
356		return null ;
357	},
358
359	Reset : function()
360	{
361		this._State = 0 ;
362	}
363};
364
365// Place a range at the start of document.
366function OnLoad()
367{
368	// First of all, translate the dialog box texts.
369	oEditor.FCKLanguageManager.TranslatePage( document ) ;
370
371	// Show the appropriate tab at startup.
372	if ( dialogArguments.CustomValue == 'Find' )
373	{
374		dialog.SetSelectedTab( 'Find' ) ;
375		dialog.SetAutoSize( true ) ;
376	}
377	else
378		dialog.SetSelectedTab( 'Replace' ) ;
379
380	SelectField( 'txtFind' + dialogArguments.CustomValue ) ;
381}
382
383function btnStat()
384{
385	GetE('btnReplace').disabled =
386		GetE('btnReplaceAll').disabled =
387			GetE('btnFind').disabled =
388				( GetE(idMap["FindText"]).value.length == 0 ) ;
389}
390
391function btnStatDelayed()
392{
393	setTimeout( btnStat, 1 ) ;
394}
395
396function GetSearchString()
397{
398	return GetE(idMap['FindText']).value ;
399}
400
401function GetReplaceString()
402{
403	return GetE("txtReplace").value ;
404}
405
406function GetCheckCase()
407{
408	return !! ( GetE(idMap['CheckCase']).checked ) ;
409}
410
411function GetMatchWord()
412{
413	return !! ( GetE(idMap['CheckWord']).checked ) ;
414}
415
416/* Is this character a unicode whitespace or a punctuation mark?
417 * References:
418 * http://unicode.org/Public/UNIDATA/PropList.txt (whitespaces)
419 * http://php.chinaunix.net/manual/tw/ref.regex.php (punctuation marks)
420 */
421function CheckIsWordSeparator( c )
422{
423	if ( !c )
424		return true;
425	var code = c.charCodeAt( 0 );
426	if ( code >= 9 && code <= 0xd )
427		return true;
428	if ( code >= 0x2000 && code <= 0x200a )
429		return true;
430	switch ( code )
431	{
432		case 0x20:
433		case 0x85:
434		case 0xa0:
435		case 0x1680:
436		case 0x180e:
437		case 0x2028:
438		case 0x2029:
439		case 0x202f:
440		case 0x205f:
441		case 0x3000:
442			return true;
443		default:
444	}
445	return /[.,"'?!;:]/.test( c ) ;
446}
447
448FindRange = null ;
449function _Find()
450{
451	var searchString = GetSearchString() ;
452	if ( !FindRange )
453		FindRange = new CharacterRange( new CharacterCursor( EditorDocument ), searchString.length ) ;
454	else
455	{
456		FindRange.RemoveHighlight() ;
457		FindRange = FindRange.GetNextRange( searchString.length ) ;
458	}
459	var matcher = new KmpMatch( searchString, ! GetCheckCase() ) ;
460	var matchState = KMP_NOMATCH ;
461	var character = '%' ;
462
463	while ( character != null )
464	{
465		while ( ( character = FindRange.GetEndCharacter() ) )
466		{
467			matchState = matcher.FeedCharacter( character ) ;
468			if ( matchState == KMP_MATCHED )
469				break ;
470			if ( FindRange.MoveNext() )
471				matcher.Reset() ;
472		}
473
474		if ( matchState == KMP_MATCHED )
475		{
476			if ( GetMatchWord() )
477			{
478				var cursors = FindRange.GetCursors() ;
479				var head = cursors[ cursors.length - 1 ].Clone() ;
480				var tail = cursors[0].Clone() ;
481				if ( !head.MoveNext() && !CheckIsWordSeparator( head.GetCharacter() ) )
482					continue ;
483				if ( !tail.MoveBack() && !CheckIsWordSeparator( tail.GetCharacter() ) )
484					continue ;
485			}
486
487			FindRange.Highlight() ;
488			return true ;
489		}
490	}
491
492	FindRange = null ;
493	return false ;
494}
495
496function Find()
497{
498	if ( ! _Find() )
499		alert( FCKLang.DlgFindNotFoundMsg ) ;
500}
501
502function Replace()
503{
504	var saveUndoStep = function( selectRange )
505	{
506		var ieRange ;
507		if ( oEditor.FCKBrowserInfo.IsIE )
508			ieRange = document.selection.createRange() ;
509
510		selectRange.Select() ;
511		oEditor.FCKUndo.SaveUndoStep() ;
512		var cloneRange = selectRange.Clone() ;
513		cloneRange.Collapse( false ) ;
514		cloneRange.Select() ;
515
516		if ( ieRange )
517			setTimeout( function(){ ieRange.select() ; }, 1 ) ;
518	}
519
520	if ( FindRange && FindRange.GetHighlightDomRange() )
521	{
522		var range = FindRange.GetHighlightDomRange() ;
523		var bookmark = range.CreateBookmark() ;
524		FindRange.RemoveHighlight() ;
525		range.MoveToBookmark( bookmark ) ;
526
527		saveUndoStep( range ) ;
528		range.DeleteContents() ;
529		range.InsertNode( EditorDocument.createTextNode( GetReplaceString() ) ) ;
530		range._UpdateElementInfo() ;
531
532		FindRange = CharacterRange.CreateFromDomRange( range ) ;
533	}
534	else
535	{
536		if ( ! _Find() )
537		{
538			FindRange && FindRange.RemoveHighlight() ;
539			alert( FCKLang.DlgFindNotFoundMsg ) ;
540		}
541	}
542}
543
544function ReplaceAll()
545{
546	oEditor.FCKUndo.SaveUndoStep() ;
547	var replaceCount = 0 ;
548
549	while ( _Find() )
550	{
551		var range = FindRange.GetHighlightDomRange() ;
552		var bookmark = range.CreateBookmark() ;
553		FindRange.RemoveHighlight() ;
554		range.MoveToBookmark( bookmark) ;
555
556		range.DeleteContents() ;
557		range.InsertNode( EditorDocument.createTextNode( GetReplaceString() ) ) ;
558		range._UpdateElementInfo() ;
559
560		FindRange = CharacterRange.CreateFromDomRange( range ) ;
561		replaceCount++ ;
562	}
563	if ( replaceCount == 0 )
564	{
565		FindRange && FindRange.RemoveHighlight() ;
566		alert( FCKLang.DlgFindNotFoundMsg ) ;
567	}
568	dialog.Cancel() ;
569}
570
571window.onunload = function()
572{
573	if ( FindRange )
574	{
575		FindRange.RemoveHighlight() ;
576		FindRange.ToDomRange().Select() ;
577	}
578}
579	</script>
580</head>
581<body onload="OnLoad()" style="overflow: hidden">
582	<div id="divFind" style="display: none">
583		<table cellspacing="3" cellpadding="2" width="100%" border="0">
584			<tr>
585				<td nowrap="nowrap">
586					<label for="txtFindFind" fcklang="DlgReplaceFindLbl">
587						Find what:</label>
588				</td>
589				<td width="100%">
590					<input id="txtFindFind" onkeyup="btnStat()" oninput="btnStat()" onpaste="btnStatDelayed()" style="width: 100%" tabindex="1"
591						type="text" />
592				</td>
593				<td>
594					<input id="btnFind" style="width: 80px" disabled="disabled" onclick="Find();"
595						type="button" value="Find" fcklang="DlgFindFindBtn" />
596				</td>
597			</tr>
598			<tr>
599				<td valign="bottom" colspan="3">
600					&nbsp;<input id="chkCaseFind" tabindex="3" type="checkbox" /><label for="chkCaseFind" fcklang="DlgReplaceCaseChk">Match
601						case</label>
602					<br />
603					&nbsp;<input id="chkWordFind" tabindex="4" type="checkbox" /><label for="chkWordFind" fcklang="DlgReplaceWordChk">Match
604						whole word</label>
605				</td>
606			</tr>
607		</table>
608	</div>
609	<div id="divReplace" style="display:none">
610		<table cellspacing="3" cellpadding="2" width="100%" border="0">
611			<tr>
612				<td nowrap="nowrap">
613					<label for="txtFindReplace" fcklang="DlgReplaceFindLbl">
614						Find what:</label>
615				</td>
616				<td width="100%">
617					<input id="txtFindReplace" onkeyup="btnStat()" oninput="btnStat()" onpaste="btnStatDelayed()" style="width: 100%" tabindex="1"
618						type="text" />
619				</td>
620				<td>
621					<input id="btnReplace" style="width: 80px" disabled="disabled" onclick="Replace();"
622						type="button" value="Replace" fcklang="DlgReplaceReplaceBtn" />
623				</td>
624			</tr>
625			<tr>
626				<td valign="top" nowrap="nowrap">
627					<label for="txtReplace" fcklang="DlgReplaceReplaceLbl">
628						Replace with:</label>
629				</td>
630				<td valign="top">
631					<input id="txtReplace" style="width: 100%" tabindex="2" type="text" />
632				</td>
633				<td>
634					<input id="btnReplaceAll" style="width: 80px" disabled="disabled" onclick="ReplaceAll()" type="button"
635						value="Replace All" fcklang="DlgReplaceReplAllBtn" />
636				</td>
637			</tr>
638			<tr>
639				<td valign="bottom" colspan="3">
640					&nbsp;<input id="chkCaseReplace" tabindex="3" type="checkbox" /><label for="chkCaseReplace" fcklang="DlgReplaceCaseChk">Match
641						case</label>
642					<br />
643					&nbsp;<input id="chkWordReplace" tabindex="4" type="checkbox" /><label for="chkWordReplace" fcklang="DlgReplaceWordChk">Match
644						whole word</label>
645				</td>
646			</tr>
647		</table>
648	</div>
649</body>
650</html>
651