manipulate html navigation with php dom - php

I need to add classes to the navigation HTML being output from a function in a custom CMS.
The only way I can get the output I need is to parse the HTML with PHP.
I am using PHP's DOM methods to look through the HTML and add a class to any <li> element that contains a child <ul> (top level navigation items).
So far it's working, but I have 2 questions:
Is there a more efficient way for me to go through this DOM data? It seems cumbersome to me, but that could just be my lack of experience.
In some cases, my <li> elements may already have a class, how can I add to the existing class attribute without destroying what may or may not already be there?
-
<?
$mcms_nav = getContent(
// call to cms that returns navigation html as a string
// ex. <ul id="pnav"><li>home</li>....</ul>
);
$dom = new DOMDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->loadHTML($mcms_nav);
$x = new DOMXPath($dom);
foreach($x->query('//ul/li/ul') as $node)
{
$parent = $node->parentNode;
$parent_attr = $dom->createAttribute('class');
$parent_attr->value = 'has-flyout';
$parent->appendChild($parent_attr);
$flyout_attr = $dom->createAttribute('class');
$flyout_attr->value = 'flyout';
$node->appendChild($flyout_attr);
}
$mcms_nav = $dom->getElementByID('pnav');
echo $dom->saveHTML($mcms_nav);
?>

Not really. You could take the XML class from the CakePHP framework, turn this into an array, manipulate the array, and turn it back. Not sure if that's an option in your case. http://book.cakephp.org/2.0/en/core-utility-libraries/xml.html
You can use dom->hasAttribute() and dom->getAttribute() to get the existing attribute contents if they exist.
Also, a new job wouldn't hurt ;)

Related

How can you hide html element with php?

if you do in php:
$dom = new DOMDocument();
$dom->loadHTML($pageUrl);
$dom->getElementById(eleId);
How can you hide the element. I know how you can do it with css and js. But I want to know it with php.
I’m going to assume that you’re trying to have the element initially hidden i.e. in existence, but not visible, upon the browser’s initial rendering and you already know that PHP can’t directly instruct the browser to hide elements.
Following your example, you could do:
$eleId = “div_to_hide”;
$dom = new DOMDocument();
$dom->loadHTML($pageUrl);
$target_elem = $dom->getElementById($eleId);
if ($target_elem) {
$target_elem-> setAttribute('style','display: none;');
}
Or you could create a “script” element containing JavaScript code to execute after the resource has totally downloaded to hide it at run-time.
Or you could create a “style” element in your DOMDocument object in which you’d put CSS content referencing the target element (by selector) to apply initial “display: none;” CSS.
Just other ideas, but the snippet above follows your paradigm.

php extract body tag content

I'm trying what should be very easy, but I can't get it to work. Which makes me wonder if I'm using the right workflow.
I have a simple html page which I load in my desktop application as a help file. This page has no menu just the content.
On my website I want to have a more sophisticated help system. So I want to use a php file which will show a menu, breadcrums and a header and footer.
To not duplicate my help content I want to load the original HTML help file and add its body content to my enhanced help page.
I'm using this code to extract the title:
function getURLContent($filename){
$url = realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . $filename;
$doc = new DOMDocument;
$doc->preserveWhiteSpace = FALSE;
#$doc->loadHTMLFile($url);
return $doc;
}
function getSingleElementValue($element){
if (!is_null($element)) {
$node = $element->childNodes->item(0);
return $node->nodeValue;
}
}
$doc = getURLContent("test.html");
$title = getSingleElementValue($doc->getElementsByTagName('title')->item(0));
echo $title;
The title is correctly extracted.
Now I try to extract the body:
function getBodyContent($element){
$mock = new DOMDocument;
foreach ($element->childNodes as $child){
$mock->appendChild($mock->importNode($child, true));
}
return $mock->saveHTML();
}
$body = getBodyContent($doc->getElementsByTagName('body')->item(0));
echo $body;
The getBodyContent() function is one of the several options I tried.
All of them return the whole HTML tag, including the HEAD tag.
My question is: Is this a correct workflow or should I use something else?
Thanks.
Update: My final goal is to have a website with multiple pages that has the help files accessible via a menu. These pages will be generated using something like generate.php?page=test.html. I'm not yet at this part. The goal is also to not duplicate the content of test.html because this file will be used in my desktop application (using a web control). In my desktop application I don't need the menu and such.
Update #2: I had to add <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> to the html-file I want to read and now I do get the body content. Unfortunaly all tags are strips. I'll need to fixed that as well.
The problem is that saveHTML() will return an actual document. You don't want this. Instead, you want just what you put in.
Thankfully, you can do this much more easily.
function getBodyContent(DOMNode $element) {
$doc = $element->ownerDocument;
$wrapper = $doc->createElement('div');
foreach( $element->childNodes as $child) {
$wrapper->appendChild($child);
}
$element->appendChild($wrapper);
$html = $doc->saveHTML($wrapper);
return substr($html, strlen("<div>"), -strlen("</div>"));
}
This wraps the contents into a single element of known tag representation (the body may have attributes that make it unknown), gets the rendered HTML from that element, and strips off the known tag of the wrapper.
I'd also like to suggest an improvement to getSingleElementValue:
function getSingleElementValue(DOMNode $element) {
return trim($element->textContent);
}
Note also the use of type hints to ensure that your functions are indeed getting the kind of thing that is expected - this is useful as it means we no longer need to check "does $element->ownerDocument exist? does $element->ownerDocument->saveHTML() do what we think it does?" and other such questions. It ensures we have a DOMNode, so we know it has those things.

Load html content from ajax response

I am creating an application in opencart in which the controller returns output in json form. If i do $('.my-div').html(json['output']) then its working fine. But how can i filter particular div or table from that response and put only that filtered html in my div. I know about $('.my-div').load('index.php?route=mymodule/subpage' .my-div > * ); But it doesn't work as my controller is created to return output not to generate.
What my controller returns is
$json['output'] = $this->load->view('default/template/mymodule/mytemplate.tpl', $data);
Edit
What i am getting in response is
json['output'] = '<div>......</div><table class="myclass"></table>';
I want to load only myclass in my div. So how can i filter only myclass table from response.? I've tried filter but its giving me error.
Like Raghubendra said, it's better to only keep the html you need in the .tpl file, but if you can't do that for whatever reason, you can try to filter it on the server side using xPath [it's not the fastest way, but it's a clean way to get the result]. Here is how I think you can do it:
$output = $this->load->view('default/template/mymodule/mytemplate.tpl', $data);
$dom = new DOMDocument();
#$dom->loadHTML($output); // # is to suppress all warnings related to the fact that html is not a proper dom document
$xpath = new DOMXPath($dom);
$match = $xpath->query('//table[#class="myclass"]');
/* $match is not an array, it implements 'Traversable', you can't access your <table> node using $match[0],
you need a foreach that will only loop once if you have one <table class="myclass"> element */
foreach($match as $table) {
$json['output'] = $table->ownerDocument->saveHTML($table); // saveHTML allows you to get the output in an HTML string
}
N.B: Please note that you need to change this code in case you have multiple <table class="myclass">, I would recommend using iterator_to_array($match); to transform the node list to an array so you can access it using $match[0]... and avoid the foreach.
If you want only a part of the html in your div then why are you rendering the whole tpl file. Why don't you remove the html code that you don't want to use.
Suppose if your mytemplate.tpl has
<div>
---------
--------
</div>
<table class="myclass"></table>
<div>
---------
--------
</div>
Then why don't you only keep the table code are remove all the other code as you are not using it while rendering the tpl file.

PHP Modify an included file

I have a bunch of .html files that I am including on a page. Conditionally, I need to add classes to some of the components in these files, for example:
<div id='foo' class='bar'></div>
to
<div id='foo' class='bar bar2'></div>
I know I can do this with some inline PHP like this
<div id='foo' class="bar <?php echo " bar2"; ?>"></div>
However, having PHP in any of the files I'm including is not an option.
I also looked into including a file and then modifying afterward, but that doesn't seem possible. Then I was thinking I should read the files line-by-line, and add it in then.
Is there a nicer way I'm not thinking of?
Since having PHP is not an option, you could use PHP's DOM Parser with an XPath selector:
$dom = new DOMDocument();
$dom->loadHTMLFile($htmlFile);
$finder = new DomXPath($dom);
// getting the class name using XPath
$nodes = $finder->query("//*[contains(#class, 'bar')]");
// changing the class name using setAttribute
foreach ($nodes as $node) {
$node->setAttribute('class', 'barbar2');
}
// modified HTML source
$html = $dom->saveHTML();
That should get you started.
You can use the DOMDocument class in PHP to retreive the information from the file and then add attributes and data.
I don't really remember the code for DOMDocument so I haven't included any code here (sorry), but here are some links:
Use this method to get the HTML from your file:
http://php.net/manual/en/domdocument.loadhtmlfile.php
Review the DOMDocument class:
http://php.net/manual/en/class.domdocument.php
You may need to use .php instead of .html.
So do like below:
$variableClass="bar2";
include("htmlfilename.html");
where the htmlfile.html consists of
<div id='foo' class="bar <?php echo $variableClass; ?>"></div>
Depends on what you actually want to achieve - but basically this tends to be better solved by jQuery on the client.
But anyway you might put your HTML fragments in a DOM object, analyze and modify it, and read the HTML back after the modifications, for example:
// including an HTML file writes to the output stream, so buffer this
ob_start();
include('myfile.html');
$html = ob_get_clean();
// make a DOMDocument
$doc = new DOMDocument();
$doc->loadHTML($html);
// make the changes you need to
$xpath = new DOMXPath($doc);
$nodelist $xpath->query('//div[#id="foo"]');
// etc...
// get modified HTML
$html = $doc->saveHTML();
Hope this helps.

Get and set inline styles with PHP

Is there a way to get and set inline styles of DOM elements inside an HTML fragment with PHP? Example:
<div style="background-color:black"></div>
I need to get whether the background-color is black and if it is, change it to white. (This is an example and not the actual goal)
I tried phpQuery, but it lacks the .css() method, while the branch that implements it doesn't seem to work (at least for me).
Basically, what I need is a port of jQuery's .css() method to PHP.
Per Ryan P's good suggestion above, the PHP DOM functions may help you out. Something like this might do what you want with that particular example.
$my_url = 'index.php';
$dom = new DOMDocument;
$dom->loadHTMLfile($my_url);
$divs = $dom->getElementsByTagName('div');
foreach ($divs as $div) {
$div_style = $div->getAttribute('style');
if ($div_style && $div_style=='background-color:black;') {
$div->setAttribute('style','background-color:white;');
}
}
echo $dom->saveHTML();

Categories