How to add a new node without prefix - ruby

I'm working with a SOAP API that requires some XML nodes without prefixes. Is it even possible to do with Nokogiri? Simply omitting the prefix from the node name makes Nokogiri use the default prefix "env".
node = Nokogiri::XML::Node.new('WageReportsToIR', envelope)
envelope.xpath('//env:Body').first.add_child(node)
results
<env:Body>\n <env:WageReportsToIR/>\n </env:Body>
Do I have any other option but to write a regex to remove the prefixes after I'm done editing the XML with Nokogiri?

Related

xmllint / Xpath extract parent node where child contains text from google shopping feed

I am trying to extract all "item" nodes containing a g:custom_label_0 with the text value "2020-2021"
So far, I manage to find all nodes containing the child g:custom_label_0, but I don't manage to filter by the text value of the field.
Here is the example XML:
<item>
<description>[...]</description>
<g:availability>in stock</g:availability>
<g:brand>Barts</g:brand>
<g:condition>new</g:condition>
<g:custom_label_0>2020-2021</g:custom_label_0>
<g:id>108873/10-3</g:id>
<g:image_link>[...]</g:image_link>
<g:price>26.99 EUR</g:price>
<g:sale_price>26.99 EUR</g:sale_price>
<g:shipping>
<g:country>NL</g:country>
<g:price>4.50 EUR</g:price>
</g:shipping>
<g:shipping_weight>7.95</g:shipping_weight>
<link>[....]</link>
</item>
...
There is nodes containing other values than 2020-2021, but I want to extract all complete item nodes containing this text.
Here's what I made in order to extract all nodes having the field available.
xmllint --xpath '//item["g:custom_label_0"]' myfile.xml
i tried adding a text filter via square brackets etc. but I have the feeling the quotation around the custom_label_0 might cause trouble. Adding more filters within the quotes gets accepted (no error), but I won't be able to add more quotations inside to filter the string.
Does work, throws no error:
xmllint --xpath '//item["g:custom_label_0[text()]"]' myfile.xml
If I wanted to filter the text now, I need to use quotations again. Escaping them breaks the code. How can i further filter down the text "2020-2021" when both types of quotation marks are already used?
You're right; the quotes around g:custom_label_0 is causing trouble. That makes it a string and that is always true so it will return all item elements.
The g: is a namespace prefix. To bind a namespace to a prefix in xmllint, you have to use it in shell mode (see https://stackoverflow.com/a/8266075/317052 for an example).
An alternative is to test the element name to select the g:custom_label_0 element and then test the value of that element to see if it's 2020-2021.
Example...
xmllint --xpath '//item[*[name()="g:custom_label_0"][.="2020-2021"]]' myfile.xml

Using a regex to get a Nokogiri node

I'm parsing an XML file with Nokogiri.
Currently, I'm using the following to get the value I need (the document includes multiple Phase nodes):
xml.xpath("//Phase[#text=' = STER P=P(T) ']")
But now, the uploaded XML file can have a text attribute with a different value. Thus, I'm trying to update my code using a regular expression since the value always contains STER.
After looking at a few questions on SO, I tried
xml.xpath("//Phase[#text~=/STER/]")
However, when I run it, I get
ERROR: Invalid predicate: //Phase[#text~=/STER/] (Nokogiri::XML::XPath::SyntaxError)
What am I missing here?
Alternatively, is there an XPATH function similar to starts-with` that looks for the substring within the entire value and not just at the beginning of it?
There are two problems with your code: first off, there is no =~ operator in XPath. The way to test whether text matches a regex is using the matches function:
//Phase[matches(#text, 'STER')]
Secondly, regex matching is a feature of XPath 2.0, but Nokogiri implements XPath 1.0.
Luckily, you are not actually using any regex features, you are simply checking for a fixed string, which can be done with XPath 1.0 using the contains function:
//Phase[contains(#text, 'STER')]

Do I need specify namespaces in xpath?

I am reading docs, and it's seems that namespaces are needed mostly for xsd-scheme and generation some other formats from XML. But I can't understand do I need to use them in XPATH. Nothing do not stop me to specify path to element without namespace.
The path without a namespace is a path to elements in the empty namespace. Nothing can stop you specifying a path without namespaces, but such a path only matches elements without namespaces.
For example, /root/a/text() returns 1, but /root/ns:a/text() returns 2:
<root xmlns:ns="some:namespace">
<a>1</a>
<ns:a>2</ns:a>
</root>
Both of the texts can be selected by /root/*[local-name()='a']/text().

Parsing XPath using Nokogiri

I am writing some scripts to change some values in config (XML) files. The script will take XPath expressions and replacement values to be replaced in a source document.
If the node is found in the source document, then the value will be replaced, but if the node is not found, I need to create a new element and add required elements with attributes.
For example, in a web.config if appSetting exists, then change its value, if not then create a new one
/configuration/appSettings/add[#key='ClientValidationEnabled']/#value
I'm wondering if it's possible to read the XPath as an expression that lets me walk it and create a new element if needed.

How can I use Nokogiri with Ruby to replace values in existing xml?

I am using Ruby 1.9.3 with the lastest Nokogiri gem. I have worked out how to extract values from an xml using xpath and specifying the path(?) to the element. Here is the XML file I have:
<?xml version="1.0" encoding="utf-8"?>
<File xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Houses>
<Ranch>
<Roof>Black</Roof>
<Street>Markham</Street>
<Number>34</Number>
</Ranch>
</Houses>
</File>
I use this code to print a value:
doc = Nokogiri::XML(File.open ("C:\\myfile.xml"))
puts doc.xpath("//Ranch//Street")
Which outputs:
<Street>Markham</Street>
This is all working fine but what I need is to write/replace the value. I want to use the same kind of path-style lookup to pass in a value to replace the one that is there. So I want to pass a street name to this path and overwrite the street name that is there. I've been all over the internet but can only find ways to create a new XML or insert a completely new node in the file. Is there a way to replace values by line like this? Thanks.
You want the content= method:
Set the Node’s content to a Text node containing string. The string gets XML escaped, not interpreted as markup.
Note that xpath returns a NodeSet not a single Node, so you need to use at_xpath or get the single node some other way:
doc = Nokogiri::XML(File.open ("C:\\myfile.xml"))
node = doc.xpath("//Ranch//Street")[0] # use [0] to select the first result
node.content = "New value for this node"
puts doc # produces XML document with new value for the node

Resources