# Xpath to attributes

**URL:** <https://www.ruby-forum.com/t/xpath-to-attributes/71692>\
**Category:** Ruby\
**Created:** [February 22, 2006, 3:14am UTC](https://www.ruby-forum.com/t/xpath-to-attributes/71692 "2006-02-22T03:14:46Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![lstrecv](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/e68b1a/32.png) [@lstrecv](https://www.ruby-forum.com/u/lstrecv)\
**Post date:** [February 22, 2006, 3:14am UTC](https://www.ruby-forum.com/t/xpath-to-attributes/71692/1 "2006-02-22T03:14:46Z")

</div>

Is there anyway to do generic xpath’s, for both elements and attributes  
(ie //customer/phone/@type --\> “home”)? REXML can do xpath to fetch  
elements (rexml\_element.text(’//customer/phone’)) but can’t seem to use  
xpath to find attributes.

---

<div class="post-metadata">

**Author:** ![lstrecv](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/e68b1a/32.png) [@lstrecv](https://www.ruby-forum.com/u/lstrecv)\
**Post date:** [February 22, 2006, 11:39am UTC](https://www.ruby-forum.com/t/xpath-to-attributes/71692/2 "2006-02-22T11:39:08Z")

</div>

On Wed, 2006-02-22 at 10:33 +0900, removed\_email\_address@domain.invalid wrote:

> Is there anyway to do generic xpath’s, for both elements and attributes  
> (ie //customer/phone/@type --\> “home”)? REXML can do xpath to fetch  
> elements (rexml\_element.text(’//customer/phone’)) but can’t seem to use  
> xpath to find attributes.

```
d = REXML::Document.new <<EOX
  <customer>
    <phone type='home'>0115</phone>
    <phone type='mobi'>07807</phone>
  </customer>
EOX

m = REXML::XPath.match(d, '//customer/phone[@type = "home"]')
p m.to_s
# => <phone type='home'>0115</phone>
```

---

<div class="post-metadata">

**Author:** ![lstrecv](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/e68b1a/32.png) [@lstrecv](https://www.ruby-forum.com/u/lstrecv)\
**Post date:** [February 23, 2006, 5:09am UTC](https://www.ruby-forum.com/t/xpath-to-attributes/71692/3 "2006-02-23T05:09:11Z")

</div>

removed\_email\_address@domain.invalid wrote:

> Is there anyway to do generic xpath’s, for both elements and attributes  
> (ie //customer/phone/@type --\> “home”)? REXML can do xpath to fetch  
> elements (rexml\_element.text(’//customer/phone’)) but can’t seem to use  
> xpath to find attributes.

class Array; alias atr first; alias txt last end  
class String  
def xtag(s)  
scan( %r!  
\< #{s} (?: \s+ ( [^\>]\* ) )? / \>  
|  
\< #{s} (?: \s+ ( [^\>]\* ) )? \>  
( ._? ) \</ #{s} \>  
!mx ).  
map{ |unpaired, attr, data| h = { }  
attr = ( unpaired || attr )  
if attr  
attr.scan( %r! ( \S+ ) = ( ["’] ) ( ._? ) \2 !x ){ |k,q,v|  
h[k] = v }  
end  
[h, data]  
}  
end  
def xpath(s)  
s.scan(%r! [^/"]+ (?: “[^”]_" )? !x).inject([[nil,self]]){|ary,str|  
if “@” == str[0,1]  
str =~ /@(._?)="(.\*)"/  
ary.select{|a,t| a[$1] == $2 }  
else  
return [] if [] == ary  
ary[0].txt.xtag(str)  
end  
}  
end  
end

p DATA.read.xpath(‘customer/@loc=“south”/phone/@type=“mobi”’)

**END**  
  
0115  
07807  
  
  
0319  
09802

---

<div class="post-metadata">

**Author:** ![lstrecv](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/e68b1a/32.png) [@lstrecv](https://www.ruby-forum.com/u/lstrecv)\
**Post date:** [February 24, 2006, 5:05pm UTC](https://www.ruby-forum.com/t/xpath-to-attributes/71692/4 "2006-02-24T17:05:03Z")

</div>

Thanks.

But I’d like to just extract the attribute value via Xpath.

If my Xpath is correct, I could just do ‘//customer/phone/@type’ to get  
‘home’. Is there any way I can do this in Ruby?

---

<div class="post-metadata">

**Author:** ![lstrecv](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/l/e68b1a/32.png) [@lstrecv](https://www.ruby-forum.com/u/lstrecv)\
**Post date:** [February 25, 2006, 9:34pm UTC](https://www.ruby-forum.com/t/xpath-to-attributes/71692/5 "2006-02-25T21:34:43Z")

</div>

removed\_email\_address@domain.invalid wrote:

> Thanks.
> 
> But I’d like to just extract the attribute value via Xpath.
> 
> If my Xpath is correct, I could just do ‘//customer/phone/@type’ to get  
> ‘home’. Is there any way I can do this in Ruby?

p DATA.read.xchain(‘customer/@loc=“south”/phone’).first.atr[‘type’]

**END**  
  
0115  
07807  
  
  
0319

The rest:

class Array; alias atr first; alias txt last end  
class String  
def xtag(s)  
result = []  
scan( %r!  
\< #{s} (?: \s+ ( [^\>]\* ) )? / \>  
|  
\< #{s} (?: \s+ ( [^\>]\* ) )? \>  
( ._? ) \</ #{s} \>  
!mx )   
{ |unpaired, attr, data| h = { }  
attr = ( unpaired || attr )  
attr.scan( %r{ ( \S+ ) = ( ["’] ) ( ._? ) \2 }x ){ |k,q,v|  
h[k] = v } if attr  
block\_given? ? ( yield [h, data] ) : result \<\< [h, data]  
}  
result  
end  
def xchain(s)  
s.scan(%r! [^/"]+ (?: “[^”]_" )? !x).inject([[nil,self]]){|ary,str|  
if “@” == str[0,1]  
str =~ /@(._?)="(.\*)"/  
ary.select{|a,t| a[$1] == $2 }  
else  
return [] if [] == ary  
ary[0].txt.xtag(str)  
end  
}  
end  
end
