I'm working using an external Webservice which I consume using the Savon gem.
I want to process the response of the WebService, before Savon, in order to clean the XML and get the correct Hash to work with. Currently, the Savon call method, answers with the Hash:
{:envelope => {
:body => {
:get_method_result => {
:result=>"OK",
:dataset_xml => "
<NewDataSet>
<xs:schema id=\"NewDataSet\" xmlns=\"\"........
Wich, as you can see, after dataset_xml has an XML string. So I have to take this and process it in order to have a full Hash.
All of this is happening because my response has thing like: <NewDataSet>\r\n <xs:schema id=\"NewDataSet\" xmlns=\ inside it's XML, which if I could be able to fix, then I wouldn't need to do all the after-process to turn it into a Hash.
You could simply try to parse the xml your self with nokogiri gem. Have you tried that already?
I would simply try
Nokogiri::XML(response[:body])
A friend solved it, he added a module named mod_substitute to Apache. I used it to parse the incoming XML, extracting the CDATA characters. With tha being done, the Savon gem received a clean XML which was parsed perfectly, in one step, to a Hash.
<Location />
AddOutputFilterByType SUBSTITUTE text/html
Substitute "s|CDATAREGEX|' '|i"
</Location>
Related
I have this Sinatra::Base code:
class Crush < Sinatra::Base
post '/upload' do
erb params.inspect
end
end
I am using Postman and its interface for uploading a file. So I send a POST request with form-data, where in the body of the request the name is hello and the value is a file test.txt which contains just a simple string hey there.
When I do params.inspect I get this long string
{"------WebKitFormBoundaryocOEEr26iZGSe75n\r\nContent-Disposition: form-data; name"=>"\"hello\"; filename=\"test.txt\"\r\nContent-Type: text/plain\r\n\r\nhey there\r\n------WebKitFormBoundaryocOEEr26iZGSe75n--\r\n"}
So basically a long has with a single key and a single value. Reading most Sinatra tutorials (where the file is accepted from a form), there's a nice way Sinatra handles this using params[:file], but this doesn't seem to be the case when the file is coming straight from the body of an HTTP request.
I tried a non-modular approach too withou Sinatra::Base, thinking it's some parsing middle-ware missing, but got the same result.
Is there something I'm missing here? Must I go and make my own custom parser to get the content of this long hash? Or is there an easier way?
I figured it's Postman issue. When I switch from 'x-www-form-urlencoded' to 'form-data' in Postman, in the Header section, the field: Content-Type => application/x-www-form-urlencoded is NOT removed. So for those who encounter this problem, make sure you remove it manually.
I'm a bit stumped as to why our new operations are not being picked up when using the Savon gem but are when using a simple curl command.
For example when using Savon
#client = Savon.client(wsdl: "#url-here", basic_auth: %w(username password))
#client.operations
:operation_name
# At this point i expect the operation to be OperationName
When I then run a curl request
curl #url-here --user username:password
Within the XML response is
<operation name="OperationName">
We recently changed the naming convention of our operations to camel case (previously using snake case) so the tests used to pass but now they fail as not picking up the new operations (When using Savon)
Would there be any reason for this ?
Thanks
Since version 1 of Savon:
Your service probably uses (lower)CamelCase names for actions and params, but Savon maps those to snake_case Symbols for you.
I assume, Savon mapped your new camel case actions to snake case. So if you had updated your tests - you shouldn't.
I have been trying to know how to build a SOAP XML request having different tags with different namespaces through Savon - as that shown below - but haven't been able to do it yet. Could anybody please help me? The request written below was generated by SOAPUI (linked it a WSDL). Notice the urn and urn1 prefixes for namespaces. I use Savon 2, Ruby 1.9 and Rails 3.
<soapenv:Envelope xmlns:urn="urn:safetypay:messages:mws:api" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn1="urn:safetypay:schema:mws:api">
<soapenv:Body>
<urn:ExpressTokenRequest>
<urn1:Language>ES</urn1:Language>
<urn:Signature>2984764ac8214cca0a44e06badaba4bbc93ed381af59199f65d9c</urn:Signature>
</urn:ExpressTokenRequest>
</soapenv:Body>
</soapenv:Envelope>
Thank you in advance :)
I WANT TO SHARE THE SOLUTION:
Finally, I was able to puzzle it out. Notice that the code posted in this forum is not exactly the real one for security and/or brevity purposes (it's incomplete).
# Instance of the Savon client using the respective wsdl. Notice the two (multiple) namespaces.
# Also, see the namespace prefix urn (making it different to the default one).
# In addition,the code changed the envelope namespace prefix to soapenv.
# While debugging in console (irb), I used the parameters log: true, pretty_print_xml: true inside the following client instance too (e.g. ...env_namespace: :soapenv, log: true, pretty_print_xml: true... ). This allowed me to see very easily the XML Savon was sending when requesting.
# Remember to require the necessary resources. Maybe require 'savon', require 'digest',...
client = Savon.client(wsdl: "/MerchantExpressWs.wsdl", namespaces: {"xmlns:urn" => "urn:safetypay:messages:mws:api", "xmlns:urn1" =>"urn:safetypay:schema:mws:api"}, namespace_identifier: :urn, env_namespace: :soapenv)
# Assignment of variables below must be done in this case. I don't write it here to be this code not so large.
# A thing I lasted much time to know how to do was to add the prefix urn1 only to the tag Language (urn1:Language), since the code was adding urn to all due to the namespace_identifier: :urn (see the client instance above). As you can see, I simply added it to the array message (message1).
message1 = {"urn1:Language" => language, "Signature"=> signature }
# The respective operation for this request is create_express_token.
response = client.call(:create_express_token, message: message1 )
# This was the way Savon sent the request as needed by the Web Service. Savon added additional namespaces in comparison to the original XML request I wanted to send, but it was not problematic, it was OK (e.g., it added some additional stuff to the namespaces soapenv, urn, urn1)
I hope this post helps many to make easy to consume SOAP Web Services through Savon - Ruby (and maybe with Rails).
Thank God primarily for every good thing :)
I am trying to parse the xml below to get the email address out. I can get the messageid but I think having the a: in front is enabling me to use xpath. Not sure how to pull out the email address. I am trying
xml.xpath("//s:Body/Discover/request/EmailAddress").children.text.to_s
and
xml.xpath("//s:Body/Discover/EmailAddress").children.text.to_s
if i do xml.xpath("//s:Body").children.text.to_s i get the email and the version with all the newlines and tabs but i do not want to parse the email out if i do not have to.
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">test url</a:Action>
<a:MessageID>mid</a:MessageID>
<a:ReplyTo>
<a:Address>test url</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">test url</a:To>
</s:Header>
<s:Body>
<Discover xmlns="test url">
<request xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<EmailAddress>bob#xml.com</EmailAddress>
<RequestVersion>1.0</RequestVersion>
</request>
</Discover>
</s:Body>
</s:Envelope>
The test url is preventing Nokogiri's Xpath from catching on to your namespacing within s:Body. Try simply
email = xml.xpath("//s:Body").first.to_xml.scan(/<EmailAddress>([^<]+)/)[0][0]
The Discover element (and its children) are in a different namespace, and you need to specify this in your query. The second argument to the xpath method is a hash where you can associate prefixes used in the query with namespace urls. Have a look at the section on namespaces in the Nokogiri tutorial.
With Nokogiri, if you don’t specify a namespace hash it will automatically register any namespaces defined on the root node for you. In this case that is the a prefix for http://www.w3.org/2005/08/addressing and the s prefix for http://www.w3.org/2003/05/soap-envelope. This is why your query for //s:Body works. The namespace declaration for Discover isn’t on the root, so you have to register it yourself.
When you provide your own namespace hash Nokogiri doesn’t add those defined on the root, so you will also need to include any of those used in your query.
In your case the following will find the EmailAddress node. The actual prefix you used doesn’t matter (here I’ve chosen t) as long as the URI matches).
xml.xpath('//s:Body/t:Discover/t:request/t:EmailAddress',
's' => "http://www.w3.org/2003/05/soap-envelope",
't' => "test url")
n00b REST question. I'm making a GET request to an API's endpoint and getting the proper XML response. The question I have is, how do I get the value of a particular XML element in the servers REST response using Ruby?
So let's say one of the elements is 'Body' and I want to assign its value 'Blah blah blah' to a variable
Part of the XML response:
<Body>Blah blah blah</Body>
How would I do that with the response? Basically I want to do something like this
variable = params["Body"]
Thanks in advance!
The best solution is to use RestClient or HTTParty and have it parse the response for you.
Otherwise, you'll have to parse the response itself using a library such as Nokogiri:
doc = Nokogiri.XML(response)
variable = doc.at("body").text
You'll want to use an XML parser of some kind.
It sounds like you want something like XmlSimple, which will turn an XML document into ruby arrays and hashes. There's tons of examples of how to use it on the page that has been linked.
One thing to be aware of is that XML to native container mappings are imperfect. If you're dealing with a complex document, you'll likely want to use a more robust parser, like Nokogiri.
If you want full XML Object Mapping, HappyMapper is a decent library, although it isn't very active anymore. It can work with XML from any source, so you'll still want something like the libraries mentioned by #Fitzsimmons or #MarkThomas to do the HTTP request.