php DOMDocument: html to node tree - php

This function converts html into a node tree (<ul> structure). However, every node is returned as a child node of the preceding node even if that node was a sibling of the current node.
$xml = '
<div>
<div>
<b></b>
</div>
<p></p>
</div>
';
function xml2array($xml,&$result = '') {
foreach($xml->children() as $name => $xmlchild) {
xml2array($xmlchild, $result);
}
$result = "<ul><li>".$xml->getName().$result."</li></ul>";
}
$result='';
$dd = xml2array(simplexml_load_string($xml), $result);
echo "<pre>";
print_r($result);
the above code returns this:
<ul>
<li>div
<ul>
<li>p
<ul>
<li>div
<ul>
<li>b</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
you notice 'div' is now a child of 'p' even though they are siblings, and 'div' comes after 'p' unlike the orignal input.
this is how it should look:
<ul>
<li>div
<ul>
<li>div
<ul>b</ul>
</li>
<li>p</li>
</ul>
</li>
</ul>

Here you go, a non-broken function, with the call by reference removed because if you don't know exactly what they are, possibilities of obscure bugs occur (and you might know how/when to use them, the next coder might struggle, references are best used with a very clear reason or not at all):
<?php
$xml = '
<div>
<div>
<b></b>
</div>
<p></p>
</div>
';
function xml2ul($xml) {
$children = $xml->children();
if(empty($children)) return '';
$result = '<ul>';
foreach($xml->children() as $name => $xmlchild) {
$result .= '<li>'.$name.xml2ul($xmlchild).'</li>';
}
$result .= '</ul>';
return $result;
}
echo xml2ul(simplexml_load_string($xml), $result);

Related

How to know I'm in the first cycle of fetchArray() loop

I want to print an attribute (i.e. active) on the first element of an array.
The ugly way is to use a variable:
<ul class="nav nav-pills">
<?php $firstItem = true; ?>
<?php while ($row = $res_sections->fetchArray()) { ?>
<li class="nav-item">
<a class="nav-link<?php if ($firstItem) echo ' active' ?>" data-toggle="pill" href="#nav<?= $row['section']?>"><?php echo "{$row["section"]}"?></a>
</li>
<?php $firstItem = false; ?>
<?php } ?>
</ul>
I wonder if there's something more elegant.
Is there a way to know if $row is the first element (or any) of the fetchArray() result?
EDIT
Here where fetchArray() comes from:
$db = new SQLite3("/db.sqlite");
$sql_sections = "SELECT DISTINCT section FROM settings";
$res_sections = $db->query(SQLite3::escapeString($sql_sections));

<UL> basesd on common id fetch from databse

I'm trying to iterate through my array and group li's with their relative ul, based on a common id.
expected result is as follows
<ul>
<li>b</li>
<li>c</li>
<li>a</li>
<li>g</li>
<li>e</li>
</ul>
<ul>
<li>d</li>
<li>f</li>
<li>i</li>
</ul>
<ul>
<li>d</li>
<li>f</li>
<li>i</li>
</ul>
I have tried following code
<?php
$IMPLODED_trid = 1,2,3,4,5,6 ;
$result=mysqli_query('SELECT * FROM tablegroup where id IN ($IMPLODED_trid)');
while($row=mysqli_fetch_array($result))
{
$name=$row['name'];
?>
<ul>
<li><?php $name ;?></li>
</ul>
<?php
}
?>
but above code gives following result
<ul>
<li>c</li>
</ul>
<ul>
<li>a</li>
</ul>
<ul>
<li>d</li>
</ul>
<ul>
<li>i</li>
</ul>
You'll want to use two loops.
One outer for generating the <ul> elements, another for generating the <li> elements within the <ul>
Such code might look like this
<?php
$result = []; //grouped by ul ID
for ($i = 1; $i < count($ids); $i++) {
echo '<ul>';
while ($row = $result[$i])
echo '<li>' . $result[$i]['name'] . '</li>';
echo '</ul>';
}
?>
It's valid PHP but it doesn't work ofcourse.
This is the structure you're looking for tho since you'll want an outer loop to go through the amount of <ul> elements you need and within that loop you'll want to loop through the list-items themselves.
The outer loop wouldn't be needed if you only had one <ul> ofcourse.
Your code is wrong. Put ul tag out of the body of while loop. Try this:
<ul>
<?php
$IMPLODED_trid = 1,2,3,4,5,6 ;
$result=mysqli_query('SELECT * FROM tablegroup where id IN ($IMPLODED_trid)');
while($row=mysqli_fetch_array($result))
{
$name=$row['name'];
?>
<li><?php $name ;?></li>
<?php
}
?>
</ul>

Get DIV Element contents thru DOMDocument PHP

I have to recover some news from a div of a site. The div is structured as follows:
The HTML Markup:
<ul id="news-accordion" class="rounded" style="padding: 2px;">
<li class="o">
<h3>
<span>TITLE ARTICLE</span>
<span>30/10/2014</span>
</h3>
<div style="display: none;">
<p>text of article</p>
</div>
</li>
<li class="e">
<h3>
<span>TITLE ARTICLE</span>
<span>28/10/2014</span>
</h3>
<div style="display: none;">
<p>text of article</p>
</div>
</li>
<li class="o">
<h3>
<span>TITLE ARTICLE</span>
<span>29/10/2014</span>
</h3>
<div style="display: none;">
<p>text of article</p>
</div>
</li>
</ul>
PHP
<?php
$doc = new DomDocument;
$doc->validateOnParse = true;
$doc->loadHtml(file_get_contents('http://www.xxxxxxxxx/news.php'));
$news = $doc->getElementById('news-accordion');
$li = $news->getElementsByTagName('li');
foreach ($li as $row){
$title = $row->getElementsByTagName('h3');
echo $title->item(0)->nodeValue."<br><br>";
/*foreach ($title as $row2){
echo $row2->nodeValue."<br><br>";
//echo $row2->item(0)->nodeValue."<br><br>";
}*/
$text = $row->getElementsByTagName('p');
echo utf8_decode($text->item(0)->nodeValue)."<br><br><br>";
}
?>
The code works correctly, but when I print the contents of the span tag echo $title->item(0)->nodeValue;,
The text of the two span is printed together.
How can I take the contents of the two span separately? Thanks.
Yes you can, just adjust the ->item() index. Just like what you have done already in the other elements, point it to that header element, then just explicitly point it to those span children:
foreach ($li as $row){
$h3 = $row->getElementsByTagName('h3')->item(0);
$title = $h3->getElementsByTagName('span')->item(0); // first span
$date = $h3->getElementsByTagName('span')->item(1); // second span
echo $title->nodeValue . '<br/>';
echo $date->nodeValue . '<br/>';
$text = $row->getElementsByTagName('p');
echo utf8_decode($text->item(0)->nodeValue)."<br><br><br>";
}
$title = $row->getElementsByTagName('h3');
echo $title->item(0)->nodeValue."<br><br>";
Replace above two line with below (instead of using h3 tag use span tag)
$title = $row->getElementsByTagName('span');
echo $title->item(0)->nodeValue."<br><br>";
echo $title->item(1)->nodeValue."<br><br>";
It's working for me.

Build dynamic Mega Menu using recursion?

I'm trying to create a Mega Menu using PHP and I'm having a problem getting the structure to output correctly. I've hard-coded the Mega Menu to test everything and it works fine, but obviously I need PHP to create it for me.
I have an example with the Mega Menu hard-coded so everyone can see what I'm trying to create:
http://www.libertyeaglearms.com/dev
Or here's the code:
DESIRED OUTPUT:
<div id="wrapper">
<ul class="mega-menu">
<li class="mega-menu-drop">
Firearms
<div class="mega-menu-content">
<div class="column">
<h4>Rifles</h4>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
</div>
<div class="column">
<h4>Handguns</h4>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
</div>
<div class="column">
<h4>Shotguns</h4>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
</div>
</div>
</li>
<li class="mega-menu-drop">
Archery
<div class="mega-menu-content">
<div class="column">
<h4>Bows</h4>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
</div>
<div class="column">
<h4>Arrows</h4>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
</div>
</div>
</li>
</ul>
</div>
CURRENT OUTPUT: (very messed up. be warned, lol)
<div id="wrapper">
<ul class="mega-menu">
<li class="mega-menu-drop">
archery
<div class="mega-menu-content">
<div class="column">
<h4>compound</h4>
<ul>
<h4>bows</h4>
<ul>
</div>
</li>
<li class="mega-menu-drop">
firearms
<div class="mega-menu-content">
<div class="column">
<h4>rifle ammunition</h4>
<ul>
<h4>ammunition</h4>
<ul>
</div>
</li>
</ul>
</div>
HERE MY PHP:
$json = json_decode($category->buildDepartments(NULL),true);
function buildDepartments($array,$parent)
{
$html = '';
foreach($array as $category)
{
if($category['parent'] == $parent)
{
if($category['parent'] == NULL)
{
$html .= '<li class="mega-menu-drop">' . "\n\t\t\t\t";
$html .= ''.$category['category_name'].'' . "\n\t\t\t\t";
$html .= '<div class="mega-menu-content">' . "\n\t\t\t\t\t";
$html .= '<div class="column">' . "\n\t\t\t\t\t\t";
$html .= buildDepartments($array,$category['category_id']);
$html .= '</div>' . "\n\t\t\t";
$html .= '</li>' . "\n\t\t\t";
}
else
{
$html .= buildDepartments($array,$category['category_id']);
$html .= '<h4>'.$category['category_name'].'</h4>' . "\n\t\t\t\t\t\t";
$html .= '<ul>' . "\n\t\t\t\t";
}
}
}
return $html;
}
print(buildDepartments($json,NULL));
HERE'S MY DATABSE:
EDIT AFTER BOUNTY
Building off what icktoofay suggested, I cannot seem to figure out the foreach() loops. The problem I'm getting is I get the department name inside the mega menu when I should see category and sub categories. I think the problem is I need to perform a loop with a specific parent id in order to get all the children, but I'm not really sure if that's the problem. Here's the code:
<div id="wrapper">
<ul class="mega-menu">
<?php $json = json_decode($category->buildDepartments(NULL)); ?>
<?php foreach($json as $category): ?>
<li class="mega-menu-drop">
<?php if($category->parent === NULL){echo $category->category_name;} ?>
<div class="mega-menu-content">
<?php foreach($category as $subcategory): ?>
<div class="column">
<?php if($category->category_id === $category->parent){echo '<h4>' . $category->category_name . '</h4>';}?>
<ul>
<?php foreach($category as $subcategory)
{
echo '<li>' . $category->category_name . '</li>';
}
?>
</ul>
</div>
<?php endforeach; ?>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
Since it's a fixed depth and the format for each level is different, recursion may not be appropriate. Additionally, putting everything in strings is inelegant. Instead, you may want to try weaving the HTML and loops together like this:
<div id="wrapper">
<ul class="mega-menu">
<?php foreach($categories as $category): ?>
<li class="mega-menu-drop">
<?php echo $category->name; ?>
<div class="mega-menu-content">
<?php foreach($category->subcategories as $subcategory): ?>
<!-- and so on -->
<?php endforeach; ?>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
The code I've used here assumes you've already got it from a sort-of-flat-array to a tree-like structure. For example, if we've got a Category class:
class Category {
public $id;
public $parentID;
public $name;
public $parent;
public $subcategories;
public function __construct($id, $parentID, $name) {
$this->id = $id;
$this->parentID = $parentID;
$this->name = $name;
$this->parent = null; // will be filled in later
$this->subcategories = array(); // will be filled in later
}
}
And an array of associative arrays like you might get from a database call (which we'll call $flatCategories), we can build a bunch of not-yet-connected Category instances like this:
$categories = array();
foreach($flatCategories as $flatCategory) {
$categories[$flatCategory['id']] =
new Category($flatCategory['category_id'],
$flatCategory['parent'],
$flatCategory['category_name']);
}
Then we can connect them all up into a hierarchal structure:
foreach($categories as $category) {
if($category->parentID !== null) {
$category->parent = $categories[$category->parentID];
$category->parent->subcategories[] = $category;
}
}
Now they're all linked up and we only care about keeping references to the roots:
$roots = array();
// This could, of course, be merged into the last loop,
// but I didn't for clarity.
foreach($categories as $category) {
if($category->parentID === null) {
$roots[] = $category;
}
}
Now $roots contains all the root categories, and it's fairly simple to traverse. In my example at the top, I was assuming $categories had something similar to $roots in it.
you need to generate something called a super tree and use a recursive function to echo out each branch, this will allow for an unlimited depth tree, but, it also allows for an unknown depth tree.
First, SQL:
SELECT category_id, parent, category_name
Second, use SQL to create tree:
$super_tree = array();
while(($row = mysql_fetch_assoc($res)) != NULL) {
$parent = $row['parent'] == NULL ? 0 : $row['parent']; //clean up the parent
$super_tree[$parent][$row['category_id']] = $row['category_name'];
}
Third, echo out the tree
function echo_branch($tree_branch, $tree_root) {
echo("<ul>\n"); //open up our list for this branch
foreach($tree_branch as $id => $name) { //foreach leaf on the branch
echo("<li>"); //create a list item
echo("{$name}"); //echo out our link
if(!empty($tree_root[$id])) { //if our branch has any sub branches, echo those now
echo_branch($tree_root[$id], $tree_root); //pass the new branch, plus the root
}
echo("</li>\n"); //close off this item
}
echo("</ul>\n"); //close off this list
}
echo_branch($super_tree[0], $super_tree); //echo out unlimited depth tree structure
this allows for unlimited depth in your tree structure and allows for simple code to be able to create the structure within the code base.
All that you would need to do now is add in your extra's such as classes and your extra html elements in the correct places.
if you are looking to track the current depth of the tree to be able to echo different things depending on the depth, you can make the following alterations
In the function definition
function echo_branch($tree_branch, $tree_root, $depth) {
In the recursive call within the if
echo_branch($tree_root[$id], $tree_root, $depth++);
In the initial call
echo_branch($super_tree[0], $super_tree, 0);
Look like you are using the variable
$category
when you should use
$subcategory
Try this:
<div id="wrapper">
<ul class="mega-menu">
<?php $json = json_decode($category->buildDepartments(NULL)); ?>
<?php foreach($json as $category): ?>
<li class="mega-menu-drop">
<?php if($category->parent === NULL){echo $category->category_name;} ?>
<div class="mega-menu-content">
<?php foreach($category as $subcategory): ?>
<div class="column">
<?php if($subcategory->category_id === $category->parent){echo '<h4>' . $category->category_name . '</h4>';}?>
<ul>
<?php foreach($subcategory as $subcategory2)
{
echo '<li>' . $subcategory2->category_name . '</li>';
}
?>
</ul>
</div>
<?php endforeach; ?>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>

xPath insert before and after - With DOM and PHP

I need to add a class to a HTML structure.
My class is called "container" and should start right after <div><ul><li></h4> (the child of ul and its simblings, not grandchilds) and should end right before the closing of the same element.
My whole code looks like this:
<?php
$content = '
<div class="sidebar-1">
<ul>
<li>
<h4>Title</h4>
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</li>
<li>
<p>Paragraf</p>
</li>
<li>
<h4>New title</h4>
<ul>
<li>Some text</li>
<li>Some text åäö</li>
</ul>
</li>
</ul>
</div>
';
$doc = new DOMDocument();
$doc->loadHTML($content);
$x = new DOMXPath($doc);
$start_text = '<div class="container">';
$end_text = '</div>';
foreach($x->query('//div/ul/li') as $anchor)
{
$anchor->insertBefore(new DOMText($start_text),$anchor->firstChild);
}
echo $doc->saveXML($doc->getElementsByTagName('ul')->item(0));
?>
It works as far as i can add the class opening but not the closing element. I also get strange encoding doing this. I want the output to be the same encoding as the input.
The result should be
<div class="sidebar-1">
<ul>
<li>
<h4>Title</h4>
<div class="content">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
</li>
<li>
<div class="content">
<p>Paragraf</p>
</div>
</li>
<li>
<h4>New title</h4>
<div class="content">
<ul>
<li>Some text</li>
<li>Some text åäö</li>
</ul>
</div>
</li>
</ul>
</div>
I couldn't find a more elegant way to reassign all children, so I guess this will do. I think it gets what you're after, though.
(NOTE: Code updated to reflect additional requirements in the comments.)
$doc = new DOMDocument();
$doc->loadHTML($content);
$x = new DOMXPath($doc);
foreach($x->query('//div/ul/li') as $anchor)
{
$container = $doc->importNode(new DOMElement('div'));
$container->setAttribute('class', 'container');
$next = $anchor->firstChild;
while ($next !== NULL) {
$curr = $next;
$next = $curr->nextSibling;
if (($curr->nodeName != 'h4')
|| ($curr->attributes === NULL)
|| ($curr->attributes->getNamedItem('class') === NULL)
|| !preg_match('#(^| )title( |$)#', $curr->attributes->getNamedItem('class')->nodeValue)
) {
$container->appendChild($anchor->removeChild($curr));
}
}
$anchor->appendChild($container);
}
As for character encoding, I've been messing with it for a while and it's a tricky issue. The characters display correctly when you load with loadXML() but not with loadHTML(). There's a workaround in the comments, but it ain't pretty. Hopefully some of the user comments will help you can find a usable solution.

Categories