# Yahoo! UI Library

### highlight 3.3.0

Yahoo! UI Library > highlight > highlight-base.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * Provides methods for highlighting strings within other strings by wrapping
     * them in HTML.
     *
     * @module highlight
     * @since 3.3.0
     */

    /**
     * <p>
     * Provides methods for highlighting strings within other strings by wrapping
     * them in HTML.
     * </p>
     *
     * <p>
     * The highlight methods first escape any special HTML characters in the input
     * strings and then highlight the appropriate substrings by wrapping them in a
     * <code>&lt;b class="yui3-highlight"&gt;&lt;/b&gt;</code> element. The
     * <code>&lt;b&gt;</code> element is used rather than
     * <code>&lt;strong&gt;</code> in accordance with HTML5's definition of
     * <code>&lt;b&gt;</code> as being purely presentational, which is exactly what
     * highlighting is.
     * </p>
     *
     * @module highlight
     * @submodule highlight-base
     * @class Highlight
     * @static
     */

    var YArray    = Y.Array,
        Escape    = Y.Escape,
        WordBreak = Y.Text.WordBreak,

        isArray = Y.Lang.isArray,

        EMPTY_OBJECT = {},

        // Regex string that captures zero or one unclosed HTML entities. Used in
        // the static regex template properties below. The entity matching is
        // intentionally loose here, since there's a world of complexity involved in
        // doing strict matching for this use case.
        UNCLOSED_ENTITY = '(&[^;\\s]*)?',

    Highlight = {
        // -- Protected Static Properties ------------------------------------------

        /**
         * <p>
         * Regular expression template for highlighting a match that occurs anywhere
         * in a string. The placeholder <code>%needles</code> will be replaced with
         * a list of needles to match, joined by <code>|</code> characters.
         * </p>
         *
         * <p>
         * This regex should have two capturing subpatterns: the first should match
         * an unclosed HTML entity (e.g. "&amp" without a ";" at the end) 0 or 1
         * times; the second should contain the <code>%needles</code> placeholder.
         * The first subpattern match is used to emulate a negative lookbehind
         * assertion, in order to prevent highlighting inside HTML entities.
         * </p>
         *
         * @property _REGEX
         * @type {String}
         * @protected
         * @static
         * @final
         */
        _REGEX: UNCLOSED_ENTITY + '(%needles)',

        /**
         * Regex replacer function or string for normal matches.
         *
         * @property _REPLACER
         * @type {Function|String}
         * @protected
         * @static
         * @final
         */
        _REPLACER: function (match, p1, p2) {
             // Mimicking a negative lookbehind assertion to prevent matches inside
             // HTML entities. Hat tip to Steven Levithan for the technique:
             // http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript
             return p1 && !(/\s/).test(p2) ? match :
                        Highlight._TEMPLATE.replace(/\{s\}/g, p2);
         },

        /**
         * <p>
         * Regular expression template for highlighting start-of-string matches
         * (i.e., only matches that occur at the beginning of a string). The
         * placeholder <code>%needles</code> will be replaced with a list of needles
         * to match, joined by <code>|</code> characters.
         * </p>
         *
         * <p>
         * See <code>_REGEX</code> for a description of the capturing subpatterns
         * this regex should contain.
         * </p>
         *
         * @property _START_REGEX
         * @type {String}
         * @protected
         * @static
         * @final
         */
        _START_REGEX: '^' + UNCLOSED_ENTITY + '(%needles)',

        /**
         * Highlight template which will be used as a replacement for matched
         * substrings. The placeholder <code>{s}</code> will be replaced with the
         * matched substring.
         *
         * @property _TEMPLATE
         * @type {String}
         * @default '<b class="yui3-highlight">{s}</b>'
         * @protected
         * @static
         * @final
         */
        _TEMPLATE: '<b class="yui3-highlight">{s}</b>',

        // -- Public Static Methods ------------------------------------------------

        /**
         * Highlights all occurrences in the <em>haystack</em> string of the items
         * in the <em>needles</em> array, regardless of where they occur. The
         * returned string will have all HTML characters escaped except for the
         * highlighting markup.
         *
         * @method all
         * @param {String} haystack String to apply highlighting to.
         * @param {String|Array} needles String or array of strings that should be
         *   highlighted.
         * @param {Object} options (optional) Options object, which may contain
         *   zero or more of the following properties:
         *
         * <dl>
         *   <dt>caseSensitive (Boolean)</dt>
         *   <dd>
         *     If <code>true</code>, matching will be case-sensitive. Default is
         *     <code>false</code>.
         *   </dd>
         *
         *   <dt>startsWith (Boolean)<dt>
         *   <dd>
         *     By default, needles are highlighted wherever they appear in the
         *     haystack. If <code>startsWith</code> is <code>true</code>, matches
         *     must be anchored to the beginning of the string.
         *   </dd>
         * </dl>
         *
         * @return {String} Escaped and highlighted copy of <em>haystack</em>.
         * @static
         */
        all: function (haystack, needles, options) {
            var i, len, regex, replacer;

            if (!options) {
                options = EMPTY_OBJECT;
            }

            // TODO: document options.replacer
            regex    = options.startsWith ? Highlight._START_REGEX : Highlight._REGEX;
            replacer = options.replacer || Highlight._REPLACER;

            // Create a local copy of needles so we can safely modify it in the next
            // step.
            needles = isArray(needles) ? needles.concat() : [needles];

            // Escape HTML characters and special regular expression characters in
            // the needles so they can be used in a regex and matched against the
            // escaped haystack.
            for (i = 0, len = needles.length; i < len; ++i) {
                needles[i] = Escape.regex(Escape.html(needles[i]));
            }

            // Escape HTML characters in the haystack to prevent HTML injection.
            haystack = Escape.html(haystack);

            return haystack.replace(
                new RegExp(
                    regex.replace('%needles', needles.join('|')),
                    options.caseSensitive ? 'g' : 'gi'
                ),
                replacer
            );
        },

        /**
         * Same as <code>all()</code>, but case-sensitive by default.
         *
         * @method allCase
         * @param {String} haystack String to apply highlighting to.
         * @param {String|Array} needles String or array of strings that should be
         *   highlighted.
         * @param {Object} options (optional) Options object. See <code>all()</code>
         *   for details.
         * @return {String} Escaped and highlighted copy of <em>haystack</em>.
         * @static
         */
        allCase: function (haystack, needles, options) {
            return Highlight.all(haystack, needles,
                    Y.merge(options || EMPTY_OBJECT, {caseSensitive: true}));
        },

        /**
         * Highlights <em>needles</em> that occur at the start of <em>haystack</em>.
         * The returned string will have all HTML characters escaped except for the
         * highlighting markup.
         *
         * @method start
         * @param {String} haystack String to apply highlighting to.
         * @param {String|Array} needles String or array of strings that should be
         *   highlighted.
         * @param {Object} options (optional) Options object, which may contain
         *   zero or more of the following properties:
         *
         * <dl>
         *   <dt>caseSensitive (Boolean)</dt>
         *   <dd>
         *     If <code>true</code>, matching will be case-sensitive. Default is
         *     <code>false</code>.
         *   </dd>
         * </dl>
         *
         * @return {String} Escaped and highlighted copy of <em>haystack</em>.
         * @static
         */
        start: function (haystack, needles, options) {
            return Highlight.all(haystack, needles,
                    Y.merge(options || EMPTY_OBJECT, {startsWith: true}));
        },

        /**
         * Same as <code>start()</code>, but case-sensitive by default.
         *
         * @method startCase
         * @param {String} haystack String to apply highlighting to.
         * @param {String|Array} needles String or array of strings that should be
         *   highlighted.
         * @return {String} Escaped and highlighted copy of <em>haystack</em>.
         * @static
         */
        startCase: function (haystack, needles) {
            // No options passthru for now, since it would be redundant. If start()
            // ever supports more options than caseSensitive, then we'll start
            // passing the options through.
            return Highlight.start(haystack, needles, {caseSensitive: true});
        },

        /**
         * Highlights complete words in the <em>haystack</em> string that are also
         * in the <em>needles</em> array. The returned string will have all HTML
         * characters escaped except for the highlighting markup.
         *
         * @method words
         * @param {String} haystack String to apply highlighting to.
         * @param {String|Array} needles String or array of strings containing words
         *   that should be highlighted. If a string is passed, it will be split
         *   into words; if an array is passed, it is assumed to have already been
         *   split.
         * @param {Object} options (optional) Options object, which may contain
         *   zero or more of the following properties:
         *
         * <dl>
         *   <dt>caseSensitive (Boolean)</dt>
         *   <dd>
         *     If <code>true</code>, matching will be case-sensitive. Default is
         *     <code>false</code>.
         *   </dd>
         * </dl>
         *
         * @return {String} Escaped and highlighted copy of <em>haystack</em>.
         * @static
         */
        words: function (haystack, needles, options) {
            var caseSensitive,
                mapper,
                template = Highlight._TEMPLATE,
                words;

            if (!options) {
                options = EMPTY_OBJECT;
            }

            caseSensitive = !!options.caseSensitive;

            // Convert needles to a hash for faster lookups.
            needles = YArray.hash(
                isArray(needles) ? needles : WordBreak.getUniqueWords(needles, {
                    ignoreCase: !caseSensitive
                })
            );

            // The default word mapping function can be overridden with a custom
            // one. This is used to implement accent-folded highlighting in the
            // highlight-accentfold module.
            mapper = options.mapper || function (word, needles) {
                if (needles.hasOwnProperty(caseSensitive ? word : word.toLowerCase())) {
                    return template.replace(/\{s\}/g, Escape.html(word));
                }

                return Escape.html(word);
            };

            // Split the haystack into an array of words, including punctuation and
            // whitespace so we can rebuild the string later.
            words = WordBreak.getWords(haystack, {
                includePunctuation: true,
                includeWhitespace : true
            });

            return YArray.map(words, function (word) {
                return mapper(word, needles);
            }).join('');
        },

        /**
         * Same as <code>words()</code>, but case-sensitive by default.
         *
         * @method wordsCase
         * @param {String} haystack String to apply highlighting to.
         * @param {String|Array} needles String or array of strings containing words
         *   that should be highlighted. If a string is passed, it will be split
         *   into words; if an array is passed, it is assumed to have already been
         *   split.
         * @return {String} Escaped and highlighted copy of <em>haystack</em>.
         * @static
         */
        wordsCase: function (haystack, needles) {
            // No options passthru for now, since it would be redundant. If words()
            // ever supports more options than caseSensitive, then we'll start
            // passing the options through.
            return Highlight.words(haystack, needles, {caseSensitive: true});
        }
    };

    Y.Highlight = Highlight;

Copyright © 2011 Yahoo! Inc. All rights reserved.