The XML I am currently trying to build needs to have two <Location> elements, one for origin, one for destination, found in the same parent element. I append the first child without a problem, but the elements that would go to the second child end up on the first.
The DOMDocument is being created with a helper function that excepts data for the current node/children.
public function set_data(Array $data, $root = null){
//Check if element exists
$dom = ($root === null)? $dom = $this->xml : $this->xml->getElementsByTagName($root)->item(0);
//Create the element
$element = ( ! empty( $data['value'] ) ) ? $this->xml->createElement( $data['element'], $data['value'] ): $this->xml->createElement( $data['element']);
//Add any attributes
if ( !empty( $data['attributes'] ) && is_array( $data['attributes'] ) ) {
foreach ( $data['attributes'] as $attribute_key => $attribute_value ) {
$element->setAttribute( $attribute_key, $attribute_value );
}
}
$dom->appendChild($element);
//add children
foreach ( $data as $data_key => $child_data ) {
if(is_array($child_data) && is_numeric($data_key)){
$this->set_data($child_data, $data['element']);
}
}
return true;
}
Input example:
array('element' => 'ShipmentLocations',
array('element' => 'Location',
array('element' => 'LocationType', 'value' => 'Origin'),
array('element' => 'HasLoadingDock', 'value' => 'false')
),
array('element' => 'Location',
array('element' => 'LocationType', 'value' => 'Destination'),
array('element' => 'HasLoadingDock', 'value' => 'true')
)
)...
This function uses getElementsByTagName to see if the element exists, if so then add the child to it. I understand something here needs to be changed. Is there currently a way to explicitly state that the child being added should go to the second element, not the first with the identical name? If not, what would be the best work around for this, can namespaces be added with no problem?
Thanks,
Related
I have an array of files with dependencies. I need them sorted so all dependent files are indexed after their dependency. I've made a lot of attempts with forEach loops, while loops, but once a dependency is moved, the loops don't account for previous iterations and indexed elements wind up out of order.
I have a simplified version of the data I am working with:
$dependencies = array(
array(
'handle' => 'jquery',
'requires' => array(
'jquery-core',
'jquery-migrate'
)
),
array(
'handle' => 'jquery-migrate',
'requires' => array()
),
array(
'handle' => 'common',
'requires' => array(
'utils',
'jquery',
'jquery-core',
'jquery-migrate',
'jquery-effects-core',
'backbone'
)
),
array(
'handle' => 'jquery-effects-core',
'requires' => array(
'jquery',
'jquery-core',
'jquery-migrate'
)
),
array(
'handle' => 'backbone',
'requires' => array(
'underscore',
'jquery'
)
),
array(
'handle' => 'underscore',
'requires' => array()
),
array(
'handle' => 'utils',
'requires' => array()
),
array(
'handle' => 'jquery-core',
'requires' => array()
)
);
If [handle] appears in an elements [requires] array, that element needs to be moved ahead of the indicated [requires] element, all while maintaining any other dependencies previously accounted for.
function moveEle(&$array, $a, $b){
$out = array_splice($array, $a, 1);
array_splice($array, $b, 0, $out);
}
foreach($dependencies as $i=>$dependency){
if( count($dependency['requires'])>0 ){
$itr = count($dependency['requires']);
echo $dependency['handle']."<br/>";
while($itr > 0){
// loop through current files required files
foreach( $dependency['requires'] as $k=>$dep ){
// loop through dependencies array again to find required file handle
echo "-- " . $dep . "<br/>";
foreach($dependencies as $j=>$jDep){
// $j = index in dependencies array of required file, $i = index in dependencies array of dependent file.
if( $dep === $jDep['handle'] && $j > $i ){
echo "found " . $jDep['handle'] . "# " . $j."<br/>";
moveEle(&$dependencies, $j, $i );
}
}
$itr--;
}
}
}
}
I'm taking it there is probably some recursive way to do this that is a little beyond my scope of skill at this point. Any help would be greatly appreciated.
I did find this solution:
PHP Order array based on elements dependency
which does work, however, once the array gets to 150 files (the actual data size), it times out or takes an incredibly long time. I'd like a more efficient solution, if one exists.
Well, I got the desired results with this:
/*
*
* #description return nested array with all dependencies & sub dependencies
*
**/
function getWithDependencies($dependency, $collection){
$helper = new Insight_WP_Scripts_Helpers();
$set = array(
'handle' => $dependency,
'dependencies' => array()
);
foreach($collection as $index=>$data){
if( $data['handle'] === $set['handle'] ){
// echo "<h4>".$data['handle']."</h4>";
if(count($helper->getDependencies($set, $collection))>0){
$dependencies = $helper->getDependencies($set['handle'], $collection);
foreach($dependencies as $i=>$dependency){
// echo $dependency . "<br/>";
$set['dependencies'][$i] = array(
'handle' => $dependency,
'dependencies' => array()
);
if( count($helper->getDependencies($dependency, $collection)) > 0 ){
foreach(getWithDependencies($dependency, $collection) as $k=>$dep){
$set['dependencies'][$i]['dependencies'] = $dep;
}
}
}
}
}
}
return $set;
}
/*
*
* #description recursively sorts dependencies to depth returned by getWithDependencies()
*
**/
function sortDependency($data,&$collection){
$helper = new Insight_WP_Scripts_Helpers();
foreach($collection as $index=>$inst){
if( $inst['handle'] === $data['handle'] && count($data['dependencies'])>0){
// iterate over each dependency
foreach( $inst['dependencies'] as $i=>$dependency ){
// iterate over the collection to find dependencies position for comparison against dependent file.
foreach( $collection as $k=>$kdep ){
if($kdep['handle'] === $dependency['handle'] && $index < $k){
$helper->moveArrayElement($collection, $k, $index);
// call recursively to search full depth
if( count($dependency['dependencies']) > 0 ){
sortDependency($dependency,$collection);
}
}
}
}
}
}
}
foreach( $dependencies as $index=>$data ){
$fullDependencies = array();
foreach($dependencies as $i=>$dependency){
$fullDependencies[] = getWithDependencies($dependency['handle'], $dependencies);
}
}
$_fullDependencies = $fullDependencies;
foreach( $fullDependencies as $index=>$data ){
sortDependency($data,$_fullDependencies);
}
$fullDependencies = $_fullDependencies;
return $fullDependencies;
I am using following code to parse XML:
foreach ($rss->getElementsByTagName('item') as $node) {
$item = array(
'title' => $node->getElementsByTagName('title')->item(0)->nodeValue,
'desc' => $node->getElementsByTagName('enclosure')->item(0)->attributes['url']->nodeValue,
'link' => $node->getElementsByTagName('link')->item(0)->nodeValue
);
array_push($feed, $item);
}
It works perfectly on localhost without any errors. On the server, however, I get Cannot use object of type DOMNamedNodeMap as array error for the middle line which stores desc. What is causing this issue?
The problem is the line:
$node->getElementsByTagName('enclosure')->item(0)->attributes['url']->nodeValue
You're fetching an element node with:
$node->getElementsByTagName('enclosure')->item(0)...
Element nodes have an attributes property, that is an instance of DOMNamedNodeList. This is not an array and does not implement ArrayAccess. So you can not access the attribute nodes using array syntax. You will have to use the method DOMNamedNodeList::getNamedItem().
$node
->getElementsByTagName('enclosure')
->item(0)
->attributes
->getNamedItem('url')...
This will return the attribute node, so you can access its value with DOMAttribute::$value.
$node
->getElementsByTagName('enclosure')
->item(0)
->attributes
->getNamedItem('url')
->value
However you can fetch the attribute value directly from the element node using DOMElement::getAttribute().
$node
->getElementsByTagName('enclosure')
->item(0)
->getAttribute('url')
An even better way is to use XPath. DOMXpath::evaluate() allows you to use expression to fetch nodes and scalar values from an DOM.
$rss = new DOMDocument();
$rss->loadXml($rssFeedXml);
$xpath = new DOMXpath($rss);
foreach ($xpath->evaluate('//item') as $itemNode) {
$feed[] = [
'title' => $xpath->evaluate('string(title)', $itemNode),
'desc' => $xpath->evaluate('string(enclosure/#url)', $itemNode),
'link' => $xpath->evaluate('string(link)', $itemNode)
];
}
You could try explicitly casting the nodelist as an array to see if that resolves things - totally untested btw.
<?php
$items=$rss->getElementsByTagName('item');
if( is_object( $items ) ){
$col=(array)$items;
if( !is_array( $col ) ) exit('Not an array!');
foreach ( $col as $node ) {
$item = array(
'title' => $node->getElementsByTagName('title')->item(0)->nodeValue,
'desc' => $node->getElementsByTagName('enclosure')->item(0)->attributes['url']->nodeValue,
'link' => $node->getElementsByTagName('link')->item(0)->nodeValue
);
array_push( $feed, $item );
}
} else {
exit( 'Type: '.gettype( $items ) );
}
?>
This is my simple looper code
foreach( $cloud as $item ) {
if ($item['tagname'] == 'nicetag') {
echo $item['tagname'];
foreach( $cloud as $item ) {
echo $item['desc'].'-'.$item['date'];
}
} else
//...
}
I need to use if method in this looper to get tags with same names but diferent descriptions and dates. The problem is that I dont know every tag name becouse any user is allowed to create this tags.
Im not really php developer so I'm sory if it's to dummies question and thanks for any answers!
One possible solution is to declare a temporary variable that will hold tagname that is currently looped through:
$currentTagName = '';
foreach( $cloud as $item ) {
if ($item['tagname'] != $currentTagName) {
echo $item['tagname'];
$currentTagName = $item['tagname'];
}
echo $item['desc'] . '-' . $item['date'];
}
I presume that your array structure is as follows:
$cloud array(
array('tagname' => 'tag', 'desc' => 'the_desc', 'date' => 'the_date'),
array('tagname' => 'tag', 'desc' => 'the_desc_2', 'date' => 'the_date_2'),
...
);
BUT
This solution raises a problem - if your array is not sorted by a tagname, you might get duplicate tagnames.
So the better solution would be to redefine your array structure like this:
$cloud array(
'tagname' => array (
array('desc' => 'the_desc', 'date' => 'the_date'),
array('desc' => 'the_desc_2', 'date' => 'the_date_2')
),
'another_tagname' => array (
array('desc' => 'the_desc_3', 'date' => 'the_date_3'),
...
)
);
and then you can get the data like this:
foreach ($cloud as $tagname => $items) {
echo $tagname;
foreach($items as $item) {
echo $item['desc'] . '-' . $item['date'];
}
}
I've an array that has around 300 items. There are 5 properties per item. Some items have a parent item. the nested items need to be able to scale to unlimited nesting.
I need to organise the array so that all items that have a parent are within the parent item children property. The child will have children
I can create an associative array that makes the first 2 levels. The top parent items and it's children.
Now my problem comes when I am trying to create unlimited nesting. I can't see a way to automatically create a method that will adapt to multiple nesting levels and put the information to where it should be nested to.
I've never had to build such a tree and performance is crucial. Could anyone give me guidance on how could I achieve what I intend to achieve? I would really appreciate it
Edit: my issue is if i have to use recursion, not how to split what i've just done into 1 separate method.
$tree = array();
/*
* $three = array( array( 'id','parent_id','displayAs', 'children' ) )
*/
foreach ( $taxonomyFullList as $firstLevel ) {
// Get all
if( (int)$firstLevel['parent_id'] == 0 ) {
$tree[] = array(
'id' => $firstLevel['id'],
'parent_id' => $firstLevel['parent_id'],
'displayAs' => $firstLevel['displayAs'],
'type' => $firstLevel['type'],
'children' => array()
);
$key = array_search( $firstLevel,$taxonomyFullList );
unset( $taxonomyFullList[$key] );
}
}
foreach ( $taxonomyFullList as $secondLevel ) {
foreach ( $tree as $firstTreeLevel ) {
if( (int)$secondLevel['parent_id'] === (int)$firstTreeLevel['id'] ) {
$newArray = array(
'id' => $secondLevel['id'],
'parent_id' => $secondLevel['parent_id'],
'displayAs' => $secondLevel['displayAs'],
'type' => $secondLevel['type'],
'children' => array()
);
$key = array_search( $firstTreeLevel, $tree );
array_push( $tree[$key]['children'], $newArray );
$taxonomyFullListKey = array_search( $secondLevel,$taxonomyFullList );
unset( $taxonomyFullList[$taxonomyFullListKey] );
}
}
}
It depends on your data but you'll probably have to use recursion somewhere. First, create a function that populates one item then, when this item has children, reuse the same function.
Basically, you'll need to have something like this:
function createItem() {
$item = array();
// ...
// populate the item properties
// ...
$item['children'] = createItem();
return $item;
}
$root = createItem();
So I am having issues understanding how to iterate over an array that looks as such in php:
$styles = array(
'css' => array(
'name' => array(
'core-css',
'bootstrap-css',
'bootstrap-responsive-css'
),
'path' => array(
get_bloginfo('stylesheet_url'),
get_template_directory_uri() . '/lib/bootstrap/css/bootstrap.min.css',
get_template_directory_uri() . '/lib/bootstrap/css/bootstrap.responsive.min.css'
),
),
);
Essentially this styles gets passed to a constructor of a class which then iterates over the array in a method that looks like this (note this array is stored class level in a protected value called _options, hence the $this->_options in the following code:
foreach ( $this->_options as $key => $value ) {
// load all the css files
if (isset ( $this->_options ['css'] )) {
foreach ( $value ['name'] as $name ) {
foreach ( $value ['path'] as $path ) {
wp_enqueue_style ( $name, $path );
}
}
}
}
This will spit out something like:
core-css style.css
core-css bootstrap
core-css bootstrap-responsive
.
The problem should be clear right now, the name never changes and I think it has something to do with how I am iterating over the array of, essentially, arrays.
So your help is much appreciated.
The path array is not apart of the name array, and should look something like:
foreach ( $this->_options as $key => $value ) {
// load all the css files
if (isset ( $this->_options ['css'] )) {
foreach ( $value ['path'] as $k=>$path ) {
wp_enqueue_style ( $value['name'][$k], $path );
}
}
}
Or, you could always change the structure of your array:
$styles = array(
'css' => array(
array(
'name'=>'core-css',
'path'=>get_bloginfo('stylesheet_url')
),
array(
'name'=>'bootstrap-css',
'path'=>get_template_directory_uri() . '/lib/bootstrap/css/bootstrap.min.css'
),
array(
'name'=>'bootstrap-responsive-css',
'path'=>get_template_directory_uri() . '/lib/bootstrap/css/bootstrap.responsive.min.css'
)
)
);
foreach($this->_options as $key => $value){
if (isset ( $this->_options ['css'] )) {
foreach($this->_options ['css'] as $k=>$v){
wp_enqueue_style ( $v['name'], $v['path'] );
}
}
}
Hope that order matches, other way you in trouble.
Something like this:
if (isset($this->_options['css'])) {
foreach ($this->_options['css']['name'] as $key => $name) {
echo $name; // Your name
echo $this->_options['css']['path'][$key]; // Your math
}
}