XML::Simple encoding problem - perl

I have an xml-file I want to parse:
<?xml version="1.0" encoding="UTF-8" ?>
<tag>û</tag>
It's perfectly parsed by firefox. But XML::Simple corrupts some data. I have a perl-program like this:
my $content = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
$content .= "<tag>\x{c3}\x{bb}</tag>\n";
print "input:\n$content\n";
my $xml = new XML::Simple;
my $data = $xml->XMLin($content, KeepRoot => 1);
print "data:\n";
print Dumper $data;
and get:
input:
<?xml version="1.0" encoding="UTF-8" ?>
<tag>û</tag>
data:
$VAR1 = {
'tag' => "\x{fb}"
};
it doesn't seem to be what I expected. I think there some encoding issues. Am I doing something wrong?
UPD:
I thought that XMLin returned text in utf-8 (as the input). Just added
encode_utf8($data->{'tag'});
and it worked

XML::Simple is fickle.
Its calling Encode::decode('UTF-8',$content) which is putting your UTF-8 in native.
Do this:
my $content_utf8 = "whatevér";
my $xml = XMLin($content_utf8);
my $item_utf8 = Encode::encode('UTF-8',$xml->{'item'});
This sort of works too, but risky w/ double encoding:
my $content_utf8 = "whatevér";
my $double_encoded_utf8 = Encode::encode('UTF-8',$content_utf8);
my $xml = XMLin($double_encoded_utf8);
my $item_utf8 = $xml->{'item'};

Hexadecimal FB (dec 251) is ASCII code of "û" character. Could you please elaborate on what you expected to get in the data structure which leads you to conclude what you got was "corrupt"?

Related

How to add Child and Grandchild XML tags by XML::LibXML?

I'm trying to make a simple XML file and add Child and Grandchild XML tags by XML::LibXML.
I made country and location tags as below, but I want to add child and grandchild tags under country and location tags such as <dev>value</dev>. How do I add Child and Grandchild XML tags by XML::LibXML?
use strict;
use warnings;
use XML::LibXML;
my $doc = XML::LibXML::Document->new('1.0', 'utf-8');
#my $record = $doc->documentElement;
my $root = $doc->createElement('my-root-element');
$root->setAttribute('some-attr'=> 'some-value');
my $country = $doc->createElement('country');
$country-> appendText('Jamaica');
$root->appendChild($country);
my $dev = $doc->createElement('dev');
$dev-> appendText('value');
$country->appendChild($dev);
my $location = $doc->createElement('location');
$location-> appendText('21.241.21.2');
$root->appendChild($location);
$doc->setDocumentElement($root);
print $doc->toString(1);
The result I got is below:
<?xml version="1.0" encoding="utf-8"?>
<my-root-element some-attr="some-value">
<country>Jamaica<dev>value</dev></country>
<location>21.241.21.2</location>
</my-root-element>
Actually, I expected the output as below
<?xml version="1.0" encoding="utf-8"?>
<my-root-element some-attr="some-value">
<country>
<dev>
<Name>Jameica</Name>
<field>value</field>
<range>value1</range>
</dev>
</country>
<country>
<dev>
<Name>USA</Name>
<field>value</field>
<range>value1</range>
</dev>
</country>
</my-root-element>
Since <country> only has elements, not text, you should not use $country->appendText('Jamaica');. Similarly, you should not use appendText for <dev>.
You also need to create 3 child elements under <dev>: Name, field and range. For those, you need to call $dev->appendChild, etc.
This code sets you on a path to get the type of output you desire:
use strict;
use warnings;
use XML::LibXML;
my $doc = XML::LibXML::Document->new('1.0', 'utf-8');
my $root = $doc->createElement('my-root-element');
$root->setAttribute('some-attr'=> 'some-value');
my $country = $doc->createElement('country');
$root->appendChild($country);
my $dev = $doc->createElement('dev');
$country->appendChild($dev);
my $name = $doc->createElement('Name');
$name->appendText('Jamaica');
$dev->appendChild($name);
my $field = $doc->createElement('field');
$field->appendText('value');
$dev->appendChild($field);
my $range = $doc->createElement('range');
$range->appendText('value1');
$dev->appendChild($range);
$doc->setDocumentElement($root);
print $doc->toString(1);
Prints:
<?xml version="1.0" encoding="utf-8"?>
<my-root-element some-attr="some-value">
<country>
<dev>
<Name>Jamaica</Name>
<field>value</field>
<range>value1</range>
</dev>
</country>
</my-root-element>

USPS HTTP Post Request

Here is the error message I am receiving when I try to run the following Perl code. Any ideas what causing this? It appears it does not like the API=Verify, but it was the only name for the API which returns a 9 digit zip code I could find.
80040B1AAPI Authorization failure. is not a valid API name for this
protocol.USPSCOM::DoAuth
# Perl subroutine for POST Request
#########
sub FindTracking() {
$saddress="60 Passional Way";
$scity="Burnsville";
$szip="27690";
$sstate="NC";
print "start of tracking...<br>";
$queryString = qq~
<?xml version="1.0"?>
https://secure.shippingapis.com/ShippingAPI.dll?API=Verify&XML=
<AddressValidateRequest USERID="xxxxxxx">
<Revision>1</Revision>
<Address ID="0">
<Address1></Address1>
<Address2>$saddress</Address2>
<City>$scity</City>
<State>$sstate</State>
<Zip5>$szip</Zip5>
<Zip4></Zip4>
</Address>
</AddressValidateRequest>
<?xml version="1.0"?>
<AddressValidateResponse><Address ID="0">
<Address2></Address2><City></City><State></State><Zip5></Zip5>
<Zip4></Zip4></Address></AddressValidateResponse>
<Error>
<Number></Number>
<Source></Source>
<Description></Description>
<HelpFile></HelpFile>
<HelpContext></HelpContext>
</Error>
~;
# Instantiate the user agent and set our agent string
$userAgent = new LWP::UserAgent;
$userAgent->agent( 'USPS' );
$queryString =~ s/ /\%20/ig;
$request = new HTTP::Request( 'POST',
'https://secure.shippingapis.com/ShippingAPI.dll' );
# Set the content type
$request->content_type( 'text/xml' );
# Set the query string
$request->content( $queryString );
# Make the request
$response = $userAgent->request( $request );
print $response->content();
# Check the status of the request
if ( $response->is_success ) {
$content = $$response{ "_content" };
$TrackingNumber = "";
($success) = $content =~ /\<AddressValidateRequest\>(.*)\
<\/AddressValidateRequest\>/;
print "<br><br>==>some $content<br>";
if ($success eq "Success") {
($TrackingNumber)= $content =~ /\<Zip4\>(.*)\<\/Zip4\>/;
print "here with $TrackingNumber<br>";
}
}
else {
print "<br><br>here with resp=$response<br>req=$request <br>cont
$content";
}
}
To start, you have an invalid query string. Compare what you are doing to the USPS WebTools example and remove what they don't show. Be sure that you are setting your USERID properly (and not using 'xxxxxxx'); an environment variable is handy there.
Or, you can try using the Business::USPS::WebTools module from GitHub. It implements the Zip Code Lookup.
Change your query XML to:
$queryString = qq~
<AddressValidateRequest USERID="xxxxxxxxxxx">
<Revision>1</Revision>
<Address ID="0">
<Address1></Address1>
<Address2>$saddress</Address2>
<City>$scity</City>
<State>$sstate</State>
<Zip5>$szip</Zip5>
<Zip4></Zip4>
</Address>
</AddressValidateRequest>
~;
Then build the request as a GET instead of a POST:
$userAgent = new LWP::UserAgent;
$userAgent->agent( 'USPS' );
$url = "https://secure.shippingapis.com/ShippingAPI.dll?API=Verify&XML=$queryString";
$request = new HTTP::Request( 'GET', $url );

XMLin not parsing XML properly

I have an XML as follows in $response_xml
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"><?xml version="1.0" encoding="utf-8"?><wholeSaleApi><credentials><referenceNumber></referenceNumber></credentials><wholeSaleOrderResponse><statusCode>666</statusCode><description>Object reference not set to an instance of an object.</description></wholeSaleOrderResponse></wholeSaleApi></string>
When I parse it using
my $xs = XML::Simple->new();
my $xmlDS = eval{ $xs->XMLin($response_xml) };
I get the following data structure
$xmlDS = {
'xmlns' => 'http://schemas.microsoft.com/2003/10/Serialization/',
'content' => '<?xml version="1.0" encoding="utf-8"?><wholeSaleApi><credentials><referenceNumber></referenceNumber></credentials><wholeSaleOrderResponse><statusCode>666</statusCode><description>Object reference not set to an instance of an object.</description></wholeSaleOrderResponse></wholeSaleApi>'
};
How do I get the content portion from this?
What you get is a hash reference. You can use the follwoing syntax to get to the particular key:
my $content = $xmlDS->{content};

Perl using XML Path Context to extract out data

I have the following xml
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Function Name="GetSomethingById">
<something idSome="1" Code="1" Description="TEST01" LEFT="0" RIGHT="750" />
</Function>
</Response>
and I want the attributes of <something> node as a hash. Im trying like below
my $xpc = XML::LibXML::XPathContext->new(
XML::LibXML->new()->parse_string($xml) # $xml is containing the above xml
);
my #nodes = $xpc->findnodes('/Response/Function/something');
Im expecting to have something like $nodes[0]->getAttributes, any help?
my %attributes = map { $_->name => $_->value } $node->attributes();
Your XPATH query seems to be wrong - you are searching for '/WSApiResponse/Function/something' while the root node of your XML is Response and not WSApiResponse
From the docs of XML::LibXML::Node (the kind of stuff that findnodes() is expected to return), you should look for my $attrs = $nodes[0]->attributes() instead of $nodes[0]->getAttributes
I use XML::Simple for this type of thing. So if the XML file is data.xml
use strict;
use XML::Simple();
use Data::Dumper();
my $xml = XML::Simple::XMLin( "data.xml" );
print Data::Dumper::Dumper($xml);
my $href = $xml->{Function}->{something};
print Data::Dumper::Dumper($href);
Note: With XML::Simple the root tag maps to the result hash itself. Thus there is no $xml->{Response}

Perl: what kind of data should i feed to delcampe API?

I write soap-client based on Delcampe API. Simple methods work fine, but functions with need on complex data give me an error message like "You must send item's data!". Based on PHP example here i thought, that data should be either hash or hashref, but both give me error mentioned before.
Sample script i use:
use 5.010;
use SOAP::Lite;
use SOAP::WSDL;
use strict;
use warnings;
use Data::Dumper;
my $API_key = 'xyz';
my $service = SOAP::Lite->service('http://api.delcampe.net/soap.php?wsdl');
my $return = $service->authenticateUser($API_key);
if ($return->{status}) {
my $key = $return->{data};
my %data = (description => 'updated description');
my $response = $service->updateItem($key, 123456, \%data);
if ($response->{status}) {
say Dumper $response->{data};
} else {
say $response->{errorMsg};
}
} else {
say "no: " . $return->{status};
}
So, what kind of data structure should i use instead of %data or how could i debug the SOAP-envelope, which is produced as request? (PHP code based on example works fine)
ADDITION
with use SOAP::Lite qw(trace); igot SOAP envelope too:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://api.delcampe.net/soap.php">
<soap:Body>
<tns:updateItem>
<token xsi:type="xsd:string">secret_one</token>
<id_item xsi:type="xsd:int">123456</id_item>
<arrData xsi:nil="true" xsi:type="soap-enc:Array" />
</tns:updateItem>
</soap:Body>
</soap:Envelope>
As seen above, there is no bit of data sent. I tried data also as string, array and arrayref. Maybe it is bug of SOAP::Lite?
May be you'd try to replace
my %data = (description => 'updated description');
with
my $data = SOAP::Data->name(description => 'updated description');
We have similar issues when working on our SOAP API, and it was solved by something like that, wrapping complex data into SOAP::Data. So I hope this'll help. )
UPDATE:
The previous advice didn't help: looks like it's indeed the SOAP::Lite bug, which ignores the 'soap-enc:Array' definition in WSDL file whatsoever.
Have finally found a workaround, though. It's not pretty, but as a final resort it may work.
First, I've manually downloaded the WSDL file from Delcampe site, saved it into local directory, and referred to it as ...
my $service = SOAP::Lite->service('file://...delcampe.wsdl')
... as absolute path is required.
Then I've commented out the 'arrData line' within WSDL updateItem definition.
And, finally, I've made this:
my $little_monster = SOAP::Data->name(arrData =>
\SOAP::Data->value((
SOAP::Data->name(item =>
\SOAP::Data->value(
SOAP::Data->name(key => 'personal_reference'),
SOAP::Data->name(value => 'Some Personal Reference')->type('string'),
)
),
SOAP::Data->name(item =>
\SOAP::Data->value(
SOAP::Data->name(key => 'title'),
SOAP::Data->name(value => 'Some Amazing Title')->type('string'),
)
),
# ...
))
)->type('ns1:Map');
... and, I confess, successfully released it into the wilderness by ...
$service->updateItem($key, 123456, $little_monster);
... which, at least, generated more-o-less likable Envelope.
I sincerely hope that'll save at least some poor soul from banging head against the wall as much as I did working on all that. )