|
7 | 7 | * Author: Diego Perini <diego.perini at gmail com> |
8 | 8 | * Version: 2.0.8 |
9 | 9 | * Created: 20070722 |
10 | | - * Release: 20180727 |
| 10 | + * Release: 20180901 |
11 | 11 | * |
12 | 12 | * License: |
13 | 13 | * http://javascript.nwbox.com/nwsapi/MIT-LICENSE |
|
38 | 38 | WSP = '[\\x20\\t\\r\\n\\f]', |
39 | 39 |
|
40 | 40 | CFG = { |
| 41 | + // extensions |
41 | 42 | operators: '[~*^$|]=|=', |
42 | | - combinators: '[\\x20\\t\\r\\n\\f>+~](?=[^>+~])' |
| 43 | + combinators: '[\\x20\\t>+~](?=[^>+~])' |
| 44 | + }, |
| 45 | + |
| 46 | + NOT = { |
| 47 | + // not enclosed in double/single/parens/square |
| 48 | + double_enc: '(?=(?:[^"]*["][^"]*["])*[^"]*$)', |
| 49 | + single_enc: "(?=(?:[^']*['][^']*['])*[^']*$)", |
| 50 | + parens_enc: '(?![^\\x28]*\\x29)', |
| 51 | + square_enc: '(?![^\\x5b]*\\x5d)' |
43 | 52 | }, |
44 | 53 |
|
45 | 54 | REX = { |
| 55 | + // regular expressions |
46 | 56 | HasEscapes: RegExp('\\\\'), |
47 | 57 | HexNumbers: RegExp('^[0-9a-fA-F]'), |
48 | 58 | EscOrQuote: RegExp('^\\\\|[\\x22\\x27]'), |
49 | | - RegExpChar: RegExp('(?:(?!\\\\)[\\\\^$.*+?()[\\]{}|\\/])' ,'g'), |
50 | | - TrimSpaces: RegExp('[\\r\\n\\f]|^' + WSP + '+|' + WSP + '+$', 'g'), |
| 59 | + RegExpChar: RegExp('(?:(?!\\\\)[\\\\^$.*+?()[\\]{}|\\/])', 'g'), |
| 60 | + TrimSpaces: RegExp('[\\n\\r\\f]+|^' + WSP + '+|' + WSP + '+$', 'g'), |
51 | 61 | FixEscapes: RegExp('\\\\([0-9a-fA-F]{1,6}' + WSP + '?|.)|([\\x22\\x27])', 'g'), |
52 | | - SplitGroup: RegExp(WSP + '*,' + WSP + '*(?![^\\[]*\\]|[^\\(]*\\)|[^\\{]*\\})', 'g') |
| 62 | + CombineWSP: RegExp('[\\n\\r\\f\\x20]+' + NOT.single_enc + NOT.double_enc, 'g'), |
| 63 | + TabCharWSP: RegExp('(\\x20?\\t+\\x20?)' + NOT.single_enc + NOT.double_enc, 'g'), |
| 64 | + CommaGroup: RegExp(WSP + '+,' + WSP + '+' + NOT.single_enc + NOT.double_enc, 'g'), |
| 65 | + SplitGroup: RegExp(WSP + '?,' + WSP + '?' + NOT.square_enc + NOT.parens_enc, 'g') |
53 | 66 | }, |
54 | 67 |
|
55 | | - struct_1 = '(root|empty|(?:(?:first|last|only)(?:-child|-of-type)))\\b', |
56 | | - struct_2 = '(nth(?:-last)?(?:-child|-of-type))(?:\\(\\s?(even|odd|(?:[-+]?\\d*)(?:n\\s?[-+]?\\s?\\d*)?)\\s?(?:\\)|$))', |
57 | | - |
58 | | - pseudo_1 = '(dir|lang)\\x28\\s?([-\\w]{2,})\\s?(?:\\x29|$)', |
59 | | - pseudo_2 = ':?(after|before|first-letter|first-line|selection|backdrop|placeholder)\\b', |
60 | | - |
61 | | - noparm_1 = '(link|visited|target|scope|hover|active|focus|enabled|disabled|read-only|read-write|placeholder-shown)\\b', |
62 | | - noparm_2 = '(default|checked|indeterminate|required|optional|valid|invalid|in-range|out-of-range)\\b', |
63 | | - |
64 | | - logicals = '(matches|not)\\x28\\s?([^()]*|[^\\x28]*\\x28[^\\x29]*\\x29)\\s?(?:\\x29|$)', |
| 68 | + GROUPS = { |
| 69 | + // pseudo-classes requiring parameters |
| 70 | + linguistic: '(dir|lang)\\x28\\s?([-\\w]{2,})\\s?(?:\\x29|$)', |
| 71 | + logicalsel: '(matches|not)\\x28\\s?([^()]*|[^\\x28]*\\x28[^\\x29]*\\x29)\\s?(?:\\x29|$)', |
| 72 | + treestruct: '(nth(?:-last)?(?:-child|-of-type))(?:\\x28\\s?(even|odd|(?:[-+]?\\d*)(?:n\\s?[-+]?\\s?\\d*)?)\\s?(?:\\x29|$))', |
| 73 | + // pseudo-classes not requiring parameters |
| 74 | + locationpc: '(link|visited|target|scope)\\b', |
| 75 | + useraction: '(hover|active|focus|focus-within)\\b', |
| 76 | + structural: '(root|empty|(?:(?:first|last|only)(?:-child|-of-type)))\\b', |
| 77 | + pseudoelem: ':?(after|before|first-letter|first-line|selection|placeholder)\\b', |
| 78 | + inputstate: '(enabled|disabled|read-only|read-write|placeholder-shown|default)\\b', |
| 79 | + inputvalue: '(checked|indeterminate|required|optional|valid|invalid|in-range|out-of-range)\\b' |
| 80 | + }, |
65 | 81 |
|
66 | 82 | Patterns = { |
67 | 83 | // pseudo-classes |
68 | | - struct_n: RegExp('^:(?:' + struct_1 + ')(.*)', 'i'), |
69 | | - struct_p: RegExp('^:(?:' + struct_2 + ')(.*)', 'i'), |
70 | | - hpseudos: RegExp('^:(?:' + pseudo_1 + ')(.*)', 'i'), |
71 | | - epseudos: RegExp('^:(?:' + pseudo_2 + ')(.*)', 'i'), |
72 | | - lpseudos: RegExp('^:(?:' + logicals + ')(.*)', 'i'), |
73 | | - fpseudos: RegExp('^:(?:' + noparm_1 + ')(.*)', 'i'), |
74 | | - ipseudos: RegExp('^:(?:' + noparm_2 + ')(.*)', 'i'), |
| 84 | + treestruct: RegExp('^:(?:' + GROUPS.treestruct + ')(.*)', 'i'), |
| 85 | + structural: RegExp('^:(?:' + GROUPS.structural + ')(.*)', 'i'), |
| 86 | + linguistic: RegExp('^:(?:' + GROUPS.linguistic + ')(.*)', 'i'), |
| 87 | + pseudoelem: RegExp('^:(?:' + GROUPS.pseudoelem + ')(.*)', 'i'), |
| 88 | + useraction: RegExp('^:(?:' + GROUPS.useraction + ')(.*)', 'i'), |
| 89 | + inputstate: RegExp('^:(?:' + GROUPS.inputstate + ')(.*)', 'i'), |
| 90 | + inputvalue: RegExp('^:(?:' + GROUPS.inputvalue + ')(.*)', 'i'), |
| 91 | + locationpc: RegExp('^:(?:' + GROUPS.locationpc + ')(.*)', 'i'), |
| 92 | + logicalsel: RegExp('^:(?:' + GROUPS.logicalsel + ')(.*)', 'i'), |
75 | 93 | // combinators symbols |
76 | 94 | children: RegExp('^' + WSP + '?\\>' + WSP + '?(.*)'), |
77 | 95 | adjacent: RegExp('^' + WSP + '?\\+' + WSP + '?(.*)'), |
|
890 | 908 | // :first-child, :last-child, :only-child, |
891 | 909 | // :first-of-type, :last-of-type, :only-of-type, |
892 | 910 | case ':': |
893 | | - if ((match = selector.match(Patterns.struct_n))) { |
| 911 | + if ((match = selector.match(Patterns.structural))) { |
894 | 912 | match[1] = match[1].toLowerCase(); |
895 | 913 | switch (match[1]) { |
896 | 914 | case 'root': |
|
936 | 954 | // *** child-indexed & typed child-indexed pseudo-classes |
937 | 955 | // :nth-child, :nth-of-type, :nth-last-child, :nth-last-of-type |
938 | 956 | // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) |
939 | | - else if ((match = selector.match(Patterns.struct_p))) { |
| 957 | + else if ((match = selector.match(Patterns.treestruct))) { |
940 | 958 | match[1] = match[1].toLowerCase(); |
941 | 959 | switch (match[1]) { |
942 | 960 | case 'nth-child': |
|
987 | 1005 |
|
988 | 1006 | // *** logical combination pseudo-classes |
989 | 1007 | // :matches( s1, [ s2, ... ]), :not( s1, [ s2, ... ]) |
990 | | - else if ((match = selector.match(Patterns.lpseudos))) { |
| 1008 | + else if ((match = selector.match(Patterns.logicalsel))) { |
991 | 1009 | match[1] = match[1].toLowerCase(); |
992 | 1010 | switch (match[1]) { |
993 | 1011 | case 'matches': |
|
1010 | 1028 |
|
1011 | 1029 | // *** linguistic pseudo-classes |
1012 | 1030 | // :dir( ltr / rtl ), :lang( en ) |
1013 | | - else if ((match = selector.match(Patterns.hpseudos))) { |
| 1031 | + else if ((match = selector.match(Patterns.linguistic))) { |
1014 | 1032 | match[1] = match[1].toLowerCase(); |
1015 | 1033 | switch (match[1]) { |
1016 | 1034 | case 'dir': |
|
1033 | 1051 | } |
1034 | 1052 | } |
1035 | 1053 |
|
1036 | | - // *** location, user actiond and input pseudo-classes |
1037 | | - else if ((match = selector.match(Patterns.fpseudos))) { |
| 1054 | + // *** location pseudo-classes |
| 1055 | + // :link, :visited, :target, :scope |
| 1056 | + else if ((match = selector.match(Patterns.locationpc))) { |
1038 | 1057 | match[1] = match[1].toLowerCase(); |
1039 | 1058 | switch (match[1]) { |
1040 | | - // *** location pseudo-classes |
1041 | | - // :link, :visited, :target, :scope |
1042 | 1059 | case 'link': |
1043 | 1060 | source = 'if(' + N + '(/^a|area|link$/i.test(e.nodeName)&&e.hasAttribute("href"))){' + source + '}'; |
1044 | 1061 | break; |
|
1051 | 1068 | case 'scope': |
1052 | 1069 | source = 'if((s.from.compareDocumentPosition(e)&20)==20){' + source + '}'; |
1053 | 1070 | break; |
| 1071 | + default: |
| 1072 | + emit('\'' + selector_string + '\'' + qsInvalid); |
| 1073 | + break; |
| 1074 | + } |
| 1075 | + } |
1054 | 1076 |
|
1055 | | - // *** user actions pseudo-classes |
1056 | | - // :hover, :active, :focus |
| 1077 | + // *** user actions pseudo-classes |
| 1078 | + // :hover, :active, :focus |
| 1079 | + else if ((match = selector.match(Patterns.useraction))) { |
| 1080 | + match[1] = match[1].toLowerCase(); |
| 1081 | + switch (match[1]) { |
1057 | 1082 | case 'hover': |
1058 | 1083 | source = 'hasFocus' in doc && doc.hasFocus() ? |
1059 | 1084 | 'if(' + N + '(e===s.doc.hoverElement)){' + source + '}' : |
|
1069 | 1094 | 'if(' + N + '(e===s.doc.activeElement&&s.doc.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number"))){' + source + '}' : |
1070 | 1095 | 'if(' + N + '(e===s.doc.activeElement&&(e.type||e.href))){' + source + '}'; |
1071 | 1096 | break; |
| 1097 | + default: |
| 1098 | + emit('\'' + selector_string + '\'' + qsInvalid); |
| 1099 | + break; |
| 1100 | + } |
| 1101 | + } |
1072 | 1102 |
|
1073 | | - // *** user interface and form pseudo-classes |
1074 | | - // :enabled, :disabled, :read-only, :read-write, :placeholder-shown |
| 1103 | + // *** user interface and form pseudo-classes |
| 1104 | + // :enabled, :disabled, :read-only, :read-write, :placeholder-shown, :default |
| 1105 | + else if ((match = selector.match(Patterns.inputstate))) { |
| 1106 | + match[1] = match[1].toLowerCase(); |
| 1107 | + switch (match[1]) { |
1075 | 1108 | case 'enabled': |
1076 | 1109 | source = 'if(' + N + '(("form" in e||/^optgroup$/i.test(e.nodeName))&&"disabled" in e &&e.disabled===false' + |
1077 | 1110 | ')){' + source + '}'; |
|
1105 | 1138 | '(!s.match(":focus",e))' + |
1106 | 1139 | ')){' + source + '}'; |
1107 | 1140 | break; |
1108 | | - default: |
1109 | | - emit('\'' + selector_string + '\'' + qsInvalid); |
1110 | | - break; |
1111 | | - } |
1112 | | - } |
1113 | | - |
1114 | | - // *** input pseudo-classes for form validation (was web-forms) |
1115 | | - // :default, :checked, :indeterminate, :valid, :invalid |
1116 | | - // :in-range, :out-of-range, :required, :optional |
1117 | | - else if ((match = selector.match(Patterns.ipseudos))) { |
1118 | | - match[1] = match[1].toLowerCase(); |
1119 | | - switch (match[1]) { |
1120 | 1141 | case 'default': |
1121 | 1142 | source = |
1122 | 1143 | 'if(' + N + '("form" in e && e.form)){' + |
|
1134 | 1155 | '(("|radio|checkbox|".includes("|"+e.type+"|"))&&e.defaultChecked)' + |
1135 | 1156 | ')){' + source + '}'; |
1136 | 1157 | break; |
| 1158 | + default: |
| 1159 | + emit('\'' + selector_string + '\'' + qsInvalid); |
| 1160 | + break; |
| 1161 | + } |
| 1162 | + } |
| 1163 | + |
| 1164 | + // *** input pseudo-classes (for form validation) |
| 1165 | + // :checked, :indeterminate, :valid, :invalid |
| 1166 | + // :in-range, :out-of-range, :required, :optional |
| 1167 | + else if ((match = selector.match(Patterns.inputvalue))) { |
| 1168 | + match[1] = match[1].toLowerCase(); |
| 1169 | + switch (match[1]) { |
1137 | 1170 | case 'checked': |
1138 | 1171 | source = 'if(' + N + '(/^input$/i.test(e.nodeName)&&' + |
1139 | 1172 | '("|radio|checkbox|".includes("|"+e.type+"|")&&e.checked)||' + |
|
1203 | 1236 | } |
1204 | 1237 |
|
1205 | 1238 | // allow pseudo-elements as :after/:before (single or double colon) |
1206 | | - else if ((match = selector.match(Patterns.epseudos))) { |
| 1239 | + else if ((match = selector.match(Patterns.pseudoelem))) { |
1207 | 1240 | source = 'if(' + D + '(/1|11/).test(e.nodeType)){' + source + '}'; |
1208 | 1241 | } |
1209 | 1242 |
|
|
1318 | 1351 | selector = '' + selector; |
1319 | 1352 | } |
1320 | 1353 |
|
1321 | | - // normalize selector |
| 1354 | + // normalize input selector string |
1322 | 1355 | selector = selector. |
1323 | 1356 | replace(/\x00|\\$/g, '\ufffd'). |
| 1357 | + replace(REX.CombineWSP, '\x20'). |
| 1358 | + replace(REX.TabCharWSP, '\t'). |
| 1359 | + replace(REX.CommaGroup, ','). |
1324 | 1360 | replace(REX.TrimSpaces, ''); |
1325 | 1361 |
|
1326 | 1362 | // parse, validate and split possible selector groups |
|
1396 | 1432 | selector = '' + selector; |
1397 | 1433 | } |
1398 | 1434 |
|
1399 | | - // normalize selector |
| 1435 | + // normalize input selector string |
1400 | 1436 | selector = selector. |
1401 | 1437 | replace(/\x00|\\$/g, '\ufffd'). |
| 1438 | + replace(REX.CombineWSP, '\x20'). |
| 1439 | + replace(REX.TabCharWSP, '\t'). |
| 1440 | + replace(REX.CommaGroup, ','). |
1402 | 1441 | replace(REX.TrimSpaces, ''); |
1403 | 1442 |
|
1404 | 1443 | // parse, validate and split possible selector groups |
|
0 commit comments