Forum: Ruby REXML get specific element

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
59193d8082ee0fe75675d3bb376cf2ba?d=identicon&s=25 Pierre Pierre (pierre_p)
on 2009-03-18 06:18
All,

I'm trying for the first time to play with XML and Ruby.
Since I have only the core library at the moment, my choice went
directly for REXML.

So, here is my problem.
I'm willing to get a specific element in my xml file.

Normally I would do something like this:
doc.root.elements["programmer[@name='Matz']"]

But let's say I have a user interface, and I let a user enter the
programmer he wants to see. How can I look for an element using a
variable?
I hoped something like this would work:
entered_name = 'Matz'
doc.root.elements["magician[@name=#{entered_name}]"]
Unfortunately, it didn't...

After trying a few things, check with uncle google and in this forum,
I'm driving out of clues, so I'd appreciate if someone could show me the
correct way!

Thanks.
59193d8082ee0fe75675d3bb376cf2ba?d=identicon&s=25 Pierre Pierre (pierre_p)
on 2009-03-18 06:19
Obviously, in the second example, it's 'programmer' instead of
'magician' ;-)
(Copy paste failure in order to leave some magician's privacy :p)
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-03-18 13:13
(Received via mailing list)
2009/3/18 Pierre Pat <theyojimbo@gmail.com>:
> doc.root.elements["programmer[@name='Matz']"]
>
> But let's say I have a user interface, and I let a user enter the
> programmer he wants to see. How can I look for an element using a
> variable?
> I hoped something like this would work:
> entered_name = 'Matz'
> doc.root.elements["magician[@name=#{entered_name}]"]

You forgot the single quotes around #{}, i.e. you must only replace
"Matz" with "#{entered_name}":

doc.root.elements["programmer[@name='#{entered_name}']"]

> Unfortunately, it didn't...
>
> After trying a few things, check with uncle google and in this forum,
> I'm driving out of clues, so I'd appreciate if someone could show me the
> correct way!

Cheers

robert
4e39ce958e5ab660993b8b175b129803?d=identicon&s=25 Henrik Hodne (Guest)
on 2009-03-18 14:48
(Received via mailing list)
Hello,


On 18 Mar 2009, at 06:15, Pierre Pat <theyojimbo@gmail.com> wrote:

> doc.root.elements["programmer[@name='Matz']"]
>
> But let's say I have a user interface, and I let a user enter the
> programmer he wants to see. How can I look for an element using a
> variable?
> I hoped something like this would work:
> entered_name = 'Matz'
> doc.root.elements["magician[@name=#{entered_name}]"]
> Unfortunately, it didn't...

You are missing the '-s.
Try
doc.root.elements["programmer[@name='#{entered_name}']"]
>
Regards,
Henrik Hodne
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-03-18 14:55
(Received via mailing list)
> You are missing the '-s.
> Try
> doc.root.elements["programmer[@name='#{entered_name}']"]

Then that croaks if the name contains an apostrophe.

Try (IIRC)...

   XPath.first(doc, '//programmer[ @name = $name ]', :name =>
entered_name)
54404bcac0f45bf1c8e8b827cd9bb709?d=identicon&s=25 7stud -- (7stud)
on 2009-03-18 20:26
Phlip wrote:
>> You are missing the '-s.
>> Try
>> doc.root.elements["programmer[@name='#{entered_name}']"]
>
> Then that croaks if the name contains an apostrophe.
>
> Try (IIRC)...
>
>    XPath.first(doc, '//programmer[ @name = $name ]', :name =>
> entered_name)

That doesn't work for me.  And I couldn't figure out the escaping
necessary to handle both a name entered with a single quote or a double
quote.  I thought I understood ruby escaping, but not anymore.

Here is the easy case:

1)
<xml version="1.0" encoding="ISO-8859-1"?>
<programmers>
        <programmer age="50">John</programmer>
        <programmer age="30">Sally</programmer>
</programmers>


require 'rexml/document'
include REXML

f = File.new('xml4.xml')
doc = Document.new(f)

input_name = gets
input_name = input_name.chomp

target = XPath.match(doc, "//programmer[text() = 'John']")
puts target[0].attributes["age"]

-------

$ ruby r1test.rb
John
50


Now suppose the programmer's name is Joh'n

<xml version="1.0" encoding="ISO-8859-1"?>
<programmers>
        <programmer age="50">Joh'n</programmer>
        <programmer age="30">Sally</programmer>
</programmers>


This is one of my favorite attempts to escape a single quote in the
input name:

input_name = "Joh'n"
p input_name
input_name = input_name.gsub("'", "\\'")
p input_name

--output:--
"Joh'n"
"Johnn"

Can anyone explain what's going on there?
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-03-18 20:40
(Received via mailing list)
7stud -- wrote:

> That doesn't work for me.  And I couldn't figure out the escaping
> necessary to handle both a name entered with a single quote or a double
> quote.  I thought I understood ruby escaping, but not anymore.

You understand Ruby escaping - just not XPath escaping. There is none.
If you
use ' to delimit a string, you cannot use ' inside it. I would love to
be proven
wrong, but I did research this for quite a while.

Why didn't my $name substitution work? It's in the rdocs...

> f = File.new('xml4.xml')
> doc = Document.new(f)
>
> input_name = gets
> input_name = input_name.chomp
>
> target = XPath.match(doc, "//programmer[text() = 'John']")
> puts target[0].attributes["age"]

Wouldn't this work?

target = XPath.match(doc, "//programmer[text() = $name]", :name =>
$john)

> input_name = input_name.gsub("'", "\\'")
> p input_name
>
> --output:--
> "Joh'n"
> "Johnn"
>
> Can anyone explain what's going on there?

XPath does not escape ticks with \.
54404bcac0f45bf1c8e8b827cd9bb709?d=identicon&s=25 7stud -- (7stud)
on 2009-03-18 21:11
Phlip wrote:
> 7stud -- wrote:
>
>> That doesn't work for me.  And I couldn't figure out the escaping
>> necessary to handle both a name entered with a single quote or a double
>> quote.  I thought I understood ruby escaping, but not anymore.
>
> You understand Ruby escaping - just not XPath escaping. There is none.

Ah.  Ok.


> If you
> use ' to delimit a string, you cannot use ' inside it. I would love to
> be proven
> wrong, but I did research this for quite a while.
>


> Why didn't my $name substitution work? It's in the rdocs...
>
>> f = File.new('xml4.xml')
>> doc = Document.new(f)
>>
>> input_name = gets
>> input_name = input_name.chomp
>>
>> target = XPath.match(doc, "//programmer[text() = 'John']")
>> puts target[0].attributes["age"]
>
> Wouldn't this work?
>
> target = XPath.match(doc, "//programmer[text() = $name]", :name =>
> $john)
>

I don't know what you are trying to do there.  What is $john?  This
doesn't work for me:

<xml version="1.0" encoding="ISO-8859-1"?>
<programmers>
        <programmer age="50">Joh'n</programmer>
        <programmer age="30">Sally</programmer>
</programmers>


require 'rexml/document'
include REXML

f = File.new('xml4.xml')
doc = Document.new(f)

input_name = gets
input_name = input_name.chomp
p input_name

target = XPath.match(doc, "//programmer[text() = $name]", :name =>
$input_name)
p target

puts target[0].attributes["age"]


--output:--
$ ruby r1test.rb
Joh'n
"Joh'n"
[]
r1test.rb:14: undefined method `attributes' for nil:NilClass
(NoMethodError)


I thought the question you presented was how are you going to be able to
handle all these cases in the xml:

<programmer age="50">John</programmer>
<programmer age="50">Joh'n</programmer>
<programmer age="50">Joh"n</programmer>

The user may type in: John, or Joh'n, or Joh"n.
54404bcac0f45bf1c8e8b827cd9bb709?d=identicon&s=25 7stud -- (7stud)
on 2009-03-18 21:19
The following code works when the user types in John or Joh'n but not
Joh"n:

<xml version="1.0" encoding="ISO-8859-1"?>
<programmers>
        <programmer age="50">Joh'n</programmer>
        <programmer age="30">John</programmer>
        <programmer age="20">Joh"n</programmer>
</programmers>


require 'rexml/document'
include REXML

f = File.new('xml4.xml')
doc = Document.new(f)

input_name = gets
input_name = input_name.chomp
p input_name

target = XPath.match(doc, "//programmer[text() = \"#{input_name}\"]")
p target

puts target[0].attributes["age"]


$ruby r1test.rb
Joh"n
"Joh\"n"
/usr/lib/ruby/1.8/rexml/xpath_parser.rb:124:in `internal_parse':
undefined method `node_type' for "Joh":String (NoMethodError)
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:123:in `each'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:123:in
`internal_parse'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:49:in `match'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:402:in `Predicate'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:346:in `Predicate'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:204:in
`internal_parse'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:199:in `times'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:199:in
`internal_parse'
         ... 9 levels...
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:49:in `match'
        from /usr/lib/ruby/1.8/rexml/xpath_parser.rb:34:in `parse'
        from /usr/lib/ruby/1.8/rexml/xpath.rb:59:in `match'
        from r1test.rb:11
09348009e57e24e10bbc08d925bf69ca?d=identicon&s=25 Matthias Reitinger (reima)
on 2009-03-18 22:58
7stud -- wrote:
> This doesn't work for me:
>
> <xml version="1.0" encoding="ISO-8859-1"?>
> <programmers>
>         <programmer age="50">Joh'n</programmer>
>         <programmer age="30">Sally</programmer>
> </programmers>
>
>
> require 'rexml/document'
> include REXML
>
> f = File.new('xml4.xml')
> doc = Document.new(f)
>
> input_name = gets
> input_name = input_name.chomp
> p input_name
>
> target = XPath.match(doc, "//programmer[text() = $name]", :name =>
> $input_name)
> p target
>
> puts target[0].attributes["age"]

There are several flaws in your code:  First, $input_name is not the
same variable as input_name.  Second, according to the REXML
documentation the variables hash for the XPath query is in fact the
fourth argument to XPath#match, not the third.  The third one is for the
namespaces hash.  And lastly, you have to use strings as keys here, not
symbols (took me some time to figure that one out).  So this does the
trick:

  require 'rexml/document'
  include REXML

  doc = Document.new(DATA)

  input_name = gets.chomp
  p input_name

  target = XPath.match(doc, "//programmer[text()=$name]", {}, "name" =>
input_name)
  p target

  puts target[0].attributes["age"]

  __END__
  <xml version="1.0" encoding="ISO-8859-1"?>
  <programmers>
    <programmer age="50">John</programmer>
    <programmer age="50">Joh'n</programmer>
    <programmer age="50">Joh"n</programmer>
  </programmers>


-Matthias
59193d8082ee0fe75675d3bb376cf2ba?d=identicon&s=25 Pierre Pierre (pierre_p)
on 2009-03-19 01:30
Thanks guys!
I knew it had to be something small, just couldn't figure it out.

It really helped!
Thanks again
54404bcac0f45bf1c8e8b827cd9bb709?d=identicon&s=25 7stud -- (7stud)
on 2009-03-19 02:40
Matthias Reitinger wrote:
> So this does the
> trick:
>
>
>   target = XPath.match(doc, "//programmer[text()=$name]", {}, "name" =>
> input_name)

Nice.  I downloaded the REXML docs and took a look around.  A question:
how in the hell would anyone know to use a $ variable in there, and that
a hash specified as the 4th argument would somehow match a value to that
variable, when all the docs say is:

match(element, path=nil, namespaces=nil, variables={})
59193d8082ee0fe75675d3bb376cf2ba?d=identicon&s=25 Pierre Pierre (pierre_p)
on 2009-03-19 03:55
Matthias Reitinger wrote:
>   target = XPath.match(doc, "//programmer[text()=$name]", {}, "name" =>
> input_name)
>   p target

Since I'm now digging one level deeper in my xml and again trying to
locate a specific element, I thought I'd try XPath, which should be
easier.

But that example you gave is to locate an element knowing the text
value, then showing the attribute.
I'm trying to do the opposite, looking for an element depending on its
attribute's value.

I looked in XPath and the Functions module it's using, I can't find any
method to do that :o

Is there any way to do that?

Regards
59193d8082ee0fe75675d3bb376cf2ba?d=identicon&s=25 Pierre Pierre (pierre_p)
on 2009-03-19 05:11
Ha, actually, I hadn't worked with XPath before, so I didn't really know
what it was about.
It's actually pretty cool, you can access any elements directly.
for instance:
the_lang = 'ruby'
doc.elements["//language[@lang='#{the_lang}']"]
for an xml which would have a language tag inside the programmer one
with lang as attributes.

Learning and enjoying :-)
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-03-19 07:40
(Received via mailing list)
7stud -- wrote:

>> target = XPath.match(doc, "//programmer[text() = $name]", :name =>
>> $john)
>>
>
> I don't know what you are trying to do there.  What is $john?

It is a typo.

 > This
> doesn't work for me:
>
> target = XPath.match(doc, "//programmer[text() = $name]", :name =>
> $input_name)

Take the $ off. Sorry, but $ is also valid Ruby, meaning "a global
variable, yet
different from 'input_name'".
This topic is locked and can not be replied to.