Comparing version 0.0.1 to 0.0.3
716
index.js
'use strict'; | ||
(function () { | ||
var xpath_to_lower = function (s) { | ||
return "translate(" + (s || 'normalize-space()') + ", 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz')"; | ||
}, | ||
xpath_ends_with = function (s1, s2) { | ||
return 'substring(' + s1 + ',string-length(' + s1 + ')-string-length(' + s2 + ')+1)=' + s2; | ||
}, | ||
xpath_url = function (s) { | ||
return 'substring-before(concat(substring-after(' + (s || xpath_url_attrs) + ',"://"),"?"),"?")'; | ||
}, | ||
xpath_url_path = function (s) { | ||
var xpath_to_lower = function (s) { | ||
return 'translate(' + | ||
(s || 'normalize-space()') + | ||
', \'ABCDEFGHJIKLMNOPQRSTUVWXYZ\'' + | ||
', \'abcdefghjiklmnopqrstuvwxyz\')'; | ||
}, | ||
xpath_ends_with = function (s1, s2) { | ||
return 'substring(' + s1 + ',' + | ||
'string-length(' + s1 + ')-string-length(' + s2 + ')+1)=' + s2; | ||
}, | ||
xpath_url = function (s) { | ||
return 'substring-before(concat(substring-after(' + | ||
(s || xpath_url_attrs) + ',"://"),"?"),"?")'; | ||
}, | ||
xpath_url_path = function (s) { | ||
return 'substring-after(' + (s || xpath_url_attrs) + ',"/")'; | ||
}, | ||
}, | ||
xpath_url_domain = function (s) { | ||
return 'substring-before(concat(substring-after(' + | ||
(s || xpath_url_attrs) + ',"://"),"/"),"/")'; | ||
}, | ||
xpath_url_attrs = '@href|@src', | ||
xpath_lower_case = xpath_to_lower(), | ||
xpath_ns_uri = 'ancestor-or-self::*[last()]/@url', | ||
xpath_ns_path = xpath_url_path(xpath_url(xpath_ns_uri)), | ||
xpath_has_protocal = '(starts-with(' + xpath_url_attrs + ',"http://") or starts-with(' + xpath_url_attrs + ',"https://"))', | ||
xpath_is_internal = 'starts-with(' + xpath_url() + ',' + xpath_url_domain(xpath_ns_uri) + ') or ' + xpath_ends_with(xpath_url_domain(), xpath_url_domain(xpath_ns_uri)), | ||
xpath_is_local = '(' + xpath_has_protocal + ' and starts-with(' + xpath_url() + ',' + xpath_url(xpath_ns_uri) + '))', | ||
xpath_is_path = 'starts-with(' + xpath_url_attrs + ',"/")', | ||
xpath_is_local_path = 'starts-with(' + xpath_url_path() + ',' + xpath_ns_path + ')', | ||
xpath_normalize_space = 'normalize-space()', | ||
xpath_internal = '[not(' + xpath_has_protocal + ') or ' + xpath_is_internal + ']', | ||
xpath_external = '[' + xpath_has_protocal + ' and not(' + xpath_is_internal + ')]', | ||
escape_literal = String.fromCharCode(30), | ||
escape_parens = String.fromCharCode(31), | ||
regex_string_literal = /("[^"\x1E]*"|'[^'\x1E]*'|=\s*[^\s\]\'\"]+)/g, | ||
regex_escaped_literal = /['"]?(\x1E+)['"]?/g, | ||
regex_css_wrap_pseudo = /(\x1F\)|[^\)])\:(first|limit|last|gt|lt|eq|nth)([^\-]|$)/, | ||
regex_specal_chars = /[\x1C-\x1F]+/g, | ||
regex_first_axis = /^([\s\(\x1F]*)(\.?[^\.\/\(]{1,2}[a-z]*:*)/, | ||
regex_filter_prefix = /(^|\/|\:)\[/g, | ||
regex_attr_prefix = /([^\(\[\/\|\s\x1F])\@/g, | ||
regex_nth_equation = /^([-0-9]*)n.*?([0-9]*)$/, | ||
css_combinators_regex = /\s*(!?[+>~,^ ])\s*(\.?\/+|[a-z\-]+::)?([a-z\-]+\()?((and\s*|or\s*|mod\s*)?[^+>~,\s'"\]\|\^\$\!\<\=\x1C-\x1F]+)?/g, | ||
css_combinators_callback = function (match, operator, axis, func, literal, exclude, offset, orig) { | ||
var prefix = ''; // If we can, we'll prefix a '.' | ||
xpath_url_domain = function (s) { | ||
return 'substring-before(concat(substring-after(' + (s || xpath_url_attrs) + ',"://"),"/"),"/")'; | ||
}, | ||
xpath_url_attrs = '@href|@src', | ||
xpath_lower_case = xpath_to_lower(), | ||
xpath_ns_uri = 'ancestor-or-self::*[last()]/@url', | ||
xpath_ns_path = xpath_url_path(xpath_url(xpath_ns_uri)), | ||
xpath_has_protocal = '(starts-with(' + xpath_url_attrs + ',"http://") or starts-with(' + xpath_url_attrs + ',"https://"))', | ||
xpath_is_internal = 'starts-with(' + xpath_url() + ',' + xpath_url_domain(xpath_ns_uri) + ') or ' + xpath_ends_with(xpath_url_domain(), xpath_url_domain(xpath_ns_uri)), | ||
xpath_is_local = '(' + xpath_has_protocal + ' and starts-with(' + xpath_url() + ',' + xpath_url(xpath_ns_uri) + '))', | ||
xpath_is_path = 'starts-with(' + xpath_url_attrs + ',"/")', | ||
xpath_is_local_path = 'starts-with(' + xpath_url_path() + ',' + xpath_ns_path + ')', | ||
xpath_normalize_space = "normalize-space()", | ||
xpath_internal = '[not(' + xpath_has_protocal + ') or ' + xpath_is_internal + ']', | ||
xpath_external = '[' + xpath_has_protocal + ' and not(' + xpath_is_internal + ')]', | ||
escape_literal = String.fromCharCode(30), | ||
escape_parens = String.fromCharCode(31), | ||
regex_string_literal = /("[^"\x1E]*"|'[^'\x1E]*'|=\s*[^\s\]\'\"]+)/g, | ||
regex_escaped_literal = /['"]?(\x1E+)['"]?/g, | ||
regex_css_wrap_pseudo = /(\x1F\)|[^\)])\:(first|limit|last|gt|lt|eq|nth)([^\-]|$)/, | ||
regex_specal_chars = /[\x1C-\x1F]+/g, | ||
regex_first_axis = /^([\s\(\x1F]*)(\.?[^\.\/\(]{1,2}[a-z]*:*)/, | ||
regex_filter_prefix = /(^|\/|\:)\[/g, | ||
regex_attr_prefix = /([^\(\[\/\|\s\x1F])\@/g, | ||
regex_nth_equation = /^([-0-9]*)n.*?([0-9]*)$/, | ||
css_combinators_regex = /\s*(!?[+>~,^ ])\s*(\.?\/+|[a-z\-]+::)?([a-z\-]+\()?((and\s*|or\s*|mod\s*)?[^+>~,\s'"\]\|\*\^\$\!\<\=\x1C-\x1F]+)?/g, | ||
css_combinators_callback = function (match, operator, axis, func, literal, exclude, offset, orig) { | ||
// XPath operators can look like node-name selectors | ||
// Detect false positive for " and", " or", " mod" | ||
if (operator === ' ' && exclude !== undefined) { | ||
return match; | ||
return match; | ||
} | ||
var prefix = ''; // If we can, we'll prefix a '.' | ||
if (axis === undefined) { | ||
// Only allow node-selecting XPath functions | ||
// Detect false positive for " + count(...)", " count(...)", " > position()", etc. | ||
if (func !== undefined && (func !== 'node(' && func !== 'text(' && func !== 'comment(')) | ||
return; | ||
else if (literal === undefined) | ||
literal = func; // Handle case " + text()", " > comment()", etc. where "func" is our "literal" | ||
// Only allow node-selecting XPath functions | ||
// Detect false positive for " + count(...)", " count(...)", " > position()", etc. | ||
if (func !== undefined && (func !== 'node(' && func !== 'text(' && func !== 'comment(')) { | ||
return; | ||
} else if (literal === undefined) { | ||
literal = func; | ||
} // Handle case " + text()", " > comment()", etc. where "func" is our "literal" | ||
// XPath math operators match some CSS combinators | ||
// Detect false positive for " + 1", " > 1", etc. | ||
if (isNumeric(literal)) | ||
return match; | ||
if (isNumeric(literal)) { | ||
return match; | ||
} | ||
var prevChar = orig.charAt(offset - 1); | ||
var prevChar = orig.charAt(offset - 1); | ||
if (prevChar.length === 0 || | ||
if (prevChar.length === 0 || | ||
prevChar === '(' || | ||
prevChar === '|' || | ||
prevChar === ':') | ||
prefix = '.'; | ||
prevChar === ':') { | ||
prefix = '.'; | ||
} | ||
} | ||
@@ -81,6 +84,7 @@ | ||
if (literal === undefined) { | ||
if (offset + match.length === orig.length) | ||
literal = '*'; | ||
else | ||
return match; | ||
if (offset + match.length === orig.length) { | ||
literal = '*'; | ||
} else { | ||
return match; | ||
} | ||
} | ||
@@ -90,33 +94,35 @@ | ||
switch (operator) { | ||
case ' ': | ||
return '//' + literal; | ||
case '>': | ||
return '/' + literal; | ||
case '+': | ||
return prefix + '/following-sibling::*[1]/self::' + literal; | ||
case '~': | ||
return prefix + '/following-sibling::' + literal; | ||
case ',': | ||
if (axis === undefined); | ||
axis = './/'; | ||
return '|' + axis + literal; | ||
case '^': // first child | ||
return '/child::*[1]/self::' + literal; | ||
case '!^': // last child | ||
return '/child::*[last()]/self::' + literal; | ||
case '! ': // ancestor-or-self | ||
return '/ancestor-or-self::' + literal; | ||
case '!>': // direct parent | ||
return '/parent::' + literal; | ||
case '!+': // adjacent preceding sibling | ||
return '/preceding-sibling::*[1]/self::' + literal; | ||
case '!~': // preceding sibling | ||
return '/preceding-sibling::' + literal; | ||
//case '~~' | ||
// return '/following-sibling::*/self::|'+selectorStart(orig, offset)+'/preceding-sibling::*/self::'+literal; | ||
case ' ': | ||
return '//' + literal; | ||
case '>': | ||
return '/' + literal; | ||
case '+': | ||
return prefix + '/following-sibling::*[1]/self::' + literal; | ||
case '~': | ||
return prefix + '/following-sibling::' + literal; | ||
case ',': | ||
if (axis === undefined) { | ||
} | ||
axis = './/'; | ||
return '|' + axis + literal; | ||
case '^': // first child | ||
return '/child::*[1]/self::' + literal; | ||
case '!^': // last child | ||
return '/child::*[last()]/self::' + literal; | ||
case '! ': // ancestor-or-self | ||
return '/ancestor-or-self::' + literal; | ||
case '!>': // direct parent | ||
return '/parent::' + literal; | ||
case '!+': // adjacent preceding sibling | ||
return '/preceding-sibling::*[1]/self::' + literal; | ||
case '!~': // preceding sibling | ||
return '/preceding-sibling::' + literal; | ||
// case '~~' | ||
// return '/following-sibling::*/self::|'+selectorStart(orig, offset)+'/preceding-sibling::*/self::'+literal; | ||
} | ||
}, | ||
}, | ||
css_attributes_regex = /\[([^\@\|\*\=\^\~\$\!\(\/\s\x1C-\x1F]+)\s*(([\|\*\~\^\$\!]?)=?\s*(\x1E+))?\]/g, | ||
css_attributes_callback = function (str, attr, comp, op, val, offset, orig) { | ||
css_attributes_regex = /\[([^\@\|\*\=\^\~\$\!\(\/\s\x1C-\x1F]+)\s*(([\|\*\~\^\$\!]?)=?\s*(\x1E+))?\]/g, | ||
css_attributes_callback = function (str, attr, comp, op, val, offset, orig) { | ||
var axis = ''; | ||
@@ -131,180 +137,191 @@ var prevChar = orig.charAt(offset - 1); | ||
switch (op) { | ||
case '!': | ||
return axis + '[not(@' + attr + ') or @' + attr + '!="' + val + '"]'; | ||
case '$': | ||
return axis + '[substring(@' + attr + ',string-length(@' + attr + ')-(string-length("' + val + '")-1))="' + val + '"]'; | ||
case '^': | ||
return axis + '[starts-with(@' + attr + ',"' + val + '")]'; | ||
case '~': | ||
return axis + '[contains(concat(" ",normalize-space(@' + attr + ')," "),concat(" ","' + val + '"," "))]'; | ||
case '*': | ||
return axis + '[contains(@' + attr + ',"' + val + '")]'; | ||
case '|': | ||
return axis + '[@' + attr + '="' + val + '" or starts-with(@' + attr + ',concat("' + val + '","-"))]'; | ||
default: | ||
if (comp === undefined) { | ||
if (attr.charAt(attr.length - 1) === '(' || attr.search(/^[0-9]+$/) !== -1 || attr.indexOf(':') !== -1) | ||
return str; | ||
return axis + '[@' + attr + ']'; | ||
} else { | ||
return axis + '[@' + attr + '="' + val + '"]'; | ||
} | ||
case '!': | ||
return axis + '[not(@' + attr + ') or @' + attr + '!="' + val + '"]'; | ||
case '$': | ||
return axis + '[substring(@' + attr + ',string-length(@' + attr + ')-(string-length("' + val + '")-1))="' + val + '"]'; | ||
case '^': | ||
return axis + '[starts-with(@' + attr + ',"' + val + '")]'; | ||
case '~': | ||
return axis + '[contains(concat(" ",normalize-space(@' + attr + ')," "),concat(" ","' + val + '"," "))]'; | ||
case '*': | ||
return axis + '[contains(@' + attr + ',"' + val + '")]'; | ||
case '|': | ||
return axis + '[@' + attr + '="' + val + '" or starts-with(@' + attr + ',concat("' + val + '","-"))]'; | ||
default: | ||
if (comp === undefined) { | ||
if (attr.charAt(attr.length - 1) === '(' || attr.search(/^[0-9]+$/) !== -1 || attr.indexOf(':') !== -1) { | ||
return str; | ||
} | ||
return axis + '[@' + attr + ']'; | ||
} else { | ||
return axis + '[@' + attr + '="' + val + '"]'; | ||
} | ||
} | ||
}, | ||
}, | ||
css_pseudo_classes_regex = /:([a-z\-]+)(\((\x1F+)(([^\x1F]+(\3\x1F+)?)*)(\3\)))?/g, | ||
css_pseudo_classes_callback = function (match, name, g1, g2, arg, g3, g4, g5, offset, orig) { | ||
css_pseudo_classes_regex = /:([a-z\-]+)(\((\x1F+)(([^\x1F]+(\3\x1F+)?)*)(\3\)))?/g, | ||
css_pseudo_classes_callback = function (match, name, g1, g2, arg, g3, g4, g5, offset, orig) { | ||
if (orig.charAt(offset - 1) === ':' && orig.charAt(offset - 2) !== ':') { | ||
// XPath "axis::node-name" will match | ||
// Detect false positive ":node-name" | ||
return match; | ||
return match; | ||
} | ||
if (name === 'odd' || name === 'even') { | ||
arg = name; | ||
name = "nth-of-type"; | ||
arg = name; | ||
name = 'nth-of-type'; | ||
} | ||
switch (name) { // name.toLowerCase()? | ||
case 'after': | ||
return "[count(" + css2xpath('preceding::' + arg, true) + ") > 0]"; | ||
case 'after-sibling': | ||
return "[count(" + css2xpath('preceding-sibling::' + arg, true) + ") > 0]"; | ||
case 'before': | ||
return "[count(" + css2xpath('following::' + arg, true) + ") > 0]"; | ||
case 'before-sibling': | ||
return "[count(" + css2xpath('following-sibling::' + arg, true) + ") > 0]"; | ||
case 'checked': | ||
return "[@selected or @checked]"; | ||
case 'contains': | ||
return "[contains(" + xpath_normalize_space + "," + arg + ")]"; | ||
case 'icontains': | ||
return "[contains(" + xpath_lower_case + "," + xpath_to_lower(arg) + ")]"; | ||
case 'empty': | ||
return "[not(*) and not(normalize-space())]"; | ||
case 'enabled': | ||
case 'disabled': | ||
return "[@" + name + "]"; | ||
case 'first-child': | ||
return "[not(preceding-sibling::*)]"; | ||
case 'first': | ||
case 'limit': | ||
case 'first-of-type': | ||
if (arg !== undefined) | ||
return '[position()<=' + arg + ']'; | ||
return '[1]'; | ||
case 'gt': | ||
case 'after': | ||
return '[count(' + css2xpath('preceding::' + arg, true) + ') > 0]'; | ||
case 'after-sibling': | ||
return '[count(' + css2xpath('preceding-sibling::' + arg, true) + ') > 0]'; | ||
case 'before': | ||
return '[count(' + css2xpath('following::' + arg, true) + ') > 0]'; | ||
case 'before-sibling': | ||
return '[count(' + css2xpath('following-sibling::' + arg, true) + ') > 0]'; | ||
case 'checked': | ||
return '[@selected or @checked]'; | ||
case 'contains': | ||
return '[contains(' + xpath_normalize_space + ',' + arg + ')]'; | ||
case 'icontains': | ||
return '[contains(' + xpath_lower_case + ',' + xpath_to_lower(arg) + ')]'; | ||
case 'empty': | ||
return '[not(*) and not(normalize-space())]'; | ||
case 'enabled': | ||
case 'disabled': | ||
return '[@' + name + ']'; | ||
case 'first-child': | ||
return '[not(preceding-sibling::*)]'; | ||
case 'first': | ||
case 'limit': | ||
case 'first-of-type': | ||
if (arg !== undefined) { | ||
return '[position()<=' + arg + ']'; | ||
} | ||
return '[1]'; | ||
case 'gt': | ||
// Position starts at 0 for consistency with Sizzle selectors | ||
return '[position()>' + (parseInt(arg, 10) + 1) + ']'; | ||
case 'lt': | ||
return '[position()>' + (parseInt(arg, 10) + 1) + ']'; | ||
case 'lt': | ||
// Position starts at 0 for consistency with Sizzle selectors | ||
return '[position()<' + (parseInt(arg, 10) + 1) + ']'; | ||
case 'last-child': | ||
return "[not(following-sibling::*)]"; | ||
case 'only-child': | ||
return "[not(preceding-sibling::*) and not(following-sibling::*)]"; | ||
case 'only-of-type': | ||
return "[not(preceding-sibling::*[name()=name(self::node())]) and not(following-sibling::*[name()=name(self::node())])]"; | ||
case 'nth-child': | ||
if (isNumeric(arg)) | ||
return '[(count(preceding-sibling::*)+1) = ' + arg + ']'; | ||
switch (arg) { | ||
case "even": | ||
return "[(count(preceding-sibling::*)+1) mod 2=0]"; | ||
case "odd": | ||
return "[(count(preceding-sibling::*)+1) mod 2=1]"; | ||
default: | ||
var a = (arg || "0").replace(regex_nth_equation, "$1+$2").split("+"); | ||
return '[position()<' + (parseInt(arg, 10) + 1) + ']'; | ||
case 'last-child': | ||
return '[not(following-sibling::*)]'; | ||
case 'only-child': | ||
return '[not(preceding-sibling::*) and not(following-sibling::*)]'; | ||
case 'only-of-type': | ||
return '[not(preceding-sibling::*[name()=name(self::node())]) and not(following-sibling::*[name()=name(self::node())])]'; | ||
case 'nth-child': | ||
if (isNumeric(arg)) { | ||
return '[(count(preceding-sibling::*)+1) = ' + arg + ']'; | ||
} | ||
switch (arg) { | ||
case 'even': | ||
return '[(count(preceding-sibling::*)+1) mod 2=0]'; | ||
case 'odd': | ||
return '[(count(preceding-sibling::*)+1) mod 2=1]'; | ||
default: | ||
var a = (arg || '0').replace(regex_nth_equation, '$1+$2').split('+'); | ||
a[0] = a[0] || "1"; | ||
a[1] = a[1] || "0"; | ||
return "[(count(preceding-sibling::*)+1)>=" + a[1] + " and ((count(preceding-sibling::*)+1)-" + a[1] + ") mod " + a[0] + "=0]"; | ||
} | ||
case 'nth-of-type': | ||
if (isNumeric(arg)) | ||
return '[' + arg + ']'; | ||
switch (arg) { | ||
case "odd": | ||
return "[position() mod 2=1]"; | ||
case "even": | ||
return "[position() mod 2=0 and position()>=0]"; | ||
default: | ||
var a = (arg || "0").replace(regex_nth_equation, "$1+$2").split("+"); | ||
a[0] = a[0] || '1'; | ||
a[1] = a[1] || '0'; | ||
return '[(count(preceding-sibling::*)+1)>=' + a[1] + ' and ((count(preceding-sibling::*)+1)-' + a[1] + ') mod ' + a[0] + '=0]'; | ||
} | ||
case 'nth-of-type': | ||
if (isNumeric(arg)) { | ||
return '[' + arg + ']'; | ||
} | ||
switch (arg) { | ||
case 'odd': | ||
return '[position() mod 2=1]'; | ||
case 'even': | ||
return '[position() mod 2=0 and position()>=0]'; | ||
default: | ||
var a = (arg || '0').replace(regex_nth_equation, '$1+$2').split('+'); | ||
a[0] = a[0] || "1"; | ||
a[1] = a[1] || "0"; | ||
return "[position()>=" + a[1] + " and (position()-" + a[1] + ") mod " + a[0] + "=0]"; | ||
} | ||
case 'eq': | ||
case 'nth': | ||
// Position starts at 0 for consistency with Sizzle selectors | ||
return '[' + (parseInt(arg, 10) + 1) + ']'; | ||
case 'text': | ||
return '[@type="text"]'; | ||
case 'istarts-with': | ||
return "[starts-with(" + xpath_lower_case + ',' + xpath_to_lower(arg) + ')]'; | ||
case 'starts-with': | ||
return "[starts-with(" + xpath_normalize_space + ',' + arg + ')]'; | ||
case 'iends-with': | ||
return "[" + xpath_ends_with(xpath_lower_case, xpath_to_lower(arg)) + "]"; | ||
case 'ends-with': | ||
return "[" + xpath_ends_with(xpath_normalize_space, arg) + "]"; | ||
case 'has': | ||
var xpath = prependAxis(css2xpath(arg, true), './/'); | ||
a[0] = a[0] || '1'; | ||
a[1] = a[1] || '0'; | ||
return '[position()>=' + a[1] + ' and (position()-' + a[1] + ') mod ' + a[0] + '=0]'; | ||
} | ||
case 'eq': | ||
case 'nth': | ||
// Position starts at 0 for consistency with Sizzle selectors | ||
if (isNumeric(arg)) { | ||
return '[' + (parseInt(arg, 10) + 1) + ']'; | ||
} | ||
return "[count(" + xpath + ") > 0]"; | ||
case 'has-sibling': | ||
var xpath = css2xpath('preceding-sibling::' + arg, true); | ||
return '[1]'; | ||
case 'text': | ||
return '[@type="text"]'; | ||
case 'istarts-with': | ||
return '[starts-with(' + xpath_lower_case + ',' + xpath_to_lower(arg) + ')]'; | ||
case 'starts-with': | ||
return '[starts-with(' + xpath_normalize_space + ',' + arg + ')]'; | ||
case 'iends-with': | ||
return '[' + xpath_ends_with(xpath_lower_case, xpath_to_lower(arg)) + ']'; | ||
case 'ends-with': | ||
return '[' + xpath_ends_with(xpath_normalize_space, arg) + ']'; | ||
case 'has': | ||
var xpath = prependAxis(css2xpath(arg, true), './/'); | ||
return "[count(" + xpath + ") > 0 or count(following-sibling::" + xpath.substr(19) + ") > 0]"; | ||
case 'has-parent': | ||
return "[count(" + css2xpath('parent::' + arg, true) + ") > 0]"; | ||
case 'has-ancestor': | ||
return "[count(" + css2xpath('ancestor::' + arg, true) + ") > 0]"; | ||
case 'last': | ||
case 'last-of-type': | ||
if (arg !== undefined) | ||
return "[position()>last()-" + arg + "]"; | ||
return "[last()]"; | ||
case 'selected': // Sizzle: "(option) elements that are currently selected" | ||
return "[local-name()=\"option\" and @selected]"; | ||
case 'skip': | ||
case 'skip-first': | ||
return "[position()>" + arg + "]"; | ||
case 'skip-last': | ||
if (arg !== undefined) | ||
return "[last()-position()>=" + arg + "]"; | ||
return "[position()<last()]"; | ||
case 'root': | ||
return "/ancestor::[last()]"; | ||
case 'range': | ||
var arr = arg.split(','); | ||
return '[count(' + xpath + ') > 0]'; | ||
case 'has-sibling': | ||
var xpath = css2xpath('preceding-sibling::' + arg, true); | ||
return "[" + arr[0] + "<=position() and position()<=" + arr[1] + "]"; | ||
case 'input': // Sizzle: "input, button, select, and textarea are all considered to be input elements." | ||
return '[local-name()="input" or local-name()="button" or local-name()="select" or local-name()="textarea"]'; | ||
case 'internal': | ||
return xpath_internal; | ||
case 'external': | ||
return xpath_external; | ||
case 'http': | ||
case 'https': | ||
case 'mailto': | ||
case 'javascript': | ||
return '[starts-with(@href,concat("' + name + '",":"))]'; | ||
case 'domain': | ||
return '[(string-length(' + xpath_url_domain() + ')=0 and contains(' + xpath_url_domain(xpath_ns_uri) + ',' + arg + ')) or contains(' + xpath_url_domain() + ',' + arg + ')]'; | ||
case 'path': | ||
return '[starts-with(' + xpath_url_path() + ',substring-after("' + arg + '","/"))]' | ||
case 'not': | ||
var xpath = css2xpath(arg, true); | ||
return '[count(' + xpath + ') > 0 or count(following-sibling::' + xpath.substr(19) + ') > 0]'; | ||
case 'has-parent': | ||
return '[count(' + css2xpath('parent::' + arg, true) + ') > 0]'; | ||
case 'has-ancestor': | ||
return '[count(' + css2xpath('ancestor::' + arg, true) + ') > 0]'; | ||
case 'last': | ||
case 'last-of-type': | ||
if (arg !== undefined) { | ||
return '[position()>last()-' + arg + ']'; | ||
} | ||
return '[last()]'; | ||
case 'selected': // Sizzle: "(option) elements that are currently selected" | ||
return '[local-name()="option" and @selected]'; | ||
case 'skip': | ||
case 'skip-first': | ||
return '[position()>' + arg + ']'; | ||
case 'skip-last': | ||
if (arg !== undefined) { | ||
return '[last()-position()>=' + arg + ']'; | ||
} | ||
return '[position()<last()]'; | ||
case 'root': | ||
return '/ancestor::[last()]'; | ||
case 'range': | ||
var arr = arg.split(','); | ||
if (xpath.charAt(0) === '[') | ||
xpath = 'self::node()' + xpath; | ||
return '[not(' + xpath + ')]'; | ||
case 'target': | ||
return '[starts-with(@href, "#")]'; | ||
case 'root': | ||
return 'ancestor-or-self::*[last()]'; | ||
/*case 'active': | ||
return '[' + arr[0] + '<=position() and position()<=' + arr[1] + ']'; | ||
case 'input': // Sizzle: "input, button, select, and textarea are all considered to be input elements." | ||
return '[local-name()="input" or local-name()="button" or local-name()="select" or local-name()="textarea"]'; | ||
case 'internal': | ||
return xpath_internal; | ||
case 'external': | ||
return xpath_external; | ||
case 'http': | ||
case 'https': | ||
case 'mailto': | ||
case 'javascript': | ||
return '[starts-with(@href,concat("' + name + '",":"))]'; | ||
case 'domain': | ||
return '[(string-length(' + xpath_url_domain() + ')=0 and contains(' + xpath_url_domain(xpath_ns_uri) + ',' + arg + ')) or contains(' + xpath_url_domain() + ',' + arg + ')]'; | ||
case 'path': | ||
return '[starts-with(' + xpath_url_path() + ',substring-after("' + arg + '","/"))]' | ||
case 'not': | ||
var xpath = css2xpath(arg, true); | ||
if (xpath.charAt(0) === '[') { | ||
xpath = 'self::node()' + xpath; | ||
} | ||
return '[not(' + xpath + ')]'; | ||
case 'target': | ||
return '[starts-with(@href, "#")]'; | ||
case 'root': | ||
return 'ancestor-or-self::*[last()]'; | ||
/* case 'active': | ||
case 'focus': | ||
@@ -315,21 +332,21 @@ case 'hover': | ||
return '';*/ | ||
case 'lang': | ||
return '[@lang="' + arg + '"]'; | ||
case 'read-only': | ||
case 'read-write': | ||
return '[@' + name.replace('-', '') + ']'; | ||
case 'valid': | ||
case 'required': | ||
case 'in-range': | ||
case 'out-of-range': | ||
return '[@' + name + ']'; | ||
default: | ||
return str; | ||
case 'lang': | ||
return '[@lang="' + arg + '"]'; | ||
case 'read-only': | ||
case 'read-write': | ||
return '[@' + name.replace('-', '') + ']'; | ||
case 'valid': | ||
case 'required': | ||
case 'in-range': | ||
case 'out-of-range': | ||
return '[@' + name + ']'; | ||
default: | ||
return match; | ||
} | ||
}, | ||
}, | ||
css_ids_classes_regex = /(#|\.)([^\#\@\.\/\(\[\)\]\|\:\s\+\>\<\'\"\x1D-\x1F]+)/g, | ||
css_ids_classes_callback = function (str, op, val, offset, orig) { | ||
css_ids_classes_regex = /(#|\.)([^\#\@\.\/\(\[\)\]\|\:\s\+\>\<\'\"\x1D-\x1F]+)/g, | ||
css_ids_classes_callback = function (str, op, val, offset, orig) { | ||
var axis = ''; | ||
/*var prevChar = orig.charAt(offset-1); | ||
/* var prevChar = orig.charAt(offset-1); | ||
if (prevChar.length === 0 || | ||
@@ -341,23 +358,27 @@ prevChar === '/' || | ||
axis = 'node()';*/ | ||
if (op === '#') | ||
return axis + '[@id="' + val + '"]'; | ||
if (op === '#') { | ||
return axis + '[@id="' + val + '"]'; | ||
} | ||
return axis + '[contains(concat(" ",normalize-space(@class)," ")," ' + val + ' ")]'; | ||
}; | ||
}; | ||
// Prepend descendant-or-self if no other axis is specified | ||
function prependAxis(s, axis) { | ||
function prependAxis(s, axis) { | ||
return s.replace(regex_first_axis, function (match, start, literal) { | ||
if (literal.substr(literal.length - 2) === '::') // Already has axis:: | ||
return match; | ||
if (literal.substr(literal.length - 2) === '::') // Already has axis:: | ||
{ | ||
return match; | ||
} | ||
if (literal.charAt(0) === '[') | ||
axis += '*'; | ||
//else if (axis.charAt(axis.length-1) === ')') | ||
if (literal.charAt(0) === '[') { | ||
axis += '*'; | ||
} | ||
// else if (axis.charAt(axis.length-1) === ')') | ||
// axis += '/'; | ||
return start + axis + literal; | ||
return start + axis + literal; | ||
}); | ||
} | ||
} | ||
// Find the begining of the selector, starting at i and working backwards | ||
function selectorStart(s, i) { | ||
function selectorStart(s, i) { | ||
var depth = 0; | ||
@@ -367,54 +388,57 @@ var offset = 0; | ||
while (i--) { | ||
switch (s.charAt(i)) { | ||
case ' ': | ||
case escape_parens: | ||
offset++; | ||
break; | ||
case '[': | ||
case '(': | ||
depth--; | ||
switch (s.charAt(i)) { | ||
case ' ': | ||
case escape_parens: | ||
offset++; | ||
break; | ||
case '[': | ||
case '(': | ||
depth--; | ||
if (depth < 0) | ||
return ++i + offset; | ||
break; | ||
case ']': | ||
case ')': | ||
depth++; | ||
break; | ||
case ',': | ||
case '|': | ||
if (depth === 0) | ||
return ++i + offset; | ||
default: | ||
offset = 0; | ||
if (depth < 0) { | ||
return ++i + offset; | ||
} | ||
break; | ||
case ']': | ||
case ')': | ||
depth++; | ||
break; | ||
case ',': | ||
case '|': | ||
if (depth === 0) { | ||
return ++i + offset; | ||
} | ||
default: | ||
offset = 0; | ||
} | ||
} | ||
return 0; | ||
} | ||
} | ||
// Check if string is numeric | ||
function isNumeric(s) { | ||
function isNumeric(s) { | ||
var num = parseInt(s, 10); | ||
return (!isNaN(num) && '' + num === s); | ||
} | ||
} | ||
// Append escape "char" to "open" or "close" | ||
function escapeChar(s, open, close, char) { | ||
function escapeChar(s, open, close, char) { | ||
var depth = 0; | ||
return s.replace(new RegExp('[\\' + open + '\\' + close + ']', 'g'), function (a) { | ||
if (a === open) | ||
depth++; | ||
if (a === open) { | ||
depth++; | ||
} | ||
if (a === open) { | ||
return a + repeat(char, depth); | ||
} else { | ||
return repeat(char, depth--) + a; | ||
} | ||
if (a === open) { | ||
return a + repeat(char, depth); | ||
} else { | ||
return repeat(char, depth--) + a; | ||
} | ||
}) | ||
} | ||
} | ||
function repeat(str, num) { | ||
function repeat(str, num) { | ||
num = Number(num); | ||
@@ -424,24 +448,27 @@ var result = ''; | ||
while (true) { | ||
if (num & 1) | ||
result += str; | ||
num >>>= 1; | ||
if (num & 1) { | ||
result += str; | ||
} | ||
num >>>= 1; | ||
if (num <= 0) break; | ||
str += str; | ||
if (num <= 0) { | ||
break; | ||
} | ||
str += str; | ||
} | ||
return result; | ||
} | ||
} | ||
function css2xpath(s, nested) { | ||
//s = s.trim(); | ||
function css2xpath(s, nested) { | ||
// s = s.trim(); | ||
if (nested === true) { | ||
// Replace :pseudo-classes | ||
s = s.replace(css_pseudo_classes_regex, css_pseudo_classes_callback); | ||
s = s.replace(css_pseudo_classes_regex, css_pseudo_classes_callback); | ||
// Replace #ids and .classes | ||
s = s.replace(css_ids_classes_regex, css_ids_classes_callback); | ||
s = s.replace(css_ids_classes_regex, css_ids_classes_callback); | ||
return s; | ||
return s; | ||
} | ||
@@ -456,12 +483,13 @@ | ||
s = s.replace(regex_string_literal, function (s, a) { | ||
if (a.charAt(0) === '=') { | ||
a = a.substr(1).trim(); | ||
if (a.charAt(0) === '=') { | ||
a = a.substr(1).trim(); | ||
if (isNumeric(a)) | ||
return s; | ||
} else { | ||
a = a.substr(1, a.length - 2); | ||
if (isNumeric(a)) { | ||
return s; | ||
} | ||
} else { | ||
a = a.substr(1, a.length - 2); | ||
} | ||
return repeat(escape_literal, literals.push(a)); | ||
return repeat(escape_literal, literals.push(a)); | ||
}); | ||
@@ -477,9 +505,11 @@ | ||
while (true) { | ||
var index = s.search(regex_css_wrap_pseudo); | ||
var index = s.search(regex_css_wrap_pseudo); | ||
if (index === -1) break; | ||
index = s.indexOf(':', index); | ||
var start = selectorStart(s, index); | ||
if (index === -1) { | ||
break; | ||
} | ||
index = s.indexOf(':', index); | ||
var start = selectorStart(s, index); | ||
s = s.substr(0, start) + | ||
s = s.substr(0, start) + | ||
'(' + s.substring(start, index) + ')' + | ||
@@ -497,5 +527,5 @@ s.substr(index); | ||
s = s.replace(regex_escaped_literal, function (s, a) { | ||
var str = literals[a.length - 1]; | ||
var str = literals[a.length - 1]; | ||
return '"' + str + '"'; | ||
return '"' + str + '"'; | ||
}) | ||
@@ -521,11 +551,11 @@ | ||
return s; | ||
} | ||
} | ||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { | ||
module.exports = css2xpath; | ||
} else { | ||
window.css2xpath = css2xpath; | ||
} | ||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { | ||
module.exports = css2xpath; | ||
} else { | ||
window.css2xpath = css2xpath; | ||
} | ||
})(); |
{ | ||
"name": "css2xpath", | ||
"version": "0.0.1", | ||
"version": "0.0.3", | ||
"description": "Advanced CSS to XPath converter", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21796
531
8