How to clean xml request before sending to web service - php

Good day i am trying to send an xml to a web service but would need to clean the xml before sending. So far I have tried different ways and now been stuck for a while.
I capture the data from a form and post it to my php file to process. If the user doesnt enter any data in the length/width/height then i would like to clean my xml and remove empty element so it can pass validation on the server where sending xml request too.
Here below is a snippet of the data cpatured from my post and build the xml file accordingly but what if the dimensions were omitted? Could I also clean other elements that are empty?
$xmlRequest = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<mailing-scenario xmlns="http://www.mysite.com/ws/ship/rate-v2">
<customer-number>{$mailedBy}</customer-number>
<parcel-characteristics>
<weight>{$weight}</weight>
<dimensions>
<length>{$length}</length>
<width>{$width}</width>
<height>{$height}</height>
</dimensions>
</parcel-characteristics>
<origin-postal-code>{$originPostalCode}</origin-postal-code>
<destination>
<domestic>
<postal-code>{$postalCode}</postal-code>
</domestic>
</destination>
</mailing-scenario>
XML;
$xmlRequest = phpquery::newDocument();
$xp = new DOMXPath($xmlRequest->getDOMDocument());
foreach($xp->query('//*[not(node()) or normalize-space() = ""]') as $node) {
$node->parentNode->removeChild($node);
}

Okay here an example with just simple dom. Maybe some points first, you'll have decide what to do if no customer number is given or a negative weight, ... .
So you have to clean the XML but sometimes cleaning it will make either the request invalid or the user might get some result he did not expect. For instance he might put 1kg as weight you remove the kg because the weight is set in g and a string there is just wrong. If you don't tell the user he might yell at you!
And also just because all nodes are valid does not mean the request is correct, as there might be some missing nodes, so you also have to check for the requirements!
One last word to efficency, if you can get all these fields from the user without XML because the user just sends one parcel at a time. Do it like that and just check if that data is correct.
If you have to use XML, put still just send one package at a time you can just fetch the data check the validity and reconstruct the validated XML.
I would just use this example if I know that these XML requests might be really extensive and/or have a complex format.
function cleanXML($data){
// ok the data is string! So get your node.
$node = simplexml_load_string($data);
// now we can iterate throught all child nodes:
foreach($node->children() as $child){
//so here we got the childrens
// All child nodes of the root should be mailing scenarios
if($child->getName() == "mailing-scenario"){
//otherwise we check if mailing scenario is valid
if(!validateMScenario($child)){
//This node seems not so valid
//you have to decide what to do now!
}
}
else{
//Here we remove all nodes that are different
unset($child[0]);
echo "Error: Needed to remove Node";
}
}
// Just give them their cleaned XML!
return $node->asXML();
}
function validateMScenario($ms){
// These var's safe if the requirements are fullfilled
$is_customer_number_set = 0
$is_weight_set = 0
$is_package_set = 0
// Again iterate over nodes
foreach($ms->children as $child){
//check for customer number
if($child->getName() == "customerNumber"){
if($is_customer_number_set == 1){
echo "You just need one customer number I guess?!"
return -1
}
value = (string) $child;
// Check if customer number is existing
if(strlen(value) == 0 || !is_int(value) || intval(value) == -1){
echo "Dude you need a number!";
return -1
}
$is_customer_number_set = 0;
}
else if($node->getName() == "parcel-characteristics"){
//Ok I hope it should be pretty clear what to do here!
//...
}
else{
//Remove node again?
}
}
// All requirements fullfilled?
return ($is_customer_number_set && $is_weight_set && $is_package_set);
}

Related

Removing XML Elements Created Within Loop At The End Of Each Loop

I have a script that scans every item laptop in my Snipe IT inventory, compares it against that same instance in my Jamf Pro server, and for any fields that do not match, it creates an XML and sends that XML over to Jamf. What I need is for the XML to have all of its elements "wiped" after sending the payload over. I have tried countless --- countless --- ways to make this happen, but even when I "print" the supposedly "wiped" payload at the end of the script and confirm that the element was removed, it still appears the next time around when I inspect the payload that was sent to Jamf. If I have three laptops with info that gets sent over, I get three fields. Four, I get four. And so on. I really need to figure out how to reset this XML to make this script cleaner.
Here is where I am creating the XML, if the information does not match.
// let's compare the DEVICE ASSIGNMENT across our two systems, knowing that itassets is our SOT
if ($jamf_assignment !== $itassets_assignment) {
echo "\n" . "ALERT: The device assignment is not in sync across our systems!" . "\n";
if (!isset($location)) {
$location = $jamf_xml->createElement('location');
$location->appendChild($jamf_xml->createElement('real_name',$itassets_assignment));
$process_xml = 1;
} else {
$location->appendChild($jamf_xml->createElement('real_name',$itassets_assignment));
$process_xml = 1;
}
} else {
echo "\n" . "DEVICE ASSIGNMENT CHECKS OUT! ALL GOOD HERE!" . "\n";
}
And here is where I am turning it into a payload, about to be sent.
if (!empty($location)) {
$computer->appendChild($location);
}
$jamf_xml->appendChild($computer);
$jamf_payload = $jamf_xml->saveXML();
And finally here is where I am trying to reset that XML, and start fresh with the next loop.
// and finally let's destroy the XML file
if (!empty($general)) {
$computer->removeChild($general);
}
if (!empty($location)) {
$computer->removeChild($location);
}
$jamf_xml->appendChild($computer);
$jamf_payload = $jamf_xml->saveXML();

PHP return value after XML exploration

I got a PHP array with a lot of XML users-file URL :
$tab_users[0]=john.xml
$tab_users[1]=chris.xml
$tab_users[n...]=phil.xml
For each user a <zoom> tag is filled or not, depending if user filled it up or not:
john.xml = <zoom>Some content here</zoom>
chris.xml = <zoom/>
phil.xml = <zoom/>
I'm trying to explore the users datas and display the first filled <zoom> tag, but randomized: each time you reload the page the <div id="zoom"> content is different.
$rand=rand(0,$n); // $n is the number of users
$datas_zoom=zoom($n,$rand);
My PHP function
function zoom($n,$rand) {
global $tab_users;
$datas_user=new SimpleXMLElement($tab_users[$rand],null,true);
$tag=$datas_user->xpath('/user');
//if zoom found
if($tag[0]->zoom !='') {
$txt_zoom=$tag[0]->zoom;
}
... some other taff here
// no "zoom" value found
if ($txt_zoom =='') {
echo 'RAND='.$rand.' XML='.$tab_users[$rand].'<br />';
$datas_zoom=zoom($r,$n,$rand); } // random zoom fct again and again till...
}
else {
echo 'ZOOM='.$txt_zoom.'<br />';
return $txt_zoom; // we got it!
}
}
echo '<br />Return='.$datas_zoom;
The prob is: when by chance the first XML explored contains a "zoom" information the function returns it, but if not nothing returns... An exemple of results when the first one is by chance the good one:
// for RAND=0, XML=john.xml
ZOOM=Anything here
Return=Some content here // we're lucky
Unlucky:
RAND=1 XML=chris.xml
RAND=2 XML=phil.xml
// the for RAND=0 and XML=john.xml
ZOOM=Anything here
// content founded but Return is empty
Return=
What's wrong?
I suggest importing the values into a database table, generating a single local file or something like that. So that you don't have to open and parse all the XML files for each request.
Reading multiple files is a lot slower then reading a single file. And using a database even the random logic can be moved to SQL.
You're are currently using SimpleXML, but fetching a single value from an XML document is actually easier with DOM. SimpleXMLElement::xpath() only supports Xpath expression that return a node list, but DOMXpath::evaluate() can return the scalar value directly:
$document = new DOMDocument();
$document->load($xmlFile);
$xpath = new DOMXpath($document);
$zoomValue = $xpath->evaluate('string(//zoom[1])');
//zoom[1] will fetch the first zoom element node in a node list. Casting the list into a string will return the text content of the first node or an empty string if the list was empty (no node found).
For the sake of this example assume that you generated an XML like this
<zooms>
<zoom user="u1">z1</zoom>
<zoom user="u2">z2</zoom>
</zooms>
In this case you can use Xpath to fetch all zoom nodes and get a random node from the list.
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$zooms = $xpath->evaluate('//zoom');
$zoom = $zooms->item(mt_rand(0, $zooms->length - 1));
var_dump(
[
'user' => $zoom->getAttribute('user'),
'zoom' => $zoom->textContent
]
);
Your main issue is that you are not returning any value when there is no zoom found.
$datas_zoom=zoom($r,$n,$rand); // no return keyword here!
When you're using recursion, you usually want to "chain" return values on and on, till you find the one you need. $datas_zoom is not a global variable and it will not "leak out" outside of your function. Please read the php's variable scope documentation for more info.
Then again, you're calling zoom function with three arguments ($r,$n,$rand) while the function can only handle two ($n and $rand). Also the $r is undiefined, $n is not used at all and you are most likely trying to use the same $rand value again and again, which obviously cannot work.
Also note that there are too many closing braces in your code.
I think the best approach for your problem will be to shuffle the array and then to use it like FIFO without recursion (which should be slightly faster):
function zoom($tab_users) {
// shuffle an array once
shuffle($tab_users);
// init variable
$txt_zoom = null;
// repeat until zoom is found or there
// are no more elements in array
do {
$rand = array_pop($tab_users);
$datas_user = new SimpleXMLElement($rand, null, true);
$tag=$datas_user->xpath('/user');
//if zoom found
if($tag[0]->zoom !='') {
$txt_zoom=$tag[0]->zoom;
}
} while(!$txt_zoom && !empty($tab_users));
return $txt_zoom;
}
$datas_zoom = zoom($tab_users); // your zoom is here!
Please read more about php scopes, php functions and recursion.
There's no reason for recursion. A simple loop would do.
$datas_user=new SimpleXMLElement($tab_users[$rand],null,true);
$tag=$datas_user->xpath('/user');
$max = $tag->length;
while(true) {
$test_index = rand(0, $max);
if ($tag[$test_index]->zoom != "") {
break;
}
}
Of course, you might want to add a bit more logic to handle the case where NO zooms have text set, in which case the above would be an infinite loop.

xpath search for a php variable

I am trying to search for a php variable in an xml file using xpath, but failing miserably. It works with a hard coded value, so I am nearly there..
<visitors>
<detail>
<id>876867</id>
<name>Bob McHaggis</name>
<email>bob#gmail.com</email>
</detail>
<detail>
<id>897987</id>
<name>Mark McBob</name>
<email>mark#gmail.com</email>
</detail>
</visitors>
<?php $sxe = simplexml_load_file("/CaptivePortal/visitors.xml");
foreach($sxe->xpath('//visitors/detail') as $item){
$row = simplexml_load_string($item->asXML());
$v = $row->xpath('//id[. ="'.$_COOKIE["judsons"].'"]');
} echo $v[0]; ?>
This works great checks for an id against the id stored in the cookie. But based on that value being found how do I access the name and email for the key matched?
Found & matched: 897987
I want to echo the name and email to, so based on that is Mark McBob & mark#gmail.com
My initial advice would be to take a good few minutes to (re-)read through the SimpleXML Basic Usage page in the PHP manual.
For selecting a single item within the XML file, there is no need at all to loop over anything. The example code below, and why it is different from yours, should become clear after familiarising yourself with the page mentioned above.
<?php
$search_id = (int) $_COOKIE["judsons"];
$visitors = simplexml_load_file("/CaptivePortal/visitors.xml");
$details = $visitors->xpath("detail[id='$search_id']");
if (empty($details)) {
echo "No details found for '$search_id'.";
} else {
$detail = $details[0];
$name = (string) $detail->name;
$email = (string) $detail->email;
echo "$name's email is $email";
}
The idea above is that $details will be an array containing hopefully just one <detail> element. It could be an empty array if no <detail> was found with the specified <id>, which is what the if(empty(…)) checks for. If the $details array is not empty, we're really only interested in the first one ($details[0]).
To access the information available within the <detail>, as explained on the "basic usage" page, the elements can be accessed with normal object property syntax ($detail->name). Doing so returns an object for that item (e.g. the <name>), so to get at the value as a string the object is type cast using (string).
You're actually doing quite a bit you don't need to there. For one thing, you don't need the loop. For another, inside your loop, you convert the context XML into a string (::asXML()), then convert it back into XML (simplexml_load_string()).
All you need is:
$xml = "<visitors><detail><id>876867</id><name>Bob McHaggis</name><email>bob#gmail.com</email></detail><detail><id>897987</id><name>Mark McBob</name><email>mark#gmail.com</email></detail></visitors>";
$sxe = simplexml_load_string($xml);
$row = $sxe->xpath('detail[id = '.$_COOKIE["judsons"].']');
That gets you the row. To extract part of it:
$name = $row[0]->xpath('name');
echo $name[0]; //Mark McBob

PHP XML. IP validation

My Xml looks like this example:
<?xml version="1.0" encoding="UTF-8"?>
<Allvotes>
<vote score="2" ip="116.971.203.221"/>
<vote score="5" ip="32.97.233.5"/>
<vote score="3" ip="212.977.233.225"/>
<vote score="5" ip="2.80.233.225"/>
</Allvotes>
When on my flash website (AS2), somebody press "vote" button, script in PHP getting his IP... What I want is run specyfic function, depends on his IP exist in xml file or not.
If his IP already exist, PHP send message: "ALREADY VOTED!", when IP doesn't exist in XML, then I want to run function which store his vote score and IP in xml.
So far I know that this PHP script not works:
$dom = new DomDocument('1.0', 'UTF-8');
$myXML = "votes.xml";
$s="";
if ($_POST['todo']=="vote"){
$ip=$_SERVER['REMOTE_ADDR'];
$dom->load($myXML);
$allVotes= $dom->getElementsByTagName('vote');
foreach ($allVotes as $vote){
if ($vote->getAttribute('ip')==$ip){
$s.="&msg= Already Voted";
echo $s;
break;
}else{
doOtherStuff
}
}
}
The problem is that this loop fire "doOtherStuff" function when IP is not in first node...
Is there any magic trick to do that?
Why your code does not work
To answer the immediate question: you need to defer the "already voted?" test until you have iterated over all the records:
$alreadyVoted = false;
foreach ($allVotes as $vote){
if ($vote->getAttribute('ip')==$ip){
$alreadyVoted = true;
break;
}
}
if($alreadyVoted) {
$s.="&msg= Already Voted";
echo $s;
}
else {
// other stuff
}
Why you should not do it this way
Storing your data in XML like this is a really inefficient way of doing things. You should move the data store to a database (MySql is typically easiest to set up and work with from PHP).

Not finding elements using getElementsByTagName() using DOMDocument

I'm trying to loop through multiple <LineItemInfo> products contained within a <LineItems> within XML I'm parsing to pull product Ids out and send emails and do other actions for each product.
The problem is that it's not returning anything. I've verified that the XML data is valid and it does contain the necessary components.
$itemListObject = $orderXML->getElementsByTagName('LineItemInfo');
var_dump($itemListObject->length);
var_dump($itemListObject);
The output of the var_dump is:
int(0)
object(DOMNodeList)#22 (0) {
}
This is my first time messing with this and it's taken me a couple of hours but I can't figure it out. Any advice would be awesome.
EDIT:
My XML looks like this... except with a lot more tags than just ProductId
<LineItems>
<LineItemInfo>
<ProductId href='[URL_TO_PRODUCT_XML]'>149593</ProductId>
</LineItemInfo>
<LineItemInfo>
<ProductId href='[URL_TO_PRODUCT_XML]'>149593</ProductId>
</LineItemInfo>
</LineItems>
Executing the following code does NOT get me the ProductId
$itemListObject = $orderXML->getElementsByTagName('LineItemInfo');
foreach ($itemListObject as $element) {
$product = $element->getElementsByTagName('ProductId');
$productId = $product->item(0)->nodeValue;
echo $productId.'-';
}
EDIT #2
As a side note, calling
$element->item(0)->nodeValue
on $element instead of $product caused my script's execution to discontinue and not throwing any errors that were logged by the server. It's a pain to debug when you have to run a credit card to find out whether it's functioning or not.
DOMDocument stuff can be tricky to get a handle on, because functions such as print_r() and var_dump() don't necessarily perform the same as they would on normal arrays and objects (see this comment in the manual).
You have to use various functions and properties of the document nodes to pull out the data. For instance, if you had the following XML:
<LineItemInfo attr1="hi">This is a line item.</LineItemInfo>
You could output various parts of that using:
$itemListObjects = $orderXML->getElementsByTagName('LineItemInfo');
foreach($itemListObjects as $node) {
echo $node->nodeValue; //echos "This is a line item."
echo $node->attributes->getNamedItem('attr1')->nodeValue; //echos "hi"
}
If you had a nested structure, you can follow basically the same procedure using the childNodes property. For example, if you had this:
<LineItemInfo attr1="hi">
<LineItem>Line 1</LineItem>
<LineItem>Line 2</LineItem>
</LineItemInfo>
You might do something like this:
$itemListObjects = $orderXML->getElementsByTagName('LineItemInfo');
foreach($itemListObjects as $node) {
if ($node->hasChildNodes()) {
foreach($node->childNodes as $c) {
echo $c->nodeValue .",";
}
}
}
//you'll get output of "Line 1,Line 2,"
Hope that helps.
EDIT for specific code and XML
I ran the following code in a test script, and it seemed to work for me. Can you be more specific about what's not working? I used your code exactly, except for the first two lines that create the document. Are you using loadXML() over loadHTML()? Are there any errors?
$orderXML = new DOMDocument();
$orderXML->loadXML("
<LineItems>
<LineItemInfo>
<ProductId href='[URL_TO_PRODUCT_XML]'>149593</ProductId>
</LineItemInfo>
<LineItemInfo>
<ProductId href='[URL_TO_PRODUCT_XML]'>149593</ProductId>
</LineItemInfo>
</LineItems>
");
$itemListObject = $orderXML->getElementsByTagName('LineItemInfo');
foreach ($itemListObject as $element) {
$product = $element->getElementsByTagName('ProductId');
$productId = $product->item(0)->nodeValue;
echo $productId.'-';
}
//outputs "149593-149595-"
XML tags tend to be lower-camel-case (or just "camel-case"), i.e. "lineItemInfo", instead of "LineItemInfo" and XML is case-sensitive, so check for that.

Categories