How can I compare map-like elements with XMLUnit - xmlunit

I have below xml data:
Control:
<Data>
<propertyValues>
<propertyName>Name1</propertyName>
<value>
<text>
<value>Value1</value>
</text>
</value>
</propertyValues>
<propertyValues>
<propertyName>Name2</propertyName>
<value>
<text>
<value>Value2</value>
</text>
</value>
</propertyValues>
</Data>
Test:
<Data>
<propertyValues>
<propertyName>Name2</propertyName>
<value>
<text>
<value>Value2</value>
</text>
</value>
</propertyValues>
<propertyValues>
<propertyName>Name1</propertyName>
<value>
<text>
<value>Value1</value>
</text>
</value>
</propertyValues>
</Data>
And I would expect these 2 documents are "same".
How can I config xmlUnit to make it work? (I'm using xmlunit 2.6.3)
Thanks
Leon

This is pretty similar to the running example of the "Selecting Nodes" part of XMLUnit's User Guide.
You need to use an ElementSelector that picks the correct propertyValues element when looking at the list of elements and then decides to compare the elements that contain the same nested text inside the only child element named propertyName. This directly translates into
ElementSelectors.conditionalBuilder()
.whenElementIsNamed("propertyValues")
.thenUse(ElementSelectors.byXPath("./propertyName", ElementSelectors.byNameAndText))
...
and then you need to add whatever other rules are required to make the rest work. Looking at the visible rest of your example there are no ambiguous children and a simple
...
.elseUse(ElementSelectors.byName)
.build();
will do.

Related

Can I get xpath count value in robot framework

Assume the following XML:
<data>
<node id="1" />
<node id="2" />
<node id="12" />
<node id="16" />
</data>
This xpath expression should be valid:
count(//node)
.. and should produce the number 4
I'm new to robot frameworks. Is it possible to use this xpath in robot framework?
for example something like:
${value}= Get something something source=${xml} xpath=count(//node)
The one below works but I would like the xpath to produce the end value, not a list.
#{nodelist}= Get Elements ${xml} xpath=node
Length Should Be ${nodelist} 4
Edit
I know that I can count the nodes in a list of nodes. However, I would like to get the absolute value (integer or string) using xpath. Now I need to write different code depending on if the xpath result is a node, list or attribute when the xpath could theoretically produce the final value.
You can use the Get Element Count Keyword it returns the number of elements matching the locator
You can do something as simple as this
${count} = Get Element Count name:div_name
Should Be True ${count} > 2
For more info on Keywords Have a look at this Keyword Page
When working with XML it is generally best to use the XML library. In the below example you'll find a solution for counting the elements using the XML library Get Element Count.
data.xml
<data>
<node id="1" />
<node id="2" />
<node id="12" />
<node id="16" />
</data>
Testcase.robot
*** Settings ***
Library XML
Library OperatingSystem
*** Test Cases ***
TC
${xml} Get File ./data.xml
${count} Get Element Count ${xml} xpath=node
Should Be Equal As Integers ${count} ${4}

Simplify specific XPath expression

I would like to know if the following XPath expression can be simplified:
//map[requester/#type='2' and requester/code]
Some test data:
<root>
<map>
<requester type="2">
<code>a</code>
<code>b</code>
</requester>
</map>
...
</root>
My objective is to get only map elements which have at least one requester with type attribute and value '2' and also have at least one code element.
For your use case, this is probably as simple as it could be. However, it doesn't match what you are describing doing.
Here you are selecting map elements where
There is a requester element with type attribute equal to 2
There is a requester element with a code element
The requester elements in (1) and (2) are not necessarily the same
For example, the map element in the following is selected:
<root>
<map>
<requester type="2"/>
<requester>
<code>a</code>
</requester>
</map>
</root>
If you want the elements in (1) and (2) to be the same, you should use (simplified slightly at the suggestion of kjhughes)
//map[requester[#type='2']/code]
Here we select all map elements which have a requester element which in turn has an attribute type with a value of 2 and a code element.

contains in xpath only checks first match

Taking this xml piece as example:
<list>
<element>
<title>Element 1</title>
<group>Group 1</group>
</element>
<element>
<title>Element 2</title>
<group>Group 1</group>
</element>
<element>
<title>Element 3</title>
<group>Group 1</group>
<group>Group 2</group>
</element>
<element>
<title>Element 4</title>
<group>Group 2</group>
<group>Group 3</group>
</element>
</list>
To get all groups I use the following xpath:
//group/text()
and it works fine -I remove duplicates later in python using a set as I don't know if I can do it with xpath-. But When I want to get the elements that contain "Group 3" I try the following xpath:
//element[contains(group/text(), "Group 3")]
and I get an empty result. While when I search elements that contain "Group 1" with:
//element[contains(group/text(), "Group 1")]
I get the correct result with 3 elements. And if I look for "Group 2" I get a wrong result with only one element.
What I'm not taking into account? How can I make those searches by group?
contains can only test, if a string occurs in another string. group/text() is not a string, it is a set/sequence of nodes.
You can use
//element[group/text() = "Group 1"]

Ruby + Nokogiri + Xpath navigate Node_Set

<Item id="item0">
<Links>
<FirstLink id="link1" target="one"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content</String>
</Data>
</Item>
<Item id="item1">
<Links>
<FirstLink id="link1" target="two"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content</String>
</Data>
</Item>
I have created a Nokogiri-NodeSet with this structure, i.e. a list of items with links and data children.
How can I filter any items that don't match a certain value in the 'target'-attribute of <FirstLink>?
Actually, what I want in the end is to extract the <Data><String>-Content of every <Item> that matches a certain value in it's <FirstLink> "Target"-Attribute.
I've tried several approaches already but I'm at a loss as to how to identify an element by an attribute of it's grandchild, then extracting the content of this grandchild's parent's sibling, X(.
We can build up an XPath expression to do this. Assuming we are starting from the whole XML document, rather than the node-set you already have, something like
//Item
will select all <Item> elements (I’m guessing you already have something like that to get this node-set).
Next, to select only those <Item> elements which have <Links><FirstLink> where FirstLink has a target attribute value of one:
//Item[Links/FirstLink[#target='one']]
and finally to select the Data/String children of those nodes:
//Item[Links/FirstLink[#target='one']]/Data/String
So with Nokogiri you could use something like this (where doc is your parsed document):
doc.xpath("//Item[Links/FirstLink[#target='one']]/Data/String")
or if you want to use the node-set you already have you can use a relative expression:
nodeset.xpath("self::Item[Links/FirstLink[#target='one']]/Data/String")
I completely didn't understand what your goal is. But using a guess, I am trying to show you, how to proceed in this case :
require 'nokogiri'
doc = Nokogiri::XML <<-xml
<Item id="item0">
<Links>
<FirstLink id="link1" target="one"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content1</String>
</Data>
</Item>
<Item id="item1">
<Links>
<FirstLink id="link1" target="two"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content2</String>
</Data>
</Item>
xml
#xpath method with the expression "//Item", will select all the Item nodes. Then those Item nodes will be passed to the #reject method to select only those nodes, that has a node called Links having the target attribute value is "one". If any of the links, either FirstLink or SecondLink has the target attribute value "one", for that nodes grandparent node Item will be selected.
node.at("//Links/FirstLink")['target'] will give you the string say "one" which is a value of target attribute of the node, FirstLink of first Item nodes , then "two" from the second Item node. The part ['any vaue'] in node.at("//Links/FirstLink")['target']['any vaue'] is a call to the String#[] method.
Remember below approach will give you the flexibility of the use regular expression too.
nodeset = doc.xpath("//Item").reject do |node|
node.at("//Links/FirstLink")['target']['any vaue']
end
Now nodeset contains only the required Item nodes. Now I use #map, passing each item node inside it to collect the content of the String node. Then #at method with an expression //Data/String, will select the String node. Then #text, will give you the content of each String node.
nodeset.map { |n| n.at('//Data/String').text } # => ["content1"]

Get all preceding and following siblings for a given node

I have the following XML snippet:
<root>
<CharacteristicUse>
<UseArea>Specification_Characteristics</UseArea>
<Value>
<ValueID>123</ValueID>
</Value>
<Value>
<ValueID>444</ValueID>
</Value>
<Value>
<ValueID>555</ValueID>
</Value>
<Value>
<ValueID>777</ValueID>
</Value>
<Value>
<ValueID>888</ValueID>
</Value>
</CharacteristicUse>
</root>
I want to be able to get all preceding and following siblings of the Value node which has its child node ValueID with text = 555
I have been trying to combine the following way:
/root/CharacteristicUse/Value[ValueID='555']/following-sibling::* | preceding-sibling::*
But, it only returns to me the following siblings.
Is it possible to have one single query to get the output as:
<Value>
<ValueID>123</ValueID>
</Value>
<Value>
<ValueID>444</ValueID>
</Value>
<Value>
<ValueID>777</ValueID>
</Value>
<Value>
<ValueID>888</ValueID>
</Value>
One way is brute-force:
/root/CharacteristicUse/Value[ValueID='555']/preceding-sibling::* | /root/CharacteristicUse/Value[ValueID='555']/following-sibling::*
This includes the "UseArea" node also, not sure if you want that.
Or if what you want is just all the values that are NOT a particular value, then
/root/CharacteristicUse/Value[not(ValueID='555')]
is more direct.
I found the answer.
The xpath looks like:
/root/CharacteristicUse/Value[ValueID='555']/following-sibling::Value | /root/CharacteristicUse/Value[ValueID='555']/preceding-sibling::Value

Resources