PHP XML DOM Document move sub-sub-nodes within sub-node - domdocument

I have an xml like this:
<?xml version="1.0" encoding="UTF-8"?>
<OrderListResponse>
<OrderListResponseContainer>
<DateFrom>2018-07-01T00:00:00+00:00</DateFrom>
<DateTo>2018-07-19T00:00:00+00:00</DateTo>
<Page>1</Page>
<TotalNumberOfPages>4</TotalNumberOfPages>
<Orders>
<Order>
<OrderID>158772</OrderID>
<Customer>
<Name><![CDATA[John Smith]]></Name>
<StreetAddress><![CDATA[33, Sunset Boulevrd]]></StreetAddress>
</Customer>
<Delivery>
<Name><![CDATA[John Smith]]></Name>
<StreetAddress><![CDATA[47, Rodeo Drive]]></StreetAddress>
</Delivery>
<Billing>
<Name><![CDATA[John Smith]]></Name>
<StreetAddress><![CDATA[33, Sunset Boulevrd]]></StreetAddress>
</Billing>
<Payment>
<Module>paypal</Module>
<TransactionID/>
</Payment>
<DatePurchased>2018-07-01 16:30:42</DatePurchased>
<DateLastModified>2018-07-02 21:08:28</DateLastModified>
<CheckoutMessage><![CDATA[]]></CheckoutMessage>
<Status>cancelled</Status>
<Currency>EUR</Currency>
<Products>
<Product>
<MxpID>44237</MxpID>
<SKU>IRF 8707TR</SKU>
<Quantity>3</Quantity>
<Price>2.46</Price>
</Product>
</Products>
<Total>
<SubTotal>7.38</SubTotal>
<Shipping>2.7</Shipping>
<Cod>0</Cod>
<Insurance>0</Insurance>
<Tax>1.62</Tax>
<Total>11.7</Total>
</Total>
</Order>
<Order>...</Order>
</Orders>
</OrderListResponseContainer>
</OrderListResponse>
and although surely there a better way to do it,
to parse all orders I build a routine like this:
$xmlDoc = new DOMDocument();
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->loadXML($response);
$xpath = new DOMXPath($xmlDoc);
$rootNode = $xpath->query('//OrderListResponseContainer/Orders')->item(0);
foreach($rootNode->childNodes as $node)
{
foreach($node->childNodes as $subnode)
{
Process User
foreach($subnode->childNodes as $subsubnode)
{
foreach($subsubnode->childNodes as $subsubsubnode)
{
Process Products and Sales
}
}
}
}
**** ADDED ****
I use the nested loops to create one xml for each product (each xml contains details about the buyer, the item and the
sale) and then this xml is passed to a Stored Procedure to generate
the user/item/sale records: For several reason I cannot bulky import
Users first, then Items and then Sales but while building the sale xml
I need some details from the Total Node and one way to get them is to
move Total Node on top of the XML, but clearly within the Order Node
**** ADDED ****
I need to access some Total subnodes before processing Products
The only solution I found is to move Total node at the beginning, but although many attempts, I've not been able to succeed:
The idea was to clone the totalNode and to appendbefore the OrderID Node
The problem is that I need to work on subdocuments and select the node to clone from a node itself, while all example I found do clone the full DocumentElement
perhaps an easier solution can be achieved using XSLT?
Can suggest a solution?

I don't completely understand what you are trying to say about the cloning part. Perhaps you can edit your question and clarify what you mean.
However, about accessing the Total nodes... you could simply use XPath for this as well.
$xmlDoc = new DOMDocument();
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->loadXML($response);
$xpath = new DOMXPath($xmlDoc);
// first, let's fetch all <Order> elements
$orders = $xpath->query('//OrderListResponseContainer/Orders/Order');
// loop through all <Order> elements
foreach( $orders as $order ) {
/*
There's all sorts of ways you could convert <Total> to something useful
*/
// Example 1.
// fetch <Total> that is a direct child (./) of our context node (second argument) $order
$total = $xpath->query( './Total', $order )->item( 0 );
// then do something like
$subTotal = $total->getElementsByTagName( 'SubTotal' )->item( 0 );
$shipping = $total->getElementsByTagName( 'Shipping' )->item( 0 );
// ... etc. for each child node of <Total>
// or perhaps simply convert it to a SimpleXMLElement
$total = simplexml_import_dom( $total );
var_dump( $total );
// and then access the values like this:
$total->SubTotal;
$total->Shipping;
// ... etc.
// Example 2.1
// fetch all children of <Total> into an array
$total = [];
foreach( $xpath->query( './Total/*', $order ) as $totalNode ) {
$total[ $totalNode->nodeName ] = $totalNode->textContent;
}
var_dump( $total );
// Example 2.2
// fetch all children of <Total> into a stdClass object
$total = new \stdClass;
foreach( $xpath->query( './Total/*', $order ) as $totalNode ) {
$total->{ $totalNode->nodeName } = $totalNode->textContent;
}
var_dump( $total );
/*
Now, after this you can create and process the Customer and Products data
in a similar fashion as I've shown how to process the Total data above
*/
}

Related

onFullSync action, show updated info - DHTMLX Grid + RoR

I have a Ruby on Rails project where I use a DHTMLX Grid.
Is there a way of showing, using the event handler "onFullSync" provided by the grid API, to show updated data?
Let me explain a little better... I know I can do something like:
dp.attachEvent("onFullSync", function(){
alert("update complete");
})
But what I want is something more complex. I want to, after each completed update, alter a div adding the information like this:
Field 2 was updated to XYZ and field 3 was updated to XER on line X
Field 1 was updated to 123 and field 3 was updated to XSD on line Y
Is this possible?
Thanks
There is a onAfterUpdate event that can be used similar to onFullSync
http://docs.dhtmlx.com/api__dataprocessor_onafterupdate_event.html
It will fire after each data saving operation ( if you are saving 5 rows - it will fire 5 times )
Still, info about updated columns will not be available here.
Also, you can try onEditCell event of grid. It fires after changing data in db, but before real saving in database. Here you can get all necessary info - row, column, old value and new value.
http://docs.dhtmlx.com/api__link__dhtmlxtreegrid_oneditcell_event.html
So, what I end up doing was:
After creating the grid I created an array:
var samples = [];
Then, as per #Aquatic suggestion, I added to "onEditCell" event the following line:
samples[samples.length] = grid.cells(rId, 5).getValue();
This allowed me to add to the array the value present on column 5 of the row changed. Then, on "onFullSync" event I hide or show the div created on the view with the messages (I distinguish if it's on row or more changed on the grid).
//Deals with messages after update
dp.attachEvent("onFullSync", function(){
unique_samples = uniq_fast(samples.sort());
if (unique_samples.length == 1){
$('#updated-samples').text("");
$(".messages").show();
$('#updated-samples').text("A seguinte amostra foi actualizada: " + unique_samples[0]);
//to clear the array
samples = [];
} else if (unique_samples.length > 1){
$('#updated-samples').text("");
$(".messages").show();
$('#updated-samples').text("As seguintes amostras foram actualizadas: " + unique_samples.sort().join(", "));
//to clear the array
samples = [];
} else {
$('#updated-samples').text("");
$(".messages").hide();
//to clear the array
samples = [];
}
})
The problem with using "onEditCell" is that everytime a field is changed on that row I get a repeated value on my "samples" array, I I had to remove duplicate from that array. For that I used one of the suggestions at this answer
// remove duplicates in array
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
Then I have on the beginning of the view, to show the messages:
<div class="alert alert-success messages" id="updated-samples">
And that's it. I could be not the most efficient way but it works for what I wanted. I will leave the question open a few more days to see if any other option appears.

Make message_tags linkable using offset attribute in facebook api

I'm getting this post object from Facebook api :
{
"id"=>"XXX",
...,
"message"=>"abcd efg hijkl mn The New York Times opqr",
"message_tags"=>{
"18"=>[{
"id"=>"5281959998",
"name"=>"The New York Times",
"type"=>"page",
"offset"=>18,
"length"=>18
}]
},
...
}
How can I make a link in rails to facebook page like using offset & length attributes ? Result would be like this :
abcd efg hijkl mn The New York Times opqr
I had to do this just now, but in PHP. This was my solution:
/**
* Get message HTML
*
* #param string $text The message text
* #param array $tags The tags array
* #return string HTML message string with linked tags
*/
public static function getMessageHtml($text, $tags) {
$doc = new DOMDocument;
$doc->appendChild($doc->createTextNode($text));
if (is_array($tags)) {
foreach ($tags as $tag) {
$tag = $tag[0];
$start = 0;
foreach ($doc->childNodes as $child) {
if ($tag['offset'] < $start + strlen($child->nodeValue)) {
$meat = $child->splitText($tag['offset'] - $start);
$tail = $meat->splitText($tag['length']);
$a = $doc->createElement('a');
$a->setAttribute('href', '//facebook.com/' . $tag['id']);
$a->setAttribute('title', $tag['name']);
$meat->parentNode->replaceChild($a, $meat);
$a->appendChild($meat);
break;
}
$start += strlen($child->nodeValue);
}
}
}
return trim($doc->saveHTML());
}
This has worked in all my test cases so far, but it may be dodgy if tags overlap or are nested. I'm not sure if Facebook ever returns tags like that.
Hopefully you can easily port this to Ruby.

Is it possible to access the matrix parameters (name-value pair separated by semicolon) in ColdFusion?

I'm new to matrix parameter and I know CF10 can access them through their new RESTful API support.
However, is there a way to access these parameters without using RESTful API support?
E.g. http://moremaps.com/map/color.cfm;lat=50;long=20;scale=32000
You can use:
color.cfm;lat=50;long=20;scale=32000
Then get the param string with:
ListRest(getPageContext().getRequest().getRequestUri(),';')
This worked back in CFMX - it's not specific to CF10 or part of the RESTful API, and is available due to the servlet container (Tomcat/Jrun/etc) following the servlet spec with the ability to get the original URL.
(And you can of course use URL rewriting to hide the .cfm from the user.)
There isn't a matrix scope because CF hasn't implemented it fully - it is done as part of REST webservices, (where it's as an argument with the appropriate RestArgSource attribute). Only the CF team can say why they designed it that way.
However, you can easily create your own scope/struct like so:
var MatrixString = ListRest(getPageContext().getRequest().getRequestUri(),';');
var Matrix = {};
for ( var CurParam in ListToArray(MatrixString,';') )
Matrix[ UrlDecode( ListFirst(CurParam,'=') ) ] = UrlDecode( ListRest(CurParam,'=') );
(Obviously, remove the var scoping if not using inside a function.)
That works both directly and via IIS, and should work fine on other servers too, even where path_info may have been modified.
Update: This answer is incomplete/inaccurate.
Reading up further on matrix params, they can actually appear at any point in the request_uri (the entire part after the host_name, before the query_string) - that is, both script_name and path_info can contain parameters, without affecting their final value.
To clarify this, both these URLs:
htp://domain.com/a/b.cfm/c/d
http://domain.com/a;alpha=1/b.cfm;beta=2/c;gamma=3/d;delta=4
Result in these CGI vars:
script_name = /a/b.cfm
path_info = /c/d
(Except in IIS, where path_info is incorrectly implemented.)
Obviously extracting and acting upon these properties is more complex than the code above - I'll update this answer again once I've made sure I understand them more fully.
In the meantime, here's a couple of potential options - the first returns a struct of params if a path element has one, the second returns an array containing every path element - whether either of these are suitable will depend on how the matrix params are to be used:
<cffunction name="getMatrixStruct" returntype="Struct" output=false
hint="Returns struct with item for each path element with params"
>
<cfargument name="RequestUri" type="String" required hint="The non-host and non-querystring part of a URL." />
<cfscript>
var Result = {};
for ( var CurSegment in ListToArray(RequestUri,'/') )
{
var SegName = UrlDecode( ListFirst(CurSegment,';') );
for ( var CurParam in ListToArray(ListRest(CurSegment,';')) )
Result[SegName][UrlDecode( ListFirst(CurParam,'=') ) ] = UrlDecode( ListRest(CurParam,'=') );
}
return Result;
</cfscript>
</cffunction>
<cffunction name="getMatrixArray" returntype="Array" output=false
hint="Returns array of structs for all path element, with any parameters included."
>
<cfargument name="RequestUri" type="String" required hint="The non-host and non-querystring part of a URL." />
<cfscript>
var Result = [];
var Pos = 0;
for ( var CurSegment in ListToArray(RequestUri,'/') )
{
Result[++Pos] = { 'Name' = UrlDecode( ListFirst(CurSegment,';') ) };
for ( var CurParam in ListToArray(ListRest(CurSegment,';')) )
Result[Pos][UrlDecode( ListFirst(CurParam,'=') ) ] = UrlDecode( ListRest(CurParam,'=') );
}
return Result;
</cfscript>
</cffunction>
You can access it via cgi.path_info. For example:
http://localhost/myApp/index.cfm;this=that;pet=cat
Becomes
cgi.PATH_INFO=/MyApp/index.cfm;this=that;pet=cat
Then you can
struct function getMatrix() output="false" {
var arURLData = ListToArray(cgi.path_Info, ";");
var stData = {};
if (arrayLen(arData) <= 1) {
return stData;
}
for(var i = 2; i <= ArrayLen(arData); i++) {
// setVariable("stData.#listfirst(arURLData[i], "=")#", listlast(arURLData[i]);
stData[listfirst(arURLData[i], "=")] = getToken(arURLData[i], 2, "=");
}
return stData;
}
You will probably want to add some code to protect against URL injection attacks.

Return fixed number of items from a directory in reverse alphabetical order

I have files in directories that are labeled by date (eg, 2012-07-05.xls, 2012-07-04.xls) and I want only to list (and link) the last 10 files in that directory, starting with the newest one. I can generate the list easily in the proper order, but can't figure out how to limit it to 10 (and also not return an "." and ".." entry for the directories).
Here's what I have now. Open to all suggestions.
<?php
$path = $_SERVER[DOCUMENT_ROOT]."/path/";
$dh = #opendir($path);
$files = array();
while (false !== ($file = readdir($dh))) {
array_push($files, $file);
}
rsort($files);
foreach ($files as $file){
echo "<li><a href=\"$file\">";
echo($file)."</a></li>";
}
?>
If you use scandir you can add a sorting parameter as second argument. Then you can just splice the array with array_slice to get the elements you want.
$array = scandir($path);
$files = array_slice($array, 0, 10);
to get the files you want or skip the first 2 elements because of the . and .. you can do array_slice($array, 2, 10);

Open Source Projects for i18n à la Facebook

Facebook has this unique and clever approach to localization of their site: translators (in their case users that help to translate the site voluntarily) can simply click on the not-yet-translated strings – which are marked with a green bottom border – in their natural context on the site. See http://www.facebook.com/translations/.
Now, if you ever had to deal with the translation of a website, you'll be well aware of how odd and funny some of these translations can be when using tools like poedit where the translator isn't fully aware of the spot the translated string will lated appear in on the website.
Example: Please translate "Home". In German, for instance, the start page of a website would be "Home" while the house you live in is "Heim". Now, you as the translator basically have to guess which context this term is likely to appear in on the website and translate accordingly. Chances are, you're new website on home furniture now translates as "Home-Einrichtung" which sounds ridiculous to any German.
So, my question boils down to:
Do you know any open source PHP projects that work on something like this? I'm basically looking for a framework that allows you to put your internationalized website in "translation mode" and make strings clickable and translatable e.g. through a Javascript modal.
I'm not so much looking for a full-fledged and ready-made solution, but would love to know about similar projects that I can contribute code to.
Thanks in advance!
If you want to roll your own with jquery & jquery browserLanguage, this might get you going.
Tag all translatable text's contain elements with class="i18n", and include jquery, jquery browserLanguage, and your i18n script.
1. the internationalization javascript
— this needs to accept translations via ajax from your server, like:
var i18n = {};
i18n.bank = new Array();
i18n.t = function ( text, tl=$.browserLanguage ) {
var r = false;
$.ajax({
url: "/i18n_t.php?type=request&from="+ escape(text) +"&tl="+ tl,
success: function(){ i18n.bank[text] = this; r = true; }
});
return r;
};
2. php i18n translation service
— now we need to serve up translations, and accept them
the database will look like a bunch of tables, one for each language.
// SCHEMA for each language:
CREATE TABLE `en`
(
`id` INT PRIMARY KEY AUTO INCREMENT NOT NULL,
`from` VARCHAR(500) NOT NULL,
`to` VARCHAR(500) NOT NULL
)
the php will need some connection and db manipulation.. for now this may do:
//Connect to the database
$connection = mysql_connect('host (usually localhost)', 'mysql_username' , 'mysql_password');
$selection = mysql_select_db('mysql_database', $connection);
function table_exists($tablename, $database = false) {
if(!$database) {
$res = mysql_query("SELECT DATABASE()");
$database = mysql_result($res, 0);
}
$res = mysql_query("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = '$database' AND table_name = '$tablename'
");
return mysql_result($res, 0) == 1;
}
the code is simply:
<?php
// .. database stuff from above goes here ..
$type=$_GET["type"];
$from=$_GET["from"];
$to=$_GET["to"];
$tl=$_GET["tl"];
if (! table_exists($tl)) {
...
}
if ($type == "request") { // might want to set $tl="en" when ! table_exists($tl)
$find = mysql_query("SELECT to FROM `'$tl'` WHERE from='$from'");
$row = mysql_fetch_array($find);
echo $row['to'];
} elsif ($type == "suggest") {
$find = mysql_query("SELECT COUNT(*) AS count FROM `'$tl'` WHERE from='$from'");
if ( !(mysql_result($res, 0)) == 0 ) {
$ins = mysql_query("INSERT INTO `'$tl'` (from, to) VALUES ('$from','$to')");
}
}
?>
3. page translation mechanics
— finally we can tie them together in your webpages with some further jquery:
i18n.suggest = function (from) { // post user translation to our php
$.ajax({
url: "/i18n_t.php?type=suggest&from='+from+'&to="+ escape( $('#i18n_s').contents() ) +"&tl="+ $.browserLanguage,
success: function(){ $('#i18n_t_div').html('<em>Thanks!</em>').delay(334).fadeOut().remove(); }
});
};
$(document).ready(function() {
i18n.t("submit");
i18n.t("Thanks!");
$('.i18n').click( function(event) { //add an onClick event for all i18n spans
$('#i18n_t_div').remove;
$(this).parent().append(
'<div id="i18n_t_div"><form class="i18n_t_form">
<input type="text" id="i18n_s" name="suggestion" value="+$(this).contents()+" />
<input type="button" value="'+ i18n.bank[ "submit" ] +'" onclick="i18n.suggest( '+$(this).contents()+' )" />
</form></div>'
);
}).each(function(){
var c = $(this).contents(); //now load initial translations for browser language for all the internationalized content on the page
if ( i18n.t(c) ){
$(this).html(i18n.bank[c]);
}
});
});
Mind you I don't have a server to test this on... and I don't actually code php. :D It will take some debugging but the scaffolding should be correct.

Resources