Get node name with REXML - ruby

I have an XML, which can be like
<?xml version="1.0" encoding="utf-8"?>
<testnode type="1">123</testnode>
or like
<?xml version="1.0" encoding="utf-8"?>
<othernode attrib="true">other value</othernode>
or the root node can be something completely unexpected. (Theoretically anything.)
I'm using REXML to parse it. How can I find out what XML node is the root element?

xml = REXML::Document.new "<?xml version" #etc (or load from file)
root_node = xml.elements[1]
root_node_name = root_node.name

Related

Nokogiri parsing through XML fails

The code:
response = Nokogiri::XML(open('https://geocode-maps.yandex.ru/1.x/?geocode=%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3+%D0%A1%D0%B2%D0%B5%D1%80%D0%B4%D0%BB%D0%BE%D0%B2%D1%81%D0%BA%D0%B0%D1%8F+%D0%BD%D0%B0%D0%B1%D0%B5%D1%80%D0%B5%D0%B6%D0%BD%D0%B0%D1%8F+44%D0%A2'), nil, Encoding::UTF_8.to_s)
lowerCorner = response.xpath("//lowerCorner")
XML document I parse is like:
<?xml version="1.0" encoding="utf-8"?>
<ymaps xmlns="http://maps.yandex.ru/ymaps/1.x" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://maps.yandex.ru/business/1.x http://maps.yandex.ru/schemas/business/1.x/business.xsd http://maps.yandex.ru/geocoder/1.x http://maps.yandex.ru/schemas/geocoder/1.x/geocoder.xsd http://maps.yandex.ru/psearch/1.x http://maps.yandex.ru/schemas/psearch/1.x/psearch.xsd http://maps.yandex.ru/search/1.x http://maps.yandex.ru/schemas/search/1.x/search.xsd http://maps.yandex.ru/web/1.x http://maps.yandex.ru/schemas/web/1.x/web.xsd http://maps.yandex.ru/search/internal/1.x http://maps.yandex.ru/schemas/search/internal/1.x/internal.xsd">
<GeoObjectCollection>
<metaDataProperty xmlns="http://www.opengis.net/gml">
<GeocoderResponseMetaData xmlns="http://maps.yandex.ru/geocoder/1.x">
<request>Санкт-Петербург Свердловская набережная 44Т</request>
<found>1</found>
<results>10</results>
</GeocoderResponseMetaData>
</metaDataProperty>
<featureMember xmlns="http://www.opengis.net/gml">
<GeoObject xmlns="http://maps.yandex.ru/ymaps/1.x" xmlns:gml="http://www.opengis.net/gml" gml:id="1">
<metaDataProperty xmlns="http://www.opengis.net/gml">
<GeocoderMetaData xmlns="http://maps.yandex.ru/geocoder/1.x">
<kind>house</kind>
<text>Россия, Санкт-Петербург, Свердловская набережная, 44Т</text>
<precision>exact</precision>
</GeocoderMetaData>
</metaDataProperty>
<Envelope>
<lowerCorner>30.397902 59.959183</lowerCorner>
<upperCorner>30.406113 59.9633</upperCorner>
</Envelope>
</boundedBy>
<Point xmlns="http://www.opengis.net/gml">
<pos>30.402008 59.961242</pos>
</Point>
</GeoObject>
</featureMember>
</GeoObjectCollection>
</ymaps>
I'd like to get lowerCorner, but nothing from official and others sources does work:
response.xpath('//lowerCorner')
response.search('//lowerCorner')
response.xpath('xmlns:lowerCorner')
response.xpath('xmlns:lowerCorner', ns).text
response.css('lowerCorner')
The only result is: []
So how to parse lowerCorner's content?
Removing the namespaces (or using them in your path) should help.
Try this:
require "nokogiri"
require "open-uri"
response = Nokogiri::XML(open('https://geocode-maps.yandex.ru/1.x/?geocode=%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3+%D0%A1%D0%B2%D0%B5%D1%80%D0%B4%D0%BB%D0%BE%D0%B2%D1%81%D0%BA%D0%B0%D1%8F+%D0%BD%D0%B0%D0%B1%D0%B5%D1%80%D0%B5%D0%B6%D0%BD%D0%B0%D1%8F+44%D0%A2'), nil, Encoding::UTF_8.to_s)
response.remove_namespaces! # <<<<<<<
lower_corner = response.xpath("/ymaps/GeoObjectCollection/featureMember/GeoObject/boundedBy/Envelope/lowerCorner").first
p lower_corner.text #> "30.397902 59.959183"

Adding new child nodes to an existing XML file with Ruby & Nokogiri

I have this server project in Ruby, and I would like to keep tracks of events and user sessions in a XML file. I'm totally new to this, and after days of research, I'm hitting a wall.
Here's my current sample code, assuming there's already a file named "test.xml" that contains a root node called
$ cat test.xml
<server></server>
and the code :
require 'nokogiri'
require 'securerandom'
logintime = Time.now
sessionid = SecureRandom.hex(10)
file = File.open("test.xml",'a+')
doc = Nokogiri::XML.parse file
session_node = Nokogiri::XML::Node.new("session",doc)
session_node['id'] = sessionid
logintime_node = Nokogiri::XML::Node.new("logintime",doc)
logintime_node.content = logintime
session_node << logintime_node
doc.root << session_node
file.print doc.to_xml
file.close
and here's the test.xml file after 4 runs
<server></server>
<?xml version="1.0"?>
<server>
<session id="5ef27ade2afaf5c2162f">
<logintime>2015-07-07 17:27:20 +0200</logintime>
</session>
</server>
<?xml version="1.0"?>
<server>
<session id="637595bd0857c8af1cc0">
<logintime>2015-07-07 17:27:36 +0200</logintime>
</session>
</server>
<?xml version="1.0"?>
<?xml version="1.0"?>
<server>
<session id="41e6082c4db7d1dc8692">
<logintime>2015-07-07 17:27:37 +0200</logintime>
</session>
</server>
<?xml version="1.0"?>
<?xml version="1.0"?>
<server>
<session id="1cad6c3d38d4fb96632b">
<logintime>2015-07-07 17:27:38 +0200</logintime>
</session>
</server>
<?xml version="1.0"?>
And the desired output should be something like this :
<?xml version="1.0"?>
<server>
<session id="5ef27ade2afaf5c2162f">
<logintime>2015-07-07 17:27:20 +0200</logintime>
</session>
<session id="637595bd0857c8af1cc0">
<logintime>2015-07-07 17:27:36 +0200</logintime>
</session>
<session id="41e6082c4db7d1dc8692">
<logintime>2015-07-07 17:27:37 +0200</logintime>
</session>
<session id="1cad6c3d38d4fb96632b">
<logintime>2015-07-07 17:27:38 +0200</logintime>
</session>
</server>
And I really don't know why should I do to obtain that result.
First, if there's no existing file containing the root node, the script run only once, then complains that there's already a root node when I try to run it a second time :
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/gems/2.0.0/gems/nokogiri-1.5.6/lib/nokogiri/xml/document.rb:232:in `add_child': Document already has a root node (RuntimeError)
from /Users/xxx/nokogiri.rb:13:in `<top (required)>'
from -e:1:in `load'
from -e:1:in `<main>'
So... I'm kinda lost here. Any ideas ?
The problem is you're opening your file in append mode with File.open('test.xml', 'a+') and then writing the entire XML doc to it with file.print doc.to_xml. That's why you end up with the entire document written several times into the file.
If you read and write the file independently, the XML doc will replace the file the way you want. If you need to handle the file not existing yet, you can also check for it and initialize the data with your <server> root tag.
require 'nokogiri'
require 'securerandom'
logintime = Time.now
sessionid = SecureRandom.hex(10)
# Read or initialize the data
if File.exist?('test.xml')
data = File.read("test.xml")
else
data = '<server></server>'
end
doc = Nokogiri::XML.parse data
session_node = Nokogiri::XML::Node.new("session",doc)
session_node['id'] = sessionid
logintime_node = Nokogiri::XML::Node.new("logintime",doc)
logintime_node.content = logintime
session_node << logintime_node
doc.root << session_node
# Write the document to disk
File.open('test.xml', 'w') do |file|
file.print doc.to_xml
end
I wouldn't recommend logging sessions this way for long. At any significant user load, writing the file will become very expensive. Also, if you have multiple servers running, they'll all be clobbering the file out from under one another. When you get to that point, you should at least convert your storage to a database, or even better use something like an ELK Stack that's built for this.

Reading xml nodes using VB script

I have an xml file that I want to read using VBScript (Technology limitation). Below is the code and xml file. I am able to read the file if there is no DTD element involved but the code doesn't work for file having DTD and xml-style element.
Code-
Dim xmlDoc1:Set xmlDoc1 = CreateObject("MSXML2.DomDocument")
xmlDoc1.async=False
xmlDoc1.load "C:\ABC.xml"
Dim xmlTCID:Set xmlTCID = xmlDoc1.selectNodes("//*")
For nNodeCount = 0 To xmlTCID.length
MsgBox(xmlTCID(nNodeCount).nodeName)
Next
ABC.xml -
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE RESULT SYSTEM "Result.dtd"[]>
<?xml-stylesheet type="text/xsl" href="Result.xsl"?>
<SUMMARY>
<TITLE>Test</TITLE>
</SUMMARY>
<IDS>
<DATA>
<NAME>A</NAME>
<VALUE>PASS</VALUE>
</DATA>
<DATA>
<NAME>B</NAME>
<VALUE>PASS</VALUE>
</DATA
<DATA>
<NAME>C</NAME>
<VALUE>FAIL</VALUE>
</DATA
</IDS>
<IDS>
<DATA>
<NAME>A</NAME>
<VALUE>PASS</VALUE>
</DATA>
<DATA>
<NAME>B</NAME>
<VALUE>FAIL</VALUE>
</DATA
</IDS>
Note - If I avoid -
<!DOCTYPE RESULT SYSTEM "Result.dtd"[]>
<?xml-stylesheet type="text/xsl" href="Result.xsl"?>
The above code is able to read the nodes but with the above two lines in xml file, it gives the below error -
Requirement - I need to read the name of last DATA node with FAIL for each IDS node.
Any suggestion as what to do to get the code working even with -
<!DOCTYPE RESULT SYSTEM "Result.dtd"[]>
<?xml-stylesheet type="text/xsl" href="Result.xsl"?>
As there are problems with your XML - more than one top level element, miising ">" - setting the ProhibitDTD Property to False won't solve all of your tasks.
xmlDoc.validateOnParse=False
worked for me.

Cannot use XPATH to get a value from SOAP response

Please help me to get value (013b92124ce54924) in "<web:prop value='013b92124ce54924' name='serviceId'></web:prop>"
I tried to use the xpath command unsuccessful:
/*/SOAP-ENV:Body/'ns':requestResponse/web:webapiResponse/web:data/web:prop[#name='serviceId']/#value
My SOAP response is as below:
`
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns:requestResponse xmlns:ns='http://www.phahahotel.com/BPS/sWebAPI'>
<web:webapiResponse xmlns:web='http://www.phahahotel.com/BPS/WebService' action='CreateServiceTel' transId='10100000009' clientId='svrcore'>
<web:respCode>0000</web:respCode>
<web:respDescription>Success</web:respDescription>
<web:data>
<web:prop value='SERTYPE' name='serviceType'></web:prop>
<web:prop value='013b92124ce54924' name='serviceId'></web:prop>
</web:data>
</web:webapiResponse>
</ns:requestResponse>
</SOAP-ENV:Body>
`
Thanks,
Remove the quotes around ns :-
% xpath test.xml "/*/SOAP-ENV:Body/ns:requestResponse/web:webapiResponse/web:data/web:prop[#name='serviceId']/#value"
Found 1 nodes:
-- NODE --
value="013b92124ce54924"

Replace all occurrences except the first in Ruby. The regular expression spans multiple lines

I am trying to down my last 3200 tweets in groups of 200(in multiple pages) using restclient gem.
In the process, I end up adding the following lines multiple times to my file:
</statuses>
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
To get this right(as the XML parsing goes for a toss), after downloading the file, I want to replace all occurrences of the above string except the first.
I am trying the following:
tweets_page = RestClient.get("#{GET_STATUSES_URL}&page=#{page_number}")
message = <<-MSG
</statuses>
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
MSG
unless page_number == 1
tweets_page.gsub!(message,"")
end
What is wrong in the above? Is there a better way to do the same?
I believe it would be faster to download the whole bunch at once and split the body of your response by message and add it for the first entry.
Something like this, can't try it out so consider this just as an idea.
tweets_page = RestClient.get("#{GET_STATUSES_URL}").body
tweets = tweets_page.split(message)
tweets_page = tweets[0]+message+tweets[1..-1]
You could easily break them up in groups of 200 like that also
If you want to do it with a gsub on the whole text you could use the following
tweets_page = <<-MSG
first
</statuses>
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
second
</statuses>
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
rest
MSG
message = <<-MSG
</statuses>
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
MSG
new_str = tweets_page.gsub message do |match|
if defined? #first
""
else
#first = true
message
end
end
p new_str
gives
type=\"array\">\nrest\n"
"first\n</statuses>\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<statuses type=\"array\">\nsecond\nrest\n"

Resources