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));