0

Based on this question additionally to the value of an argument I'm trying to extract the text of a node.

<parents name='Parents'>
  <Parent id='1' name='Parent_1'>
    <Children name='Children'>
      <child name='Child_1' id='1'>child1_Parent_1</child>
      <child name='Child_2' id='2'>child2_Parent_1</child>
      <child name='Child_3' id='3'>child3_Parent_1</child>
      <child name='Child_4' id='4'>child4_Parent_1</child>
    </Children>
  </Parent>
  <Parent id='2' name='Parent_2'>
    <Children name='Children'>
      <child name='Child_1' id='8'>child1_parent2</child>
      <child name='Child_2' id='7'>child2_parent2</child>
      <child name='Child_4' id='6'>child4_parent2</child>
      <child name='Child_3' id='5'>child3_parent2</child>
    </Children>
  </Parent>
</parents>

I would like to get this output when I run a match-query with "child1":

Parent_1
child1_Parent_1

Querying for two values at once

This query:

//Parent[@id='1']/Children/child[text()[matches(.,"^child1","i")]]/(text()|../../@name)

yields:

{}name="Parent_1"
child1_Parent_1

Which comes close to the result I'm looking for. But I would like the clean attribute-value, without the attribute-name.

Clean Attribute-value with xs:string

This can be done with a string-cast:

//Parent[@id='1']/Children/child[text()[matches(.,"^child1","i")]]/xs:string(../../@name)

which gives me a clean attribute-value:

Parent_1

A combination of both queries?

I would imagine it should be possible to combine these two queries:

//Parent[@id='1']/Children/child[text()[matches(.,"^child1","i")]]/(text()|xs:string(../../@name))

but that results in an error which I don't understand:

stdin:26: query failed, Error: Sequence does not match type node()* - found item of type {http://www.w3.org/2001/XMLSchema}string [err:XPTY0004]

I've tried different ways to combine the query, but none of them work. I'm using the dbxml-shell in Berkeley DB XML for testing.

Community
  • 1
  • 1
Christian Benke
  • 467
  • 6
  • 24

3 Answers3

2

To select multiple nodes in XPath 1.0 you can use the union operator |:

//Parent[@id='1']/@name | //Parent[@id='1']/Children/child[@name='Child_1']/text()

XPath 2.0 (which is supported by Berkeley DB XML) allows this:

for $child in //Parent[@id='1']/Children/child[matches(.,"^child1","i")]
return ($child/text(), $child/../../string(@name))

Both would select one attribute node and one text node for your input sample.

Spec: XPath 2.0, section 3.7: For Expressions

Tomalak
  • 306,836
  • 62
  • 485
  • 598
  • Thank you for your answer! The example I've posted initially was flawed, in reality I'm using a more expensive base-query with character-matching - I have updated the example accordingly. I want to avoid to do this match in the base-query twice, if possible with xpath. – Christian Benke Dec 14 '16 at 11:56
  • Neat, thanks! Needed a bit of adaptation for dbxml, but looks good! You may want to add a "../../string(@name))" to your answer, as that was where I ran into problems with the XPath 1.0 query – Christian Benke Dec 14 '16 at 12:25
1

While @Tomalak posted the correct answer, here's how it works in dbxml-shell:

dbxml> prepare "for $child in collection('test.dbxml')//Parent[@id='1']/Children/child[matches(.,'^child1','i')] return ($child/text(), $child/../../string(@name))"
Prepared expression 'for $child in collection('test.dbxml')//Parent[@id='1']/Children/child[matches(.,'^child1','i')] return ($child/text(), $child/../../string(@name))'

dbxml> query
2 objects returned for eager expression 'for $child in collection('test.dbxml')//Parent[@id='1']/Children/child[matches(.,'^child1','i')] return ($child/text(), $child/../../string(@name))'

dbxml> print
child1_Parent_1
Parent_1
Christian Benke
  • 467
  • 6
  • 24
0

I do not know if I understand this correctly, but the following XPath-expression would use concat() and string() to read and then to cocatenate both values you are looking for:

concat(string(//Parent[@id=1]/@name) ,'&#13;&#10;',string(//Parent[@id=1]/Children/child[@name='Child_1']/text()))

In the middle you find '&#13;&#10;', which should insert a line break, but this depends on your actual needs...

Tested this on this online tester

Shnugo
  • 62,351
  • 7
  • 42
  • 92
  • Thanks, but this would be two separate queries. I was hoping this could be done in a single query, similar to the first one I listed. – Christian Benke Dec 14 '16 at 11:28