Permalink
Browse files

support more css selector syntax with @attr.

  • Loading branch information...
1 parent c93ef58 commit b31d5db64abad74dbad8e3372855d81b844d5d33 @tomill committed Feb 10, 2010
Showing with 195 additions and 12 deletions.
  1. +6 −5 lib/Template/Semantic.pm
  2. +29 −7 lib/Template/Semantic/Document.pm
  3. +160 −0 t/11_exp_to_xpath.t
View
@@ -183,7 +183,7 @@ The 1st parameter is the input template that can take these types:
my $out = Template::Semantic->process($fh, $vars);
my $out = Template::Semantic->process(\*DATA, $vars);
-The 2nd parameter is a value set to bind the template. This should be hash-ref
+The 2nd parameter is a value set to bind the template. $vars should be hash-ref
like { 'selector' => $value, 'selector' => $value, ... }. See below
L</SELECTOR> and L</VALUE TYPE> section.
@@ -202,24 +202,25 @@ Use XPath expression or CSS selector as a selector.
print Template::Semantic->process($template, {
- # XPath sample that indicate tag:
+ # XPath sample that indicate <tag>
'/html/body/h2[2]' => ...,
'//title | //h1' => ...,
'//img[@id="foo"]' => ...,
'id("foo")' => ...,
- # XPath sample that indicate attribute:
+ # XPath sample that indicate @attr
'//a[@id="foo"]/@href' => ...,
'//meta[@name="keywords"]/@content' => ...,
- # CSS selector sample that indicate tag:
+ # CSS selector sample that indicate <tag>
'title' => ...,
'.foo span.bar' => ...,
'#foo' => ...,
- # CSS selector sample that indicate attribute:
+ # CSS selector sample that indicate @attr
'img#foo@src' => ...,
'span.bar a@href' => ...,
+ '@alt, @title' => ...,
});
@@ -54,6 +54,20 @@ sub as_string {
}
}
+my $element_with_attr_regex = qr{
+ ^
+ \s*
+ (
+ \@[^@]+? |
+ (?:
+ (?: [^"]+? | .+?"[^"]+".+? )
+ (?: \@[^@]+? )?
+ )
+ )
+ \s*
+ (?: , | $ )
+}x;
+
sub _exp_to_xpath {
my ($self, $exp) = @_;
return unless $exp;
@@ -64,14 +78,22 @@ sub _exp_to_xpath {
} elsif ($exp =~ m{^id\(}) {
$xpath = $exp;
$xpath =~ s{^id\((.+?)\)}{//\*\[\@id=$1\]}g; # id() hack
- } else { # css selector
- my ($elem, $attr) = $exp =~ m{(.*?)/?(@[^/]+)?$}; # extends @attr syntax
- if ($elem) {
- $xpath = HTML::Selector::XPath::selector_to_xpath($elem);
- $xpath .= "/$attr" if $attr;
- } elsif ($attr) {
- $xpath = "//$attr";
+ } else {
+ # css selector extends @attr syntax
+ my @x;
+ while ($exp =~ s/$element_with_attr_regex//) {
+ my $e = $1;
+ my ($elem, $attr) = $e =~ m{(.*?)/?(@[^/@]+)?$};
+ my $x;
+ if ($elem) {
+ my $x = HTML::Selector::XPath::selector_to_xpath($elem);
+ $x .= "/$attr" if $attr;
+ push @x, $x;
+ } elsif ($attr) {
+ push @x, "//$attr";
+ }
}
+ $xpath = join " | ", @x;
}
$xpath;
}
View
@@ -16,53 +16,213 @@ run {
};
__DATA__
+
===
--- exp
/
--- xpath
/
+
===
--- exp
/foo
--- xpath
/foo
+
===
--- exp
//
--- xpath
//
+
===
--- exp
./
--- xpath
./
+
===
--- exp
.
--- xpath
.
+
===
--- exp
.foo
--- xpath
//*[contains(concat(' ', @class, ' '), ' foo ')]
+
===
--- exp
id("foo")
--- xpath
//*[@id="foo"]
+
===
--- exp
foo@attr
--- xpath
//foo/@attr
+
===
--- exp
foo/@attr
--- xpath
//foo/@attr
+
===
--- exp
@attr
--- xpath
//@attr
+
+===
+--- exp
+*@foo
+--- xpath
+//*/@foo
+
+===
+--- exp
+E F@foo
+--- xpath
+//E//F/@foo
+
+===
+--- exp
+E > F@foo
+--- xpath
+//E/F/@foo
+
+===
+--- exp
+E:first-child@foo
+--- xpath
+//*[1]/self::E/@foo
+
+===
+--- exp
+F E:first-child@foo
+--- xpath
+//F//*[1]/self::E/@foo
+
+===
+--- exp
+F > E:first-child@foo
+--- xpath
+//F/*[1]/self::E/@foo
+
+===
+--- exp
+E:lang(c)@foo
+--- xpath
+//E[@xml:lang='c' or starts-with(@xml:lang, 'c-')]/@foo
+
+===
+--- exp
+E + F@foo
+--- xpath
+//E/following-sibling::*[1]/self::F/@foo
+
+===
+--- exp
+E + #bar@foo
+--- xpath
+//E/following-sibling::*[1]/self::*[@id='bar']/@foo
+
+===
+--- exp
+E + .bar@foo
+--- xpath
+//E/following-sibling::*[1]/self::*[contains(concat(' ', @class, ' '), ' bar ')]/@foo
+
+===
+--- exp
+E[foo]@foo
+--- xpath
+//E[@foo]/@foo
+
+===
+--- exp
+E[foo="warning"]@foo
+--- xpath
+//E[@foo='warning']/@foo
+
+===
+--- exp
+E[foo~="warning"]@foo
+--- xpath
+//E[contains(concat(' ', @foo, ' '), ' warning ')]/@foo
+
+===
+--- exp
+E[lang|="en"]@foo
+--- xpath
+//E[@lang='en' or starts-with(@lang, 'en-')]/@foo
+
+===
+--- exp
+DIV.warning@foo
+--- xpath
+//DIV[contains(concat(' ', @class, ' '), ' warning ')]/@foo
+
+===
+--- exp
+E#myid@foo
+--- xpath
+//E[@id='myid']/@foo
+
+===
+--- exp
+E:nth-child(1)@foo
+--- xpath
+//E[count(preceding-sibling::*) = 0]/@foo
+
+===
+--- exp
+@foo, bar
+--- xpath
+//@foo | //bar
+
+--- exp
+foo, @bar
+--- xpath
+//foo | //@bar
+
+===
+--- exp
+ foo, @bar
+--- xpath
+//foo | //@bar
+
+===
+--- exp
+@foo, @bar
+--- xpath
+//@foo | //@bar
+
+===
+--- exp
+ @foo, bar
+--- xpath
+//@foo | //bar
+
+===
+--- exp
+#foo@foo, bar@bar, @baz
+--- xpath
+//*[@id='foo']/@foo | //bar/@bar | //@baz
+
+===
+--- exp
+a[foo="@xxx,yyy"]@foo, bar@bar
+--- xpath
+//a[@foo='@xxx,yyy']/@foo | //bar/@bar
+
+===
+--- exp
+a[foo="xxx,yyy"], b[bar="@aaa"]@bar, c@baz
+--- xpath
+//a[@foo='xxx,yyy'] | //b[@bar='@aaa']/@bar | //c/@baz
+

0 comments on commit b31d5db

Please sign in to comment.