diff --git a/src/ecmascript/spidermonkey/css2xpath.c b/src/ecmascript/spidermonkey/css2xpath.c index aae09802..ba5d4613 100644 --- a/src/ecmascript/spidermonkey/css2xpath.c +++ b/src/ecmascript/spidermonkey/css2xpath.c @@ -94,20 +94,28 @@ explode(char delim, std::string const & s) result.push_back(std::move(token)); } + if (s.back() == delim) + { + result.push_back(std::string()); + } + return result; } std::string preg_replace(std::string & pattern, const char *replacement, std::string & subject) { - return std::regex_replace(subject, std::regex(pattern), replacement); + try { + return std::regex_replace(subject, std::regex(pattern), replacement); + } catch (const std::regex_error &e) { + std::cout << e.what() << " " << pattern << "\n"; + } } using namespace std; typedef std::string (*my_callback)(const std::smatch &m); - std::string preg_replace_callback(std::string & pattern, my_callback callback, std::string & subject) { @@ -136,7 +144,7 @@ class Translator; class Rule { public: - std::string apply(std::string &selector) + virtual std::string apply(std::string &selector) { return selector; } @@ -153,30 +161,133 @@ class RegexRule : public Rule RegexRule(const char *pat, const char *repl) : pattern(pat), replacement(repl) { } + std::string apply(std::string &selector) + { + std::string r(pattern); + +// std::cout << "RegexRule: pattern=" << r << " replacement=" << replacement << " selector=" << selector << "\n"; + + return preg_replace(r, replacement, selector); + } }; class NotRule : public Rule { + private: + Translator *t; public: - NotRule(Translator *t) - { - } + NotRule(Translator *tt); + std::string apply(std::string &selector); + std::string callback(const std::smatch &matches); }; +std::string +not_rule_callback(const std::smatch &m); + +class NotRule *currentNotRule; + + +static bool +is_digits(const std::string &str) +{ + return std::all_of(str.begin(), str.end(), ::isdigit); // C++11 +} + +class NthChildRule; + +class NthChildRule *currentNth; + +std::string +nth_callback(const std::smatch &m); + class NthChildRule : public Rule { public: NthChildRule() { + currentNth = this; + } + + std::string apply(std::string & selector) + { +std::cout << "NthChildRule " << selector << "\n"; + std::string pat("([a-zA-Z0-9_\\-*]+):nth-child\\(([^)]*)\\)"); + return preg_replace_callback(pat, nth_callback, selector); + } + + std::string callback(const std::smatch &matches) + { +// std::cout << matches[0] << " " << matches[1] << " " << matches[2] << std::endl; + std::ostringstream os; + std::ostringstream res; + os << matches[2]; + + if (os.str() == "n") + { + res << matches[1]; + } + else if (os.str() == "even") + { + res << matches[1] << "[(count(preceding-sibling::*) + 1) mod 2=0]"; + } + else if (os.str() == "odd") + { + res << matches[1] << "[(count(preceding-sibling::*) + 1) mod 2=1]"; + } + else if (is_digits(os.str())) + { + res << "*[" << matches[2] << "]/self::" << matches[1]; + } + else + { +// std::cout << "else" << std::endl; + + std::string pat("^([\\d]*)n.*?([\\d]*)$"); + std::string m = matches[2].str(); + std::string b1 = preg_replace(pat, "$1+$2", m); +//std::cout << b1 << std::endl; + auto b = explode('+', b1); +//std::cout << b[0] << std::endl; +//std::cout << b[1] << std::endl; + res << matches[1] << "[(count(preceding-sibling::*)+1)>=" << b[1] << " and ((count(preceding-sibling::*)+1)-" + << b[1] << ") mod " << b[0] << "=0]"; + } + + return res.str(); } }; +std::string +nth_callback(const std::smatch &m) +{ + auto fun = std::bind(&NthChildRule::callback, ::currentNth, std::placeholders::_1); + + return fun(m); +} + +std::string +not_rule_callback(const std::smatch &m) +{ + auto fun = std::bind(&NotRule::callback, ::currentNotRule, std::placeholders::_1); + + return fun(m); +} + class DollarEqualRule : public Rule { public: DollarEqualRule() { } + + std::string apply(std::string &selector) + { +// std::cout << "DollarEqualRule: selector=" << selector << "\n"; + + std::string pattern("\\[([a-zA-Z0-9\\_\\-]+)\\$=([^\\]]+)\\]"); + + return preg_replace_callback(pattern, dollar_equal_rule_callback, selector); + } }; class Translator @@ -218,7 +329,7 @@ private: // all descendant or self to // new RegexRule("(^|[^a-zA-Z0-9\\_\\-\\*])([#\\.])([a-zA-Z0-9\\_\\-]+)", "$1*$2$3"), new RegexRule("([\\>\\+\\|\\~\\,\\s])([a-zA-Z\\*]+)", "$1//$2"), - new RegexRule("\\s+\\/\\//", "//"), + new RegexRule("\\s+\\/\\/", "//"), // :first-child new RegexRule("([a-zA-Z0-9\\_\\-\\*]+):first-child", "*[1]/self::$1"), @@ -275,11 +386,37 @@ private: new RegexRule(":root", "/"), // use * when tag was omitted - new RegexRule("^\[", "*["), + new RegexRule("^\\[", "*["), new RegexRule("\\|\\[", "|*[") }; }; +NotRule::NotRule(Translator *tt) : t(tt) +{ + currentNotRule = this; +} + +std::string +NotRule::apply(std::string &selector) +{ +// std::cout << "NotRule: selector=" << selector << "\n"; + + std::string pat("([a-zA-Z0-9\\_\\-\\*]+):not\\(([^\\)]*)\\)"); + return preg_replace_callback(pat, not_rule_callback, selector); +} + +std::string +NotRule::callback(const std::smatch &matches) +{ + std::string m(matches[2].str()); + std::string pat("^[^\\[]+\\[([^\\]]*)\\].*$"); + std::string ret(t->translate(m)); + std::string subresult = preg_replace(pat, "$1", ret); + return matches[1].str() + "[not(" + subresult + ")]"; +} + + + #if 1 std::string @@ -296,8 +433,8 @@ typedef const char *test[2]; void tests() { - test provider[] = { +#if 1 {"div", "//div"}, {"body div", "//body//div"}, {"div p", "//div//p"}, @@ -326,7 +463,6 @@ tests() {"div[class|=dialog]", "//div[@class=\"dialog\" or starts-with(@class,concat(\"dialog\",\"-\"))]"}, {"div[class!=made_up]", "//div[not(@class) or @class!=\"made_up\"]"}, {"div[property!=\"made_up\"]", "//div[not(@property) or @property!=\"made_up\"]"}, - {"div[class~=example]", "//div[contains(concat(\" \",normalize-space(@class),\" \"),concat(\" \",\"example\",\" \"))]"}, {"div:not(.example)", "//div[not(contains(concat(\" \",normalize-space(@class),\" \"),\" example \"))]"}, {"p:contains(selectors)", "//p[contains(string(.),\"selectors\")]"}, @@ -336,6 +472,7 @@ tests() {"p:nth-child(3n+8)", "//p[(count(preceding-sibling::*)+1)>=8 and ((count(preceding-sibling::*)+1)-8) mod 3=0]"}, {"p:nth-child(2n+1)", "//p[(count(preceding-sibling::*)+1)>=1 and ((count(preceding-sibling::*)+1)-1) mod 2=0]"}, {"p:nth-child(3)", "//*[3]/self::p"}, +#endif {"p:nth-child(4n)", "//p[(count(preceding-sibling::*)+1)>=0 and ((count(preceding-sibling::*)+1)-0) mod 4=0]"}, {"p:only-child", "//*[last()=1]/self::p"}, {"p:last-child", "//p[not(following-sibling::*)]"}, @@ -358,8 +495,13 @@ tests() std::string expected(t[1]); std::string result = translator->translate(selector); - std::cout << selector << " " << result << " " << expected << " "; + std::cout << t[0] << " "; std::cout << ((result == expected) ? "\033[32mOK\033[0m" : "\033[31mFAIL\033[0m"); + +// if (result != expected) +// { +// std::cout << " " << result << " " << expected; +// } std::cout << "\n"; } } @@ -367,6 +509,7 @@ tests() int main(int argc, char **argv) { +#if 0 #if 1 std::vector x; @@ -398,6 +541,16 @@ main(int argc, char **argv) std::string pat2 = "(\\d{2}/\\d{2}/)(\\d{4})"; std::cout << preg_replace_callback(pat2, next_year, text); +#endif + +#if 0 + auto v = explode('+', "4+"); + + for (auto e : v) + { + std::cout << e << "\n"; + } +#endif tests();