www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

build.js (6807B)


      1 /*!
      2  * XRegExp.build 3.0.0-pre
      3  * <http://xregexp.com/>
      4  * Steven Levithan © 2012 MIT License
      5  * Inspired by Lea Verou's RegExp.create <http://lea.verou.me/>
      6  */
      7 
      8 (function(XRegExp) {
      9     'use strict';
     10 
     11     var REGEX_DATA = 'xregexp',
     12         subParts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g,
     13         parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subParts], 'g');
     14 
     15 /**
     16  * Strips a leading `^` and trailing unescaped `$`, if both are present.
     17  * @private
     18  * @param {String} pattern Pattern to process.
     19  * @returns {String} Pattern with edge anchors removed.
     20  */
     21     function deanchor(pattern) {
     22         var leadingAnchor = /^\^/,
     23             trailingAnchor = /\$$/;
     24 
     25         // Ensure that the trailing `$` isn't escaped
     26         if (leadingAnchor.test(pattern) && trailingAnchor.test(pattern.replace(/\\[\s\S]/g, ''))) {
     27             return pattern.replace(leadingAnchor, '').replace(trailingAnchor, '');
     28         }
     29 
     30         return pattern;
     31     }
     32 
     33 /**
     34  * Converts the provided value to an XRegExp. Native RegExp flags are not preserved.
     35  * @private
     36  * @param {String|RegExp} value Value to convert.
     37  * @returns {RegExp} XRegExp object with XRegExp syntax applied.
     38  */
     39     function asXRegExp(value) {
     40         return XRegExp.isRegExp(value) ?
     41             (value[REGEX_DATA] && value[REGEX_DATA].captureNames ?
     42                 // Don't recompile, to preserve capture names
     43                 value :
     44                 // Recompile as XRegExp
     45                 XRegExp(value.source)
     46             ) :
     47             // Compile string as XRegExp
     48             XRegExp(value);
     49     }
     50 
     51 /**
     52  * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in the
     53  * outer pattern and provided subpatterns are automatically renumbered to work correctly. Native
     54  * flags used by provided subpatterns are ignored in favor of the `flags` argument.
     55  * @memberOf XRegExp
     56  * @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows
     57  *   `({{name}})` as shorthand for `(?<name>{{name}})`. Patterns cannot be embedded within
     58  *   character classes.
     59  * @param {Object} subs Lookup object for named subpatterns. Values can be strings or regexes. A
     60  *   leading `^` and trailing unescaped `$` are stripped from subpatterns, if both are present.
     61  * @param {String} [flags] Any combination of XRegExp flags.
     62  * @returns {RegExp} Regex with interpolated subpatterns.
     63  * @example
     64  *
     65  * var time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', {
     66  *   hours: XRegExp.build('{{h12}} : | {{h24}}', {
     67  *     h12: /1[0-2]|0?[1-9]/,
     68  *     h24: /2[0-3]|[01][0-9]/
     69  *   }, 'x'),
     70  *   minutes: /^[0-5][0-9]$/
     71  * });
     72  * time.test('10:59'); // -> true
     73  * XRegExp.exec('10:59', time).minutes; // -> '59'
     74  */
     75     XRegExp.build = function(pattern, subs, flags) {
     76         var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern),
     77             data = {},
     78             numCaps = 0, // 'Caps' is short for captures
     79             numPriorCaps,
     80             numOuterCaps = 0,
     81             outerCapsMap = [0],
     82             outerCapNames,
     83             sub,
     84             p;
     85 
     86         // Add flags within a leading mode modifier to the overall pattern's flags
     87         if (inlineFlags) {
     88             flags = flags || '';
     89             inlineFlags[1].replace(/./g, function(flag) {
     90                 // Don't add duplicates
     91                 flags += (flags.indexOf(flag) > -1 ? '' : flag);
     92             });
     93         }
     94 
     95         for (p in subs) {
     96             if (subs.hasOwnProperty(p)) {
     97                 // Passing to XRegExp enables extended syntax and ensures independent validity,
     98                 // lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For
     99                 // subpatterns provided as native regexes, it dies on octals and adds the property
    100                 // used to hold extended regex instance data, for simplicity
    101                 sub = asXRegExp(subs[p]);
    102                 data[p] = {
    103                     // Deanchoring allows embedding independently useful anchored regexes. If you
    104                     // really need to keep your anchors, double them (i.e., `^^...$$`)
    105                     pattern: deanchor(sub.source),
    106                     names: sub[REGEX_DATA].captureNames || []
    107                 };
    108             }
    109         }
    110 
    111         // Passing to XRegExp dies on octals and ensures the outer pattern is independently valid;
    112         // helps keep this simple. Named captures will be put back
    113         pattern = asXRegExp(pattern);
    114         outerCapNames = pattern[REGEX_DATA].captureNames || [];
    115         pattern = pattern.source.replace(parts, function($0, $1, $2, $3, $4) {
    116             var subName = $1 || $2, capName, intro;
    117             // Named subpattern
    118             if (subName) {
    119                 if (!data.hasOwnProperty(subName)) {
    120                     throw new ReferenceError('Undefined property ' + $0);
    121                 }
    122                 // Named subpattern was wrapped in a capturing group
    123                 if ($1) {
    124                     capName = outerCapNames[numOuterCaps];
    125                     outerCapsMap[++numOuterCaps] = ++numCaps;
    126                     // If it's a named group, preserve the name. Otherwise, use the subpattern name
    127                     // as the capture name
    128                     intro = '(?<' + (capName || subName) + '>';
    129                 } else {
    130                     intro = '(?:';
    131                 }
    132                 numPriorCaps = numCaps;
    133                 return intro + data[subName].pattern.replace(subParts, function(match, paren, backref) {
    134                     // Capturing group
    135                     if (paren) {
    136                         capName = data[subName].names[numCaps - numPriorCaps];
    137                         ++numCaps;
    138                         // If the current capture has a name, preserve the name
    139                         if (capName) {
    140                             return '(?<' + capName + '>';
    141                         }
    142                     // Backreference
    143                     } else if (backref) {
    144                         // Rewrite the backreference
    145                         return '\\' + (+backref + numPriorCaps);
    146                     }
    147                     return match;
    148                 }) + ')';
    149             }
    150             // Capturing group
    151             if ($3) {
    152                 capName = outerCapNames[numOuterCaps];
    153                 outerCapsMap[++numOuterCaps] = ++numCaps;
    154                 // If the current capture has a name, preserve the name
    155                 if (capName) {
    156                     return '(?<' + capName + '>';
    157                 }
    158             // Backreference
    159             } else if ($4) {
    160                 // Rewrite the backreference
    161                 return '\\' + outerCapsMap[+$4];
    162             }
    163             return $0;
    164         });
    165 
    166         return XRegExp(pattern, flags);
    167     };
    168 
    169 }(XRegExp));