PHP or Regex for unique matching ID's - php

Here's some stuff stored in a database table:
<section id="earth">
<h2>Earth</h2>
<div class="MainDiv">
(article)
</div>
</section>
<section id="mars">
<h2>Mars</h2>
<div class="MainDiv">
(article)
</div>
</section>
All I want to do is modify the display so each section has a unique data-target and each div has a matching ID. (I gave the div's a class - MainDiv - to distinguish them from any nested divs they might contain.)
<section id="earth" data-target="a">
<h2>Earth</h2>
<div class="MainDiv" id="a">
<section id="mars" data-target="b">
<h2>Mars</h2>
<div class="MainDiv" id="b">
Ideally, I'd like to keep the data-targets and matching ID's simple - single, lower case letters would be perfect. Is there a PHP function that can do that? If not, do you know a regex script that would work?

No, as much as possible, don't work on regex for this task. This is what HTML parsers are for. There is already a class for PHP that does this, its DOMDocument.
First, just load up the class (this is built-in already), then you load the HTML markup, get all the elements you need (in this case, the section tags). After you got all the section tags, iterate on those found and add those data attributes you want for each of those (including the div tags), then you put it back together as string again. Here's some to get you started. Rough Example:
$i = 1; // initialize counter
// initialize DOMDocument
$dom = new DOMDocument;
#$dom->loadHTML($html); // load the markup
$sections = $dom->getElementsByTagName('section'); // get all section tags
if($sections->length > 0) { // if there are indeed section tags inside
// work on each section
foreach($sections as $section) { // for each section tag
$section->setAttribute('data-target', 'section' . $i); // set id for section tag
// get div inside each section
foreach($section->getElementsByTagName('div') as $div) {
if($div->getAttribute('class') == 'MainDiv') { // if this div has class maindiv
$div->setAttribute('id', 'div' . $i); // set id for div tag
}
}
$i++; // increment counter
}
}
// back to string again, get all contents inside body
$html = '';
foreach($dom->getElementsByTagName('body')->item(0)->childNodes as $child) {
$html .= $dom->saveHTML($child); // convert to string and append to the container
}
// output
echo $html;
Sample Output

I would do as #Barmar suggested. Doing a0,a1,a2, ..., This way we can have as many as we want and echo each <section> in PHP via loop. If we went from a,b,c,...,z what would be next? aa? And you will have to program that in. For each element I would use a simple for loop and use the index for each element. For example this:
class Planet
{
public $title;
public $name;
public $article;
function __construct($title, $name, $article) {
$this->name = $name;
$this->title = $title;
$this->article = $article;
}
}
$planets = array(
new Planet("earth", "Earth", "Hello World!"),
new Planet("mars", "Mars", "Hello World Again!")
);
for( $i = 0; $i < count($planets); $i++ ) {
$id = strtolower($planets[$i]->name);
$name = $planets[$i]->name;
$article = $planets[$i]->article;
echo <<<HERE
<section id="$id" data-target="a$i">
<h2>$name</h2>
<div class="MainDiv" id="a$i">
$article
</div>
</section>
HERE;
}
Would give the output:
<section id="earth" data-target="a0">
<h2>Earth</h2>
<div class="MainDiv" id="a0">
Hello World!
</div>
</section>
<section id="mars" data-target="a1">
<h2>Mars</h2>
<div class="MainDiv" id="a1">
Hello World Again!
</div>
</section>

Related

Iterating over elements from DOMDocument::getElementsByTagName() doesn't work

I have this tiny class that will help me to replace custom tags with valid HTML tags. My issue with it is, it replaces only the first custom tag for whatever reason. My guess is that I'm breaking the reference somewhere, but I can't figure out where... Scroll down to the bottom of this post to see the actual result and the expected output.
<?php
class DomParser {
protected $tags = [];
protected $document;
public function __construct($html) {
$this->document = new DOMDocument();
$this->document->loadXML($html);
}
public function addTag(string $name, callable $callable) {
$this->tags[$name] = $callable;
}
public function replace() {
foreach ($this->tags as $name => $callable) {
$elements = $this->document->getElementsByTagName($name);
foreach ($elements as $element) {
$callable($element, $this->document);
}
}
return $this->document->saveHTML();
}
}
Example code to run the class:
<?php
require_once 'DomParser.php';
//require_once 'RenameTag.php';
//require_once 'Container.php';
$html = '<html>
<container>
<col>
<p>
<test attribute="test" attribute2="this">test<br />test2</test>
</p>
</col>
<col>
test col
</col>
</container>
<container fluid="test"><test>dsdshsh</test></container>
</html>';
$parser = new DomParser($html);
//$parser->addTag('test', RenameTag::create('othertag'));
//$parser->addTag('container', Container::create());
$parser->addTag('col', function($oldTag) {
$document = $oldTag->ownerDocument;
$newTag = $document->createElement('div');
$oldTag->parentNode->replaceChild($newTag, $oldTag);
foreach (iterator_to_array($oldTag->childNodes) as $child) {
$newTag->appendChild($oldTag->removeChild($child));
}
$newTag->setAttribute('class', 'col');
});
echo $parser->replace();
I'm getting this result:
<html>
<container>
<div class="col">
<p>
<test attribute="test" attribute2="this">test<br>test2</test>
</p>
</div>
<col>
</container>
<container fluid="true"><test>dsdshsh</test></container>
</html>
The expected output should be:
<html>
<container>
<div class="col">
<p>
<test attribute="test" attribute2="this">test<br>test2</test>
</p>
</div>
<div class="col">
test col
</div>
</container>
<container fluid="test"><test>dsdshsh</test></container>
</html>
The problem seems to be that you are changing the document structure while trying to iterate over it.
The alternative is to use XPath, which will take it's own copy of the nodes for you to loop over, the changes are fairly small, but will give the output your after...
public function replace() {
$xp = new DOMXPath($this->document);
foreach ($this->tags as $name => $callable) {
$elements = $xp->query("//".$name);
foreach ($elements as $element) {
$callable($element, $this->document);
}
}
return $this->document->saveHTML();
}
DOMNode::getElementsByTagName() returns a "Live" result. The items and the list change as the document changes. You modify the document so the items in the list change as well. Here are tree ways to avoid the problem.
You can iterate the list in reverse (using a for loop). Most of the time this will mean that you only change parts of the document that do not affect the previous elements in your node list.
Use methods that return a stable result. DOMXpath::evaluate() (and DOMXpath::query()) return an stable list. Xpath expressions reduce the amount of code needed to fetch nodes as well.
Convert the node list into an array using iterator_to_array(). This will create an array copy of the node lists with the node objects in it. You used that method in your example code actually.

Distribute JSON data into different HTML elements with PHP

I am parsing a JSON Object and using a foreach loop to output the data.
function do_api_call() {
$place_id = get_theme_mod('place_id_setting_field');
$url = "https://maps.googleapis.com/maps/api/place/details/json?placeid=" . $place_id . "&key=myapikey";
$data = file_get_contents($url);
$rev = json_decode($data, true);
$reviews = $rev["result"]["reviews"];
foreach($reviews as $review) {
$review_snippet = $review["text"];
echo $review_snippet . '<br>';
}
}
This works fine when I call it within an HTML element with:
<?php echo do_api_call() ?>
The short of it is that I get back 5 reviews from this loop and I need each review to go to their own html element in a different file called reviews.php, this file contains 5 unique bootstrap cards with a div that needs to hold a unique review so I need to output a unique review into each of these cards.
Like so:
<div> review 1 text </div>
<div> review 2 text </div>
<div> review 3 text </div>
<div> review 4 text </div>
<div> review 5 text </div>
You access a direct review with $rev["result"]["reviews"][0] (for the first) $rev["result"]["reviews"][1] (for the second) etc. So you can pass which review as a function arg.
However to cut down on re-loading an external source with every call of the function, you may want to do the data loader outside the function:
$place_id = get_theme_mod('place_id_setting_field');
$url = 'https://maps.googleapis.com/maps/api/place/details/json?placeid='.
$place_id .'&key=myapikey';
$data = file_get_contents($url);
$rev = json_decode($data,true);
$reviews = $rev['result']['reviews'];// this is now setup and ready to use
And then setup the anonymous function using the global (php 5.3+):
$get_review = function ($r) use (&$reviews) {
if (isset($reviews[$r])) {
return '<div>'. $reviews[$r]['text'] .'<div>';
}
return '';// no review to return
};
Then down in your html where you want to begin outputting them, you call it as such (note the $ is intentional with anonymous functions assigned to variables):
<body>
blah blah other stuff
<?php echo $get_review(0);?>
more blah
<?php echo $get_review(1);?>
</body>
Or if you need to loop on how many reviews you have:
<body>
<?php for($r=0;$r < count($reviews);$r++) { echo $get_review($r); } ?>
</body>
If you are afraid of using anonymous functions as I have above, you can adjust it to this instead:
function get_review ($r,&$reviews) {
if (isset($reviews[$r])) {
return '<div>'. $reviews[$r]['text'] .'<div>';
}
return '';// no review to return
}
// call it as thus
echo get_review(0,$reviews);
echo get_review(1,$reviews);
// etc
Class Method:
Of course you COULD also turn this into a small class object, where you first load_api, then get_review as methods of the class:
class Reviews {
public static $reviews;
public static function load_api() {
$place_id = get_theme_mod('place_id_setting_field');
$url = 'https://maps.googleapis.com/maps/api/place/details/json?placeid='.
$place_id .'&key=myapikey';
$data = file_get_contents($url);
$rev = json_decode($data,true);
self::$reviews = $rev['result']['reviews'];// this is now setup and ready to use
}
public static function get_review($r) {
if (isset(self::$reviews[$r])) {
return '<div>'. self::$reviews[$r]['text'] .'<div>';
}
return '';// no review to return
}
}
// to initialize
Reviews::load_api();
// to call and output
echo Reviews::get_review(0);

Wrap all HTML tags between h3 tag sets with DOMDocument in PHP

I've got a follow up question to my question that has been answered by Jack:
Wrap segments of HTML with divs (and generate table of contents from HTML-tags) with PHP
I've been trying to add some functionality to the answer above, in order to get the following result.
This is my present HTML:
<h3>Subtitle</h3>
<p>This is a paragraph</p>
<p>This is another paragraph</p>
<h3>Another subtile
<h3>
<p>Yet another paragraph</p>
This is what I would like to achieve:
<h3 class="current">Subtitle</h3>
<div class="ac_pane" style="display:block;">
<p>This is a paragraph</p>
<p>This is another paragraph</p>
</div>
<h3>Another subtitle</h3>
<div class="ac_pane">
<p>Yet another paragraph</p>
</div>
I've been trying to modify the code out of the example above, but can't figure it out:
foreach ($d->getElementsByTagName('h3') as $h3) {
$ac_pane_nodes = array($h3);
for ($next = $h3->nextSibling; $next && $next->nodeName != 'h3'; $next = $next->nextSibling) {
$ac_pane_nodes[] = $next;
}
$ac_pane = $d->createElement('div');
$ac_pane->setAttribute('class', 'ac_pane');
// Here I'm trying to wrap all tags between h3-sets, but am failing!
$h3->parentNode->appendChild($ac_pane, $h3);
foreach ($ac_pane_nodes as $node) {
$ac_pane->appendChild($node);
}
}
Please note that the addition of class="current" to the first h3 set, and the addition of style="display:block;" to the first div.ac_pane is optional, but would be very much appreciated.
As requested, here is a working version. IMO XSLT is still the solution most appropriate to this type of problem (transforming some XML into other XML, really) but I have to admit grouping with regular code is much easier!
I ended up extending the DOM API slightly just to add a utility insertAfter method on DOMElement. It could have been done without it, but it's neater:
UPDATED TO WRAP DIV AROUND ALL TAGS AS REQUESTED IN COMMENTS
<?php
class DOMDocumentExtended extends DOMDocument {
public function __construct($version = "1.0", $encoding = "UTF-8") {
parent::__construct($version, $encoding);
$this->registerNodeClass("DOMElement", "DOMElementExtended");
}
}
class DOMElementExtended extends DOMElement {
public function insertAfter($targetNode) {
if ($targetNode->nextSibling) {
$targetNode->parentNode->insertBefore($this, $targetNode->nextSibling);
} else {
$targetNode->parentNode->appendChild($this);
}
}
public function wrapAround(DOMNodeList $nodeList) {
while (($node = $nodeList->item(0)) !== NULL) {
$this->appendChild($node);
}
}
}
$doc = new DOMDocumentExtended();
$doc->loadHTML(
"<h3>Subtitle</h3>
<p>This is a paragraph</p>
<p>This is another paragraph</p>
<h3>Another subtile</h3>
<p>Yet another paragraph</p>"
);
// Grab a nodelist of all h3 tags
$nodeList = $doc->getElementsByTagName("h3");
// Iterate over each of these h3 nodes
foreach ($nodeList as $index => $h3) {
// Special handling for first h3
if ($index === 0) {
$h3->setAttribute("class", "current");
}
// Create a div node that we'll use as our wrapper
$div = $doc->createElement("div");
$div->setAttribute("class", "ac_pane");
// Special handling for first div wrapper
if ($index === 0) {
$div->setAttribute("style", "display:block;");
}
// Move next siblings of h3 until we hit another h3
while ($h3->nextSibling && $h3->nextSibling->localName !== "h3") {
$div->appendChild($h3->nextSibling);
}
// Add the div node right after the h3
$div->insertAfter($h3);
}
// UPDATE: wrap all child nodes of body in a div
$div = $doc->createElement("div");
$body = $doc->getElementsByTagName("body")->item(0);
$div->wrapAround($body->childNodes);
$body->appendChild($div);
echo $doc->saveHTML();
Note that loadHTML will add doctype, html and body nodes. They can be stripped out if needed.

Zend fetchAll foreach 2 times run dont loop 2nd time;

This is very simple to understand
Image Class
<?php
class Image extends Zend_Db_Table_Abstract {
protected $_name = 'images';
public function getList() {
return $this->fetchAll();
}
}?>
My PHP Code
<?php
require 'config.php';
$imgTable = new Image(); // Create Object
$imgList = $imgTable->getList(); // fetch Data
$template = new Template('portfolio'); // Initialize Template and tell which template to pick
$template->imgList = $imgList; // set template variable
$template->render(); // Generate Template output
?>
I can access template variable inside template using $this
Below code is from inside the template
$xback = 0;
foreach ($this->imgList as $images) {
echo 'imageArray[' . $xback . '] = "' . $images['sef'] . '";';
$xback++;
}
?>
.......
<?php
foreach ($this->imgList as $images) {
?>
<div class="portfolio_item">
<img src="<?php echo PATH_WEB . $images['image_thumb'] ?>" height="146" width="209" />
<div class="title"><?php echo $images['image_title'] ?></div>
<div class="right link">
View Details
</div>
</div>
<?php
}
?>
Above code is working fine, but below some few lines, I have to iterate over the same data again dont output any thing. If I comment the first one, 2nd starts working.
First one is to create the JS array and is in head section,
Second part is in HTML to display images
I hope its a pointer issue, I may have to set the loop current item to start, but I am not understanding it right now .... reset($this->imgList) didnt worked
please help
I think it has something to do with the fetchAll call, try this:
<?php
class Image extends Zend_Db_Table_Abstract {
protected $_name = 'images';
protected $images;
public function getList() {
// Employ lazy loading pattern to load images if they aren't set yet
if (!isset($this->$images)) {
$this->images = $this->fetchAll();
}
return $this->images;
}
}?>

mix html template with php code : php html template

I create a dynamic website using php/mysql. my template for print data using pure php code + html . now , better, optimized, faster way for Structure of mix php + html ? ( without template engine )
for each page i have : ex.1 ( php code before wrapper )
<?PHP include ( DEFINE_SITE . '/templates/header.php' );
// isset : post : get : SELECT : INSERT ... Or ANY CODE OF PHP
?>
<div id="wrapper">
<div id="leftsidebar"><?PHP // Left DATA ?></div>
<div id="centercontent">
<?PHP // while {} Print result of php ANY LOOPS ..... ?>
</div>
<div id="rightsidebar"><?PHP // Right DATA ?></div>
</div>
<?PHP include ( DEFINE_SITE . '/templates/footer.php' ); ?>
for each page i have : ex.2 ( after side bar and before center content )
<?PHP include ( DEFINE_SITE . '/templates/header.php' );
<div id="wrapper">
<div id="leftsidebar"><?PHP // Left DATA ?></div>
<?PHP // isset : post : get : SELECT : INSERT ... Or ANY CODE OF PHP ?>
<div id="centercontent">
<?PHP // while {} Print result of php ANY LOOPS ..... ?>
</div>
<div id="rightsidebar"><?PHP // Right DATA ?></div>
</div>
<?PHP include ( DEFINE_SITE . '/templates/footer.php' );?>
Which is better? e.x 1 or e.x 2 ? your opinion ?
why the aversion to a template engine? In reality, PHP is a template engine, but perhaps if you checked a few out (like Twig) it might help you in designing something more flexible, fast and responsive without the template engine.... or you might start to love it, like me ;-)
I suggest that you keep your HTML and PHP separated. If you don't want to use a template system why don't you do it like this?
Make a HTML template:
<div id="wrapper">
<div id="leftsidebar">{LEFT}</div>
<div id="centercontent">{CONTENT}</div>
<div id="rightsidebar">{RIGHT}</div>
</div>
In a different php file:
$content = file_get_contents('template.html');
$templateVars = array();
startVar('CONTENT');
echo '<p>This is the content.</p>'; // New template vars are also allowed
endVar('CONTENT');
echo replaceTemplateVars($content);
/**
* Replace template variables recursively with the key-value pairs that are in the $templateVars queue
* #param string $content (default NULL) is code to convert.
* #return the string that is replaced
*/
function replaceTemplateVars($content = NULL)
{
global $templateVars;
$matches = array();
preg_match_all("/{[A-Z0-9_:]*}/", $content, $matches); // $matches = All vars found in your template
foreach($matches[0] as $key)
{
if(isset($templateVars[substr($key, 1, -1)]))
{
$content = str_replace($key, $templateVars[substr($key, 1, -1)], $content); // Substitute template var with contents
}
else
{
$content = str_replace($key, "", $content); // Remove empty template var from template
}
}
// Check if the replacement has entered any new template variables, if so go recursive and replace these too
if(preg_match_all("/{[A-Z0-9_:]*}/", $content, $matches))
{
$content = replaceTemplateVars($content);
}
return $content;
}
/**
* Start output buffer for a template key
* #param string $key is not used. Only for overview purposes
*/
function startVar($key)
{
ob_start();
}
/**
* End output buffer for a template key and store the contents of the output buffer in the template key
* #param string $key
*/
function endVar($key)
{
setVar($key, ob_get_contents());
ob_end_clean();
}
/**
* #param string $key to store value in
* #param mixed $value to be stored
*/
function setVar($key, $value)
{
global $templateVars;
$templateVars[$key] = $value;
}
And this is what you get on your screen:
<div id="wrapper">
<div id="leftsidebar"></div>
<div id="centercontent"><p>This is the content.</p></div>
<div id="rightsidebar"></div>
</div>
So preprocessing is done first and at the end all html code is outputted.
Of course you could add functions like startPrependVar() and startAppendVar() and wrap everything into a Template class to get rid of the global scope.

Categories