XML Xpath Failing on getElementsByTagName - php

<?xml version="1.0" encoding="UTF-8"?>
<AddProduct>
<auth><id>vendor123</id><auth_code>abc123</auth_code></auth>
</AddProduct>
What am I doing wrong to get : Fatal error: Call to undefined method DOMNodeList::getElementsByTagName()
$xml = $_GET['xmlRequest'];
$dom = new DOMDocument();
#$dom->loadXML($xml);
$xpath = new DOMXPath($dom);
$auth = $xpath->query('*/auth');
$id = $auth->getElementsByTagName('id')->item(0)->nodeValue;
$code = $auth->getElementsByTagName('auth_code')->item(0)->nodeValue;

You could retrieve the data (in the XML you posted) you want using XPath only:
$id = $xpath->query('//auth/id')->item(0)->nodeValue;
$code = $xpath->query('//auth/auth_code')->item(0)->nodeValue;
You are also calling getElementsByTagName() on $auth (DOMXPath), as #Ohgodwhy pointed out in the comments, which is causing the error. If you want to use it, you should call it on $dom.
Your XPath expression returns the auth child of the current (context) node. Unless your XML file is different, it's clearer to use one of:
/*/auth # returns auth nodes two levels below root
/AddProduct/auth # returns auth nodes in below /AddProduct
//auth # returns all auth nodes

This is what I came up with after reviewing php's documentation (http://us1.php.net/manual/en/class.domdocument.php, http://us1.php.net/manual/en/domdocument.loadxml.php, http://us3.php.net/manual/en/domxpath.query.php, http://us3.php.net/domxpath)
$dom = new DOMDocument();
$dom->loadXML($xml);
$id = $dom->getElementsByTagName("id")->item(0)->nodeValue;
$code = $dom->getElementsByTagName("auth_code")->item(0)->nodeValue;
As helderdarocha and Ohgodwhy pointed out, the getElementByTagName is a DOMDocument method not a DOMXPath method. I like helderdarocha's solution that only uses XPath, the solution I posted accomplishes the same thing but only uses the DOMDocument.

Related

getting xml node with php

Im attempting to echo/assign a variable to the contents of the node "code" which is inside status;
I can get request-id just fine...
Any ideas people?
<?
$responseXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<payment xmlns="http://www.example.com" self="http://www.example.com">
<merchant-account-id ref="http://www.example.com">0000</merchant-account-id>
<transaction-id>0000</transaction-id>
<request-id>0000</request-id>
<transaction-type>auth</transaction-type>
<transaction-state>success</transaction-state>
<completion-time-stamp>2015-12-28T17:39:25.000Z</completion-time-stamp>
<statuses>
<status code="201.0000" description="3d-acquirer:The resource was successfully created." severity="information"/>
</statuses>
<avs-code>P</avs-code>
<requested-amount currency="GBP">0.01</requested-amount>
<account-holder>
<first-name>test</first-name>
<last-name>test</last-name>
<email>test.test#hotmail.co.uk</email>
<phone>00000000000</phone>
<address>
<street1>test</street1>
<city>test test</city>
<state>test</state>
<country>GB</country>
</address>
</account-holder>
<card-token>
<token-id>000</token-id>
<masked-account-number>000000******0000</masked-account-number>
</card-token>
<ip-address>192.168.0.1</ip-address>
<descriptor></descriptor>
<authorization-code>000000</authorization-code>
<api-id>000-000</api-id>
</payment>';
$doc = new DOMDocument;
$doc->loadXML($responseXML);
echo $doc->getElementsByTagName('request-id')->item(0)->nodeValue;
echo $doc->getElementsByTagName('status code')->item(0)->nodeValue;
?>
I've tried simplexml looad string, but pulling hair out with this one, can anybody shed some light, speed of getting this info out in one process is quite important so not to stress the webserver out!
Many thanks.
Using DOM is a good idea, but the API methods are a little cumbersome. Using Xpath makes it a lot easier.
Xpath allows you to use expressions to fetch node lists or scalar values from a DOM:
$document = new DOMDocument;
$document->loadXML($responseXML);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('example', 'http://www.example.com');
echo $xpath->evaluate('string(//example:request-id)'), "\n";
echo $xpath->evaluate('string(//example:status/#code)');
Output:
0000
201.0000
Xpath does not have a default namespace so if you XML has a namespace (like your example) you need to register a prefix for it and use it.
As code is an attribute of xml tag status, doing
getElementsByTagName('status code')
is wrong.
There's a special method for getting attribute value getAttribute:
echo $doc->getElementsByTagName('status')->item(0)->getAttribute('code');
Using XPath allows to access the status node very precisely.
DOMDocument + XPath:
$responseXML = '...';
$doc = new DOMDocument();
$doc->loadXML($responseXML);
$xp = new DOMXpath($doc);
$xp->registerNamespace('example', 'http://www.example.com');
// Every status node.
$statusNodes = $xp->query('//example:status');
// or a very specific one.
$statusNodes = $xp->query('/example:payment/example:statuses/example:status');
$statusNode = $statusNodes[0];
$code = $statusNode->getAttribute('code');
// $code is '201.0000'.
// To change the 'code' value.
$statusNode->setAttribute('code', '302.0000');

Xpath in PHP with OTA standards

I have basic knowledge about the use of Xpath in PHP, but I'm having some troubles with a specific case and I think that the problem is in the standards.
This is the snippet of the XML and it's based on the OTA standards:
<SendHotelResResult xmlns:a="http://schemas/Models/OTA" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:RoomRates>
<a:RoomRate>
<a:EffectiveDate>2015-11-13T00:00:00</a:EffectiveDate>
<a:ExpireDate>2015-11-15T00:00:00</a:ExpireDate>
<a:RatePlanID>25</a:RatePlanID>
<a:RatesType>
<a:Rates>
<a:Rate>
<a:AgeQualifyingCode i:nil="true"/>
<a:EffectiveDate>2015-11-13T00:00:00</a:EffectiveDate>
<a:Total>
<a:AmountAfterTax>0</a:AmountAfterTax>
<a:AmountBeforeTax>260.00</a:AmountBeforeTax>
<a:CurrencyCode>EUR</a:CurrencyCode>
</a:Total>
</a:Rate>
<a:Rate>
<a:AgeQualifyingCode i:nil="true"/>
<a:EffectiveDate>2015-11-14T00:00:00</a:EffectiveDate>
<a:Total>
<a:AmountAfterTax>0</a:AmountAfterTax>
<a:AmountBeforeTax>260.00</a:AmountBeforeTax>
<a:CurrencyCode>EUR</a:CurrencyCode>
</a:Total>
</a:Rate>
</a:Rates>
</a:RatesType>
<a:RoomID>52</a:RoomID>
<a:Total>
<a:AmountAfterTax>546.00</a:AmountAfterTax>
<a:AmountBeforeTax>520.00</a:AmountBeforeTax>
<a:CurrencyCode>EUR</a:CurrencyCode>
</a:Total>
</a:RoomRate>
</a:RoomRates>
</SendHotelRes>
What I want:
Get a specific <RoomRate> tag based on the element <RoomID>.
Get the global RoomRate <Total> tag. I don't want the <Total> tag that is inside the <Rate> tag. This is the reason why I'm using the xpath rather than a simple getElementsByTagName('Total'). I don't know if the OTA standards has some approach to differentiate the Total tags.
My attempts until now:
$dom = new DOMDocument();
$response = $dom->load($xmlSendHotelRes);
$roomID = '52';
$roomRatesTag = $response->getElementsByTagName('RoomRates')->item(0);
$prefix = $roomRatesTag->prefix;
$namespace = $roomRatesTag->lookupNamespaceURI($prefix);
$xpath = new DOMXpath($dom);
$xpath->registerNamespace($prefix, $namespace);
$roomRateTotal = $xpath->query("//RoomRate[RoomID=$roomID]/Total", $roomRatesTag, true);
I already tried with and without $roomRatesTag as context and also other expressions like:
./RoomRate[RoomID=$roomID]/Total, //RoomRate[RoomID=$roomID]/Total, //RoomRate/[RoomID=$roomID]/Total,//RoomRate[RoomID=$roomID]/Total and //RoomRate/RoomID[text() = $roomID]/../Total but any of them works.
Actually, even $roomRate = $xpath->query("//RoomRate"); returns a empty DOMNodeList, so, I don't know what I doing wrong and I'm thinking about the problem in the standards with 2 identical tags in different places, although this not make much sense.
Are there some other expressions that I need to try?
You're fetching the namespace from the document.
$prefix = $roomRatesTag->prefix;
$namespace = $roomRatesTag->lookupNamespaceURI($prefix);
But this is not necessary or a good idea. You know that the document uses OTA, so you know the namespace is http://schemas/Models/OTA.
The prefix is just an alias for the actual namespace value the following 3 XML example all resolve to a node {http://schemas/Models/OTA}RoomRates
<a:RoomRates xmlns:a="http://schemas/Models/OTA"/>
<ota:RoomRates xmlns:ota="http://schemas/Models/OTA"/>
<RoomRates xmlns="http://schemas/Models/OTA"/>
Your Api has to look for nodes inside the namespace.
One possibility is to use the *NS (namespace aware) methods.
$response->getElementsByTagNameNS('http://schemas/Models/OTA', 'RoomRates')->item(0);
The other is to use Xpath and register prefixes for the namespaces. This can be the prefixes from the document, or different ones.
$document = new DOMDocument();
$document->load($xmlSendHotelRes);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('ota', 'http://schemas/Models/OTA');
var_dump(
$xpath->evaluate(
'string(//ota:RoomRates/ota:RoomRate[ota:RoomID=$roomID]/ota:Total)')
)
);
For a location path, DOMXpath::evaluate() would return a DOMNodeList but with string() it casts the first found node into a string and returns it.
You need to use a prefix (that you registered) and I think you want to start your path with .// and not with // if you want to search relative to the context node, so try ".//a:RoomRate[a:RoomID=$roomID]/a:Total"

DOMDocument simple GetElementsByTagName wont work?

$xml = '<?xml version="1.0" encoding="UTF-8"?>
<stw:ThumbnailResponse xmlns:stw="http://www.shrinktheweb.com/doc/stwresponse.xsd">
<stw:Response>
<stw:ThumbnailResult>
<stw:Thumbnail Exists="true">http://imagelink.com</stw:Thumbnail>
<stw:Thumbnail Verified="false">delivered</stw:Thumbnail>
</stw:ThumbnailResult>
<stw:ResponseStatus>
<stw:StatusCode>refresh</stw:StatusCode>
</stw:ResponseStatus>
<stw:ResponseTimestamp>
<stw:StatusCode>1413812009</stw:StatusCode>
</stw:ResponseTimestamp>
<stw:ResponseCode>
<stw:StatusCode>HTTP:200</stw:StatusCode>
</stw:ResponseCode>
<stw:CategoryCode>
<stw:StatusCode></stw:StatusCode>
</stw:CategoryCode>
<stw:Quota_Remaining>
<stw:StatusCode>132</stw:StatusCode>
</stw:Quota_Remaining>
<stw:Bandwidth_Remaining>
<stw:StatusCode>999791</stw:StatusCode>
</stw:Bandwidth_Remaining>
</stw:Response>
</stw:ThumbnailResponse>';
$dom = new DOMDocument;
$dom->loadXML($xml);
$result = $dom->getElementsByTagName('stw:Thumbnail')->item(0)->nodeValue;
$status = $dom->getElementsByTagName('stw:Thumbnail')->item(0)->nodeValue;
echo $result;
Having the above code should output http://imagelink.com and $status should hold "delivered" - but none of these work instead I am left with the error notice that:
Trying to get property of non-object
I have tried different xml parsing alternatives like simplexml (but that did not work when the tag names have : in it ) and i tried looping through the each scope in the xml (ThumbNailresponse, response and then thumbnailresult) without luck.
How can i get the values inside stw:Thumbnail?
You need to specify a namespace and the method DOMDocument::getElementsByTagName can't handle it. In the manual:
The local name (without namespace) of the tag to match on.
You can use DOMDocument::getElementsByTagNameNS instead:
$dom = new DOMDocument;
$dom->loadXML($xml);
$namespaceURI = 'http://www.shrinktheweb.com/doc/stwresponse.xsd';
$result = $dom->getElementsByTagNameNS($namespaceURI, 'Thumbnail')->item(0)->nodeValue;
Using simple xml you could use ->children() method on this one:
$xml = simplexml_load_string($xml_string);
$stw = $xml->children('stw', 'http://www.shrinktheweb.com/doc/stwresponse.xsd');
echo '<pre>';
foreach($stw as $e) {
print_r($e);
// do what you have to do here
}
This code actually runs just fine for me ---
Typically, that sort of error means you may've made a typo on your $dom object - double check it and try again.
Also, it is notable that you'll want to change the item(0) to item(1) when you're setting your $status variable.
$result = $dom->getElementsByTagName('stw:Thumbnail')->item(0)->nodeValue;
$status = $dom->getElementsByTagName('stw:Thumbnail')->item(0)->nodeValue;

php parsing xml formatted data

I'm trying to parse an xml data that I'm getting via an api call. I can use file_get_contents to read into a string but simpleXML_load_string seems to fail to read it. I can save it to a file and then simpleXML_load_file works. But I would rather not write the contents to a file. I can't seem to understand how to use DOM or XMLParse with this either. I'm new to PHP and parsing XML. The output data from the api call is below.
<Search>
<DS_Rating>DS3</DS_Rating>
<Overall>17.5</Overall>
<LargestGiftLow>0</LargestGiftLow>
<LargestGiftHigh>0</LargestGiftHigh>
<EstimatedCapacityRange>I - $15,000 - $24,999</EstimatedCapacityRange>
<EstimatedCapacity>20452</EstimatedCapacity>
<RealEstateEst>270073</RealEstateEst>
<RealEstateCount>1</RealEstateCount>
<LikelyMatchesCount>0</LikelyMatchesCount>
<LikelyMatchesTotal>0</LikelyMatchesTotal>
<FndBoard></FndBoard>
<GSBoard></GSBoard>
<PoliticalLikelyCount>0</PoliticalLikelyCount>
<PoliticalLikelyTotal>0</PoliticalLikelyTotal>
<BusinessRevenues>0</BusinessRevenues>
<SECStockValue>0</SECStockValue>
<SECInsider></SECInsider>
<MarketGuide></MarketGuide>
<IRS990PF></IRS990PF>
<RealEstateTrust></RealEstateTrust>
<MarketGuideComp>0</MarketGuideComp>
<MarketGuideOptions>0</MarketGuideOptions>
<BusinessAffiliation></BusinessAffiliation>
<Pension></Pension>
<PensionAssets>0</PensionAssets>
<CorpTech></CorpTech>
<Pilot></Pilot>
<AirplaneOwner></AirplaneOwner>
<Boat></Boat>
<submit_time>2014-03-11 15:48:45</submit_time>
</Search>
Figured out that the issue was that what I was seeing in the browser was actually a php output with html_entiity encoded. I was able to process it with the code below which let me load it with simplexml.
$rawxml = html_entity_decode($rawxml);
$rawxml = str_replace(array(' ', "<pre>"), '', $rawxml);
$rawxml = utf8_encode($rawxml);
$xml = simplexml_load_string($rawxml);
If you XML is in a file use
simplexml_load_file
if you have it in a string use
simplexml_load_string
Then you can use the following code to access it.
<?php
$yourxml = simplexml_load_file('your.xml');
echo $yourxml->search[0]->DS_Rating;
?>
This would then output
DS3
to the browser via the 'echo' in your code. I hope this points you in the correct direction.
Try to use this:
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8" ?>'.$yourXMLString);
In DOM you load the XML into a DOMDocument and create a DOMXpath instance for it.
$dom = new DOMDocument();
$dom->loadXml($xmlString);
//$dom->load($xmlFile);
$xpath = new DOMXpath($dom);
DOMXpath::evaluate() is used to fetch data from the DOM.
$rating = $dom->evaluate('string(/Search/DS_Rating)');
An Xpath expression like /Search/DS_rating always returns a node list. You can use foreach() to iterate it. The string() function in Xpath takes the first node from the list and casts it into a string. If here is not node in the list the result is an empty string.
$xmlString = <<<'XML'
<Search>
<DS_Rating>DS3</DS_Rating>
<Overall>17.5</Overall>
</Search>
XML;
$dom = new DOMDocument();
$dom->loadXml($xmlString);
$xpath = new DOMXpath($dom);
var_dump(
$xpath ->evaluate('string(/Search/DS_Rating)')
);
Output: https://eval.in/118921
string(3) "DS3"

Call to undefined method DOMDocument::createDocumentType()

I have the following script snippet. Originally I did not realize to use getElementById that I needed to include createDocumentType, but now I get the error listed above. What am I doing wrong here? Thanks in advance!
...
$result = curl_exec($ch); //contains some webpage i am grabbing remotely
$dom = new DOMDocument();
$dom->createDocumentType('html', '-//W3C//DTD HTML 4.01 Transitional//EN', 'http://www.w3.org/TR/html4/loose.dtd');
$elements = $dom->loadHTML($result);
$e = $elements->getElementById('1');
...
Edit: Additional note, I verified the DOM is correct on the remote page.
DOMDocument does not have a method named createDocumentType, as you can see in the Manual. The method belongs to the DOMImplemetation class. It is used like this (taken from the manual):
// Creates an instance of the DOMImplementation class
$imp = new DOMImplementation;
// Creates a DOMDocumentType instance
$dtd = $imp->createDocumentType('graph', '', 'graph.dtd');
// Creates a DOMDocument instance
$dom = $imp->createDocument("", "", $dtd);
Since you want to load HTML into the document, you don't need to specify a document type, since it is determined from the imported HTML. You just have to have some id attributes, or a DTD that identifies an other attribute as an id. This is part of the HTML file, not the parsing PHP code.
$dom = new DOMDocument();
$dom->loadHTML($result);
$element = $dom->getElementById('my_id');
will do the job.

Categories