12

I want to search div id in an html doc with certain pattern. I want to match this pattern in regex:

foo_([[:digit:]]{1.8})

using xpath. What is the xpath equivalent for the above pattern?

I'm stuck with //div[@id="foo_ and then what? If someone could continue a legal expression for it.

EDIT

Sorry, I think I have to elaborate more. Actually it's not foo_, it's post_message_

Btw, I use mechanize/nokogiri ( ruby )

Here's the snippet :

html_doc = Nokogiri::HTML(open(myfile))
message_div =  html_doc.xpath('//div[substring(@id,13) = "post_message_" and substring-after(@id, "post_message_") => 0 and substring-after(@id, "post_message_") <= 99999999]') 

Still failed. Error message:

Couldn't evaluate expression '//div[substring(@id,13) = "post_message_" and substring-after(@id, "post_message_") => 0 and substring-after(@id, "post_message_") <= 99999999]' (Nokogiri::XML::XPath::SyntaxError)

Gumbo
  • 643,351
  • 109
  • 780
  • 844
mhd
  • 4,561
  • 10
  • 37
  • 53

4 Answers4

16

How about this (updated):

XPath 1.0:

"//div[substring-before(@id, '_') = 'foo' 
       and substring-after(@id, '_') >= 0 
       and substring-after(@id, '_') <= 99999999]"

Edit #2: The OP made a change to the question. The following, even more reduced XPath 1.0 expression works for me:

"//div[substring(@id, 1, 13) = 'post_message_' 
       and substring(@id, 14) >= 0 
       and substring(@id, 14) <= 99999999]"

XPath 2.0 has a convenient matches() function:

"//div[matches(@id, '^foo_\d{1,8}$')]"

Apart from the better portability, I would expect the numerical expression (XPath 1.0 style) to perform better than the regex test, though this would only become noticeable when processing large data sets.


Original version of the answer:

"//div[substring-before(@id, '_') = 'foo' 
       and number(substring-after(@id, '_')) = substring-after(@id, '_') 
       and number(substring-after(@id, '_')) &gt;= 0 
       and number(substring-after(@id, '_')) &lt;= 99999999]"

The use of the number() function is unnecessary, because the mathematical comparison operators coerce their arguments to numbers implicitly, any non-numbers will become NaN and the greater than/less than tests will fail.

I also removed the encoding of the angle brackets, since this is an XML requirement, not an XPath requirement.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Thanks a lot! it works now,I use your Xpath 1.0 solution due to better support of Xpath 1.0 in lib-xml. Fyi,Nokogiri has different quotes syntax (see my code above). All in all, Accepted and voted-up :) – mhd Mar 03 '09 at 11:27
6

As already pointed out, in XPath 2.0 it would be good to use its standard regex capabilities with a function like the matches() function.

One possible XPath 1.0 solution:

//div[starts-with(@id, 'post_message_')
    and
      string-length(@id) = 21
    and
      translate(substring-after(@id, 'post_message_'), 
                '0123456789', 
                ''
                )
     =
      ''
      ] 

Do note the following:

  1. The use of the standard XPath function starts-with().

  2. The use of the standard XPath function string-length().

  3. The use of the standard XPath function substring-after().

  4. The use of the standard XPath function translate().

Tomalak
  • 332,285
  • 67
  • 532
  • 628
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • Thanks for the up-vote. ;-) Nice alternative solution, though it does not fulfill the {1,8} conditon the OP asked for. But that is easily fixed with a string-length() test. – Tomalak Feb 28 '09 at 23:31
  • @Tomalak Sorry, I am on a 2-day trip and only have minutes free time to look at SO problems. I'll edit this now. – Dimitre Novatchev Mar 01 '09 at 01:23
2

Or use xpath function matches(string,pattern).

  <xsl:if test="matches(name(.),'foo_')">

Unfortunately it's not regex, but it might be enough unless you have other foo_ tags you don't need, then I Guess you can add a few more "if" checks to cull them out.

Robert Gould
  • 68,773
  • 61
  • 187
  • 272
  • matches *is* a regexp function, see http://www.w3.org/TR/xpath-functions/#func-matches: "The function returns true if $input matches the regular expression ..." – phihag Feb 28 '09 at 13:16
  • Awesome! I had under estimated matches. But now I need to go back and refactor, my archaic searches ! – Robert Gould Mar 01 '09 at 02:39
0

Nikkou makes this very easy and readable:

doc.search('div').attr_matches('id', /post_message_\d{1,8}/)
Tom
  • 1,007
  • 12
  • 13