]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/highlight/highlight.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / highlight / highlight.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('highlight-base', function(Y) {
9
10 /**
11  * Provides methods for highlighting strings within other strings by wrapping
12  * them in HTML.
13  *
14  * @module highlight
15  * @since 3.3.0
16  */
17
18 /**
19  * <p>
20  * Provides methods for highlighting strings within other strings by wrapping
21  * them in HTML.
22  * </p>
23  *
24  * <p>
25  * The highlight methods first escape any special HTML characters in the input
26  * strings and then highlight the appropriate substrings by wrapping them in a
27  * <code>&lt;b class="yui3-highlight"&gt;&lt;/b&gt;</code> element. The
28  * <code>&lt;b&gt;</code> element is used rather than
29  * <code>&lt;strong&gt;</code> in accordance with HTML5's definition of
30  * <code>&lt;b&gt;</code> as being purely presentational, which is exactly what
31  * highlighting is.
32  * </p>
33  *
34  * @module highlight
35  * @submodule highlight-base
36  * @class Highlight
37  * @static
38  */
39
40 var YArray    = Y.Array,
41     Escape    = Y.Escape,
42     WordBreak = Y.Text.WordBreak,
43
44     isArray = Y.Lang.isArray,
45
46     EMPTY_OBJECT = {},
47
48     // Regex string that captures zero or one unclosed HTML entities. Used in
49     // the static regex template properties below. The entity matching is
50     // intentionally loose here, since there's a world of complexity involved in
51     // doing strict matching for this use case.
52     UNCLOSED_ENTITY = '(&[^;\\s]*)?',
53
54 Highlight = {
55     // -- Protected Static Properties ------------------------------------------
56
57     /**
58      * <p>
59      * Regular expression template for highlighting a match that occurs anywhere
60      * in a string. The placeholder <code>%needles</code> will be replaced with
61      * a list of needles to match, joined by <code>|</code> characters.
62      * </p>
63      *
64      * <p>
65      * This regex should have two capturing subpatterns: the first should match
66      * an unclosed HTML entity (e.g. "&amp" without a ";" at the end) 0 or 1
67      * times; the second should contain the <code>%needles</code> placeholder.
68      * The first subpattern match is used to emulate a negative lookbehind
69      * assertion, in order to prevent highlighting inside HTML entities.
70      * </p>
71      *
72      * @property _REGEX
73      * @type {String}
74      * @protected
75      * @static
76      * @final
77      */
78     _REGEX: UNCLOSED_ENTITY + '(%needles)',
79
80     /**
81      * Regex replacer function or string for normal matches.
82      *
83      * @property _REPLACER
84      * @type {Function|String}
85      * @protected
86      * @static
87      * @final
88      */
89     _REPLACER: function (match, p1, p2) {
90          // Mimicking a negative lookbehind assertion to prevent matches inside
91          // HTML entities. Hat tip to Steven Levithan for the technique:
92          // http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript
93          return p1 && !(/\s/).test(p2) ? match :
94                     Highlight._TEMPLATE.replace(/\{s\}/g, p2);
95      },
96
97     /**
98      * <p>
99      * Regular expression template for highlighting start-of-string matches
100      * (i.e., only matches that occur at the beginning of a string). The
101      * placeholder <code>%needles</code> will be replaced with a list of needles
102      * to match, joined by <code>|</code> characters.
103      * </p>
104      *
105      * <p>
106      * See <code>_REGEX</code> for a description of the capturing subpatterns
107      * this regex should contain.
108      * </p>
109      *
110      * @property _START_REGEX
111      * @type {String}
112      * @protected
113      * @static
114      * @final
115      */
116     _START_REGEX: '^' + UNCLOSED_ENTITY + '(%needles)',
117
118     /**
119      * Highlight template which will be used as a replacement for matched
120      * substrings. The placeholder <code>{s}</code> will be replaced with the
121      * matched substring.
122      *
123      * @property _TEMPLATE
124      * @type {String}
125      * @default '<b class="yui3-highlight">{s}</b>'
126      * @protected
127      * @static
128      * @final
129      */
130     _TEMPLATE: '<b class="yui3-highlight">{s}</b>',
131
132     // -- Public Static Methods ------------------------------------------------
133
134     /**
135      * Highlights all occurrences in the <em>haystack</em> string of the items
136      * in the <em>needles</em> array, regardless of where they occur. The
137      * returned string will have all HTML characters escaped except for the
138      * highlighting markup.
139      *
140      * @method all
141      * @param {String} haystack String to apply highlighting to.
142      * @param {String|Array} needles String or array of strings that should be
143      *   highlighted.
144      * @param {Object} options (optional) Options object, which may contain
145      *   zero or more of the following properties:
146      *
147      * <dl>
148      *   <dt>caseSensitive (Boolean)</dt>
149      *   <dd>
150      *     If <code>true</code>, matching will be case-sensitive. Default is
151      *     <code>false</code>.
152      *   </dd>
153      *
154      *   <dt>startsWith (Boolean)<dt>
155      *   <dd>
156      *     By default, needles are highlighted wherever they appear in the
157      *     haystack. If <code>startsWith</code> is <code>true</code>, matches
158      *     must be anchored to the beginning of the string.
159      *   </dd>
160      * </dl>
161      *
162      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
163      * @static
164      */
165     all: function (haystack, needles, options) {
166         var i, len, regex, replacer;
167
168         if (!options) {
169             options = EMPTY_OBJECT;
170         }
171
172         // TODO: document options.replacer
173         regex    = options.startsWith ? Highlight._START_REGEX : Highlight._REGEX;
174         replacer = options.replacer || Highlight._REPLACER;
175
176         // Create a local copy of needles so we can safely modify it in the next
177         // step.
178         needles = isArray(needles) ? needles.concat() : [needles];
179
180         // Escape HTML characters and special regular expression characters in
181         // the needles so they can be used in a regex and matched against the
182         // escaped haystack.
183         for (i = 0, len = needles.length; i < len; ++i) {
184             needles[i] = Escape.regex(Escape.html(needles[i]));
185         }
186
187         // Escape HTML characters in the haystack to prevent HTML injection.
188         haystack = Escape.html(haystack);
189
190         return haystack.replace(
191             new RegExp(
192                 regex.replace('%needles', needles.join('|')),
193                 options.caseSensitive ? 'g' : 'gi'
194             ),
195             replacer
196         );
197     },
198
199     /**
200      * Same as <code>all()</code>, but case-sensitive by default.
201      *
202      * @method allCase
203      * @param {String} haystack String to apply highlighting to.
204      * @param {String|Array} needles String or array of strings that should be
205      *   highlighted.
206      * @param {Object} options (optional) Options object. See <code>all()</code>
207      *   for details.
208      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
209      * @static
210      */
211     allCase: function (haystack, needles, options) {
212         return Highlight.all(haystack, needles,
213                 Y.merge(options || EMPTY_OBJECT, {caseSensitive: true}));
214     },
215
216     /**
217      * Highlights <em>needles</em> that occur at the start of <em>haystack</em>.
218      * The returned string will have all HTML characters escaped except for the
219      * highlighting markup.
220      *
221      * @method start
222      * @param {String} haystack String to apply highlighting to.
223      * @param {String|Array} needles String or array of strings that should be
224      *   highlighted.
225      * @param {Object} options (optional) Options object, which may contain
226      *   zero or more of the following properties:
227      *
228      * <dl>
229      *   <dt>caseSensitive (Boolean)</dt>
230      *   <dd>
231      *     If <code>true</code>, matching will be case-sensitive. Default is
232      *     <code>false</code>.
233      *   </dd>
234      * </dl>
235      *
236      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
237      * @static
238      */
239     start: function (haystack, needles, options) {
240         return Highlight.all(haystack, needles,
241                 Y.merge(options || EMPTY_OBJECT, {startsWith: true}));
242     },
243
244     /**
245      * Same as <code>start()</code>, but case-sensitive by default.
246      *
247      * @method startCase
248      * @param {String} haystack String to apply highlighting to.
249      * @param {String|Array} needles String or array of strings that should be
250      *   highlighted.
251      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
252      * @static
253      */
254     startCase: function (haystack, needles) {
255         // No options passthru for now, since it would be redundant. If start()
256         // ever supports more options than caseSensitive, then we'll start
257         // passing the options through.
258         return Highlight.start(haystack, needles, {caseSensitive: true});
259     },
260
261     /**
262      * Highlights complete words in the <em>haystack</em> string that are also
263      * in the <em>needles</em> array. The returned string will have all HTML
264      * characters escaped except for the highlighting markup.
265      *
266      * @method words
267      * @param {String} haystack String to apply highlighting to.
268      * @param {String|Array} needles String or array of strings containing words
269      *   that should be highlighted. If a string is passed, it will be split
270      *   into words; if an array is passed, it is assumed to have already been
271      *   split.
272      * @param {Object} options (optional) Options object, which may contain
273      *   zero or more of the following properties:
274      *
275      * <dl>
276      *   <dt>caseSensitive (Boolean)</dt>
277      *   <dd>
278      *     If <code>true</code>, matching will be case-sensitive. Default is
279      *     <code>false</code>.
280      *   </dd>
281      * </dl>
282      *
283      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
284      * @static
285      */
286     words: function (haystack, needles, options) {
287         var caseSensitive,
288             mapper,
289             template = Highlight._TEMPLATE,
290             words;
291
292         if (!options) {
293             options = EMPTY_OBJECT;
294         }
295
296         caseSensitive = !!options.caseSensitive;
297
298         // Convert needles to a hash for faster lookups.
299         needles = YArray.hash(
300             isArray(needles) ? needles : WordBreak.getUniqueWords(needles, {
301                 ignoreCase: !caseSensitive
302             })
303         );
304
305         // The default word mapping function can be overridden with a custom
306         // one. This is used to implement accent-folded highlighting in the
307         // highlight-accentfold module.
308         mapper = options.mapper || function (word, needles) {
309             if (needles.hasOwnProperty(caseSensitive ? word : word.toLowerCase())) {
310                 return template.replace(/\{s\}/g, Escape.html(word));
311             }
312
313             return Escape.html(word);
314         };
315
316         // Split the haystack into an array of words, including punctuation and
317         // whitespace so we can rebuild the string later.
318         words = WordBreak.getWords(haystack, {
319             includePunctuation: true,
320             includeWhitespace : true
321         });
322
323         return YArray.map(words, function (word) {
324             return mapper(word, needles);
325         }).join('');
326     },
327
328     /**
329      * Same as <code>words()</code>, but case-sensitive by default.
330      *
331      * @method wordsCase
332      * @param {String} haystack String to apply highlighting to.
333      * @param {String|Array} needles String or array of strings containing words
334      *   that should be highlighted. If a string is passed, it will be split
335      *   into words; if an array is passed, it is assumed to have already been
336      *   split.
337      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
338      * @static
339      */
340     wordsCase: function (haystack, needles) {
341         // No options passthru for now, since it would be redundant. If words()
342         // ever supports more options than caseSensitive, then we'll start
343         // passing the options through.
344         return Highlight.words(haystack, needles, {caseSensitive: true});
345     }
346 };
347
348 Y.Highlight = Highlight;
349
350
351 }, '3.3.0' ,{requires:['array-extras', 'escape', 'text-wordbreak']});
352 YUI.add('highlight-accentfold', function(Y) {
353
354 /**
355  * Adds accent-folding highlighters to <code>Y.Highlight</code>.
356  *
357  * @module highlight
358  * @submodule highlight-accentfold
359  */
360
361 /**
362  * @class Highlight
363  * @static
364  */
365
366 var AccentFold = Y.Text.AccentFold,
367     Escape     = Y.Escape,
368
369     EMPTY_OBJECT = {},
370
371 Highlight = Y.mix(Y.Highlight, {
372     // -- Public Static Methods ------------------------------------------------
373
374     /**
375      * Accent-folding version of <code>all()</code>.
376      *
377      * @method allFold
378      * @param {String} haystack String to apply highlighting to.
379      * @param {String|Array} needles String or array of strings that should be
380      *   highlighted.
381      * @param {Object} options (optional) Options object, which may contain
382      *   zero or more of the following properties:
383      *
384      * <dl>
385      *   <dt>startsWith (Boolean)<dt>
386      *   <dd>
387      *     By default, needles are highlighted wherever they appear in the
388      *     haystack. If <code>startsWith</code> is <code>true</code>, matches
389      *     must be anchored to the beginning of the string.
390      *   </dd>
391      * </dl>
392      *
393      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
394      * @static
395      */
396     allFold: function (haystack, needles, options) {
397         var template = Highlight._TEMPLATE,
398             result   = [],
399             startPos = 0;
400
401         options = Y.merge({
402             // While the highlight regex operates on the accent-folded strings,
403             // this replacer will highlight the matched positions in the
404             // original string.
405             //
406             // Note: this implementation doesn't handle multi-character folds,
407             // like "æ" -> "ae". Doing so correctly would be prohibitively
408             // expensive both in terms of code size and runtime performance, so
409             // I've chosen to take the pragmatic route and just not do it at
410             // all. This is one of many reasons why accent folding is best done
411             // on the server.
412             replacer: function (match, p1, foldedNeedle, pos) {
413                 var len;
414
415                 // Ignore matches inside HTML entities.
416                 if (p1 && !(/\s/).test(foldedNeedle)) {
417                     return match;
418                 }
419
420                 len = foldedNeedle.length;
421
422                 result.push(haystack.substring(startPos, pos) +
423                         template.replace(/\{s\}/g, haystack.substr(pos, len)));
424
425                 startPos = pos + len;
426             }
427         }, options || EMPTY_OBJECT);
428
429         // Run the highlighter on the folded strings. We don't care about the
430         // output; our replacer function will build the canonical highlighted
431         // string, with original accented characters.
432         Highlight.all(AccentFold.fold(haystack), AccentFold.fold(needles),
433                 options);
434
435         // Tack on the remainder of the haystack that wasn't highlighted, if
436         // any.
437         if (startPos < haystack.length - 1) {
438             result.push(haystack.substr(startPos));
439         }
440
441         return result.join('');
442     },
443
444     /**
445      * Accent-folding version of <code>start()</code>.
446      *
447      * @method startFold
448      * @param {String} haystack String to apply highlighting to.
449      * @param {String|Array} needles String or array of strings that should be
450      *   highlighted.
451      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
452      * @static
453      */
454     startFold: function (haystack, needles) {
455         return Highlight.allFold(haystack, needles, {startsWith: true});
456     },
457
458     /**
459      * Accent-folding version of <code>words()</code>.
460      *
461      * @method wordsFold
462      * @param {String} haystack String to apply highlighting to.
463      * @param {String|Array} needles String or array of strings containing words
464      *   that should be highlighted. If a string is passed, it will be split
465      *   into words; if an array is passed, it is assumed to have already been
466      *   split.
467      * @return {String} Escaped and highlighted copy of <em>haystack</em>.
468      * @static
469      */
470     wordsFold: function (haystack, needles) {
471         var template = Highlight._TEMPLATE;
472
473         return Highlight.words(haystack, AccentFold.fold(needles), {
474             mapper: function (word, needles) {
475                 if (needles.hasOwnProperty(AccentFold.fold(word))) {
476                     return template.replace(/\{s\}/g, Escape.html(word));
477                 }
478
479                 return Escape.html(word);
480             }
481         });
482     }
483 });
484
485
486 }, '3.3.0' ,{requires:['highlight-base', 'text-accentfold']});
487
488
489 YUI.add('highlight', function(Y){}, '3.3.0' ,{use:['highlight-base', 'highlight-accentfold']});
490