Preg replace divs with li but keep class active - php

Trying to get my head around how to create a PHP preg replace for a string that will convert
<div class="active make_link">1</div>
<div class="make_link digit">2</div>
<div class="make_link digit">3</div>
etc
to
<li class="active">1</li>
<li>2</li>
<li>3</li>
etc
Figured out how to replace the elements but not how to keep the class active.
$new_pagination = preg_replace('/<div[^>]*>(.*)<\/div>/U', '<li>$1</li>', $old_pagination);
Any ideas?

Try this..You can do this using str_ireplace too
<?php
$html='<div class="active make_link">1</div>
<div class="make_link digit">2</div>
<div class="make_link digit">3</div>';
echo str_ireplace(array('<div','</div','class="active make_link"','class="make_link digit"'),array('<li','</li','active',''),$html);

Or simple html dom:
require_once('simple_html_dom.php');
$doc = str_get_html($string);
foreach($doc->find('div') as $div){
$div->tag = 'li';
preg_match('/active/', $div->class, $m);
$div->class = #$m[0];
}
echo $doc;

This may seem a bit excessive, but it's a good use-case for XSLT:
$xslt = <<<XML
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="div">
<li>
<xsl:if test="#*[name()='class' and contains(., 'active')]">
<xsl:attribute name="class">active</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="node()" />
</li>
</xsl:template>
</xsl:stylesheet>
XML;
It uses the identity rule and then overrides handling for <div>, adding a class="active" for nodes that have such a class name.
$xsl = new XSLTProcessor;
$doc = new DOMDocument;
$doc->loadXML($xslt);
$xsl->importStyleSheet($doc);
$doc = new DOMDocument;
$html = <<<HTML
<div class="active make_link">1</div>
<div class="make_link digit">2<div>test</div></div>
<div class="make_link digit">3</div>
HTML;
$doc->loadHTML($html);
echo $xsl->transformToDoc($doc)->saveHTML();

Related

PHP/XML - Problem with default namespaces when append fragment

I have the original file and I want to append fragment in security tag.
<!-- Original File -->
<example>
<header>
<facticeA>123</facticeA>
<facticeB>456</facticeB>
</header>
<body>
<facticeC>789</facticeC>
</body>
<security></security>
</example>
<!-- ------------ -->
<!-- Original Fragment -->
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_eb0b47cc-d4b0-44ba-a08c-90047e3a8b03" IssueInstant="2022-07-18T14:08:46.138Z" Version="2.0">
<saml2:Issuer></saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">XXXXXXXX</Signature>
</saml2:Assertion>
<!-- ----------------- -->
I use "createDocumentFragment()" and "appendXml()" PHP functions
And I have this result.
<!-- Final File -->
<example>
<header>
<facticeA>123</facticeA>
<facticeB>456</facticeB>
</header>
<body>
<facticeC>789</facticeC>
</body>
<security>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:default="http://www.w3.org/2000/09/xmldsig#" ID="_eb0b47cc-d4b0-44ba-a08c-90047e3a8b03" IssueInstant="2022-07-18T14:08:46.138Z" Version="2.0">
<saml2:Issuer/>
<default:Signature xmlns="http://www.w3.org/2000/09/xmldsig#">XXXXXXXX</default:Signature>
</saml2:Assertion>
</security>
</example>
<!-- ---------- -->
The inserted fragment is not the same than original fragment.
"Signature" tag become "default:Signature" tag. And the namespace "xmldsig" present in Signature tag is append to Assertion tag with word "default"
If I delete namespace "xmldsig" in the Signature tag I have no problem.
The inserted fragment is the same than original fragment.
This seems to be a bug in the node import. A possible workaround is to define a prefix for this namespace on the document element.
$document = new DOMDocument();
$document->preserveWhiteSpace = false;
$document->loadXML($exampleXML);
$xpath = new DOMXpath($document);
$document->documentElement->setAttributeNS(
'http://www.w3.org/2000/xmlns/', 'xmlns:sig', 'http://www.w3.org/2000/09/xmldsig#'
);
$fragment = $document->createDocumentFragment();
$fragment->appendXML($samlXML);
foreach ($xpath->evaluate('(//security)[1]') as $security) {
$security->appendChild($fragment);
}
$document->formatOutput = true;
echo $document->saveXML();
Output:
<?xml version="1.0"?>
<example xmlns:sig="http://www.w3.org/2000/09/xmldsig#">
<header>
<facticeA>123</facticeA>
<facticeB>456</facticeB>
</header>
<body>
<facticeC>789</facticeC>
</body>
<security>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_eb0b47cc-d4b0-44ba-a08c-90047e3a8b03" IssueInstant="2022-07-18T14:08:46.138Z" Version="2.0">
<saml2:Issuer/>
<sig:Signature xmlns="http://www.w3.org/2000/09/xmldsig#">XXXXXXXX</sig:Signature>
</saml2:Assertion>
</security>
</example>
Interesting enough, if you create the nodes using DOM methods it works correctly:
$document = new DOMDocument();
$document->preserveWhiteSpace = false;
$document->loadXML($exampleXML);
$xpath = new DOMXpath($document);
foreach ($xpath->evaluate('(//security)[1]') as $security) {
$security->appendChild(
$saml = $document->createElementNS('urn:oasis:names:tc:SAML:2.0:assertion', 'saml2:Assertion')
);
$saml->setAttribute('ID', '_eb0b47cc-d4b0-44ba-a08c-90047e3a8b03');
$saml->appendChild(
$document->createElementNS('urn:oasis:names:tc:SAML:2.0:assertion', 'saml2:Issuer')
);
$saml->appendChild(
$signature = $document->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'Signature')
);
$signature->textContent = 'XXXXXXXX';
}
$document->formatOutput = true;
echo $document->saveXML();
Output:
<?xml version="1.0"?>
<example>
<header>
<facticeA>123</facticeA>
<facticeB>456</facticeB>
</header>
<body>
<facticeC>789</facticeC>
</body>
<security>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_eb0b47cc-d4b0-44ba-a08c-90047e3a8b03">
<saml2:Issuer/>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">XXXXXXXX</Signature>
</saml2:Assertion>
</security>
</example>
You could create a recursive function the recreates the nodes from the fragment.

Replace span's in PHP but keep content inside

I have the following string:
<span style="font-size: 13px;">
<span style="">
<span style="">
<span style="font-family: Roboto, sans-serif;">
<span style="">
Some text content
</span>
</span>
</span>
</span>
</span>
and I want to change this string to the following using PHP:
<span style="font-size: 13px;">
<span style="font-family: Roboto, sans-serif;">
Some text content
</span>
</span>
I dont have any idea, how to do that, because when I try to use str_replace to replace the <span style=""> I dont know, how to replace the </span> and keep the content inside. My next problem is, that I dont know exactly, how much <span style=""> I have in my string. I also have not only 1 of this blocks in my string.
Thanks in advance for your help, and maybe sorry for my stupid question - I'm still learning.
This is easily done with a proper HTML parser. PHP has DOMDocument which can parse X/HTML into the Document Object Model which can then be manipulated how you want.
The trick to solving this problem is being able to recursively traverse the DOM tree, seeking out each node, and replacing the ones you don't want. To this I've written a short helper method by extending DOMDocument here...
$html = <<<'HTML'
<span style="font-size: 13px;">
<span style="">
<span style="">
<span style="font-family: Roboto, sans-serif;">
<span style="">
Some text content
</span>
</span>
</span>
</span>
</span>
HTML;
class MyDOMDocument extends DOMDocument {
public function walk(DOMNode $node, $skipParent = false) {
if (!$skipParent) {
yield $node;
}
if ($node->hasChildNodes()) {
foreach ($node->childNodes as $n) {
yield from $this->walk($n);
}
}
}
}
libxml_use_internal_errors(true);
$dom = new MyDOMDocument;
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$keep = $remove = [];
foreach ($dom->walk($dom->childNodes->item(0)) as $node) {
if ($node->nodeName !== "span") { // we only care about span nodes
continue;
}
// we'll get rid of all span nodes that don't have the style attribute
if (!$node->hasAttribute("style") || !strlen($node->getAttribute("style"))) {
$remove[] = $node;
foreach($node->childNodes as $child) {
$keep[] = [$child, $node];
}
}
}
// you have to modify them one by one in reverse order to keep the inner nodes
foreach($keep as [$a, $b]) {
$b->parentNode->insertBefore($a, $b);
}
foreach($remove as $a) {
if ($a->parentNode) {
$a->parentNode->removeChild($a);
}
}
// Now we should have a rebuilt DOM tree with what we expect:
echo $dom->saveHTML();
Output:
<span style="font-size: 13px;">
<span style="font-family: Roboto, sans-serif;">
Some text content
</span>
</span>
For a more general way to modify HTML document, take a look at XSLT (Extensible Stylesheet Language Transformations). PHP has a XSLT library.
You then have an XML document with your transform rules in place:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes"/>
<!-- remove spans with empty styles -->
<xsl:template match="*[#style and string-length(./#style) = 0]">
<xsl:apply-templates />
</xsl:template>
<!-- catch all to copy any elements that aren't matched in other templates -->
<xsl:template match="*">
<xsl:copy select=".">
<!-- copy the attributes of the element -->
<xsl:copy-of select="#*" />
<!-- continue applying templates to this element's children -->
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Then your PHP:
$sourceHtml = new DOMDocument();
$sourceHtml->load('source.html');
$xsl = new DOMDocument();
$xsl->load('transform.xsl');
$xsltProcessor = new XSLTProcessor;
$xsltProcessor->importStyleSheet($xsl); // attach the xsl rules
echo $xsltProcessor->transformToXML($sourceHtml);
$transformedHtml = $xsltProcessor->transformToDoc($sourceHtml);
$transformedHtml->saveHTMLFile('transformed.html');
XSLT is superpowerful for this kind of thing, and you can set all sorts of rules for parent/sibling relationships, and modify attributes and content accordingly.

array fo data to load to a xsl file

I have two xsl pages. One is taking data from a service and show only some data. I have put an anchor to each data, when clicked it, it redirect to other xsl page. I am passing an array to the other xsl. I have no idea how to show those data in that xsl. I have tried it with a url as below which was a success,
<?php
error_reporting(E_ALL);
$type = #$_REQUEST['angelID'];
$id = #$_REQUEST['angelType'];
$url = 'http://localhost/level5/level4.php?id='.$id.'&type='.$type;
$doc = new DOMDocument();
$doc->load($url, LIBXML_NOBLANKS);
$xslt = new XSLTProcessor();
$xsl = new DOMDocument();
$xsl->load( 'detail.xsl', LIBXML_NOCDATA);
$xslt->importStylesheet( $xsl );
print $xslt->transformToXML($doc);
?>
But I want to replace the url with the data I send from the previous page.I don't want to call the service again as above.Below is how I send data ,
<xsl:for-each select="//Angel">
<div>
<xsl:value-of select="#type" />
<xsl:variable name="type">
<xsl:value-of select="." />
</xsl:variable>
<a width="160" height="200" href="showInDetailXslt.php?angelType={$type}">
<img style="display: block; margin-left: auto;margin-right: auto">
<xsl:attribute name="src">
<xsl:value-of select="concat('http://localhost/level5/images/',service/images)"/>
</xsl:attribute>
</img>
</a>
I don't know how to get it php and show in the other xsl page

XPath to query multiple selectors

I want to get values and attributes from a selector
and then get attributes and values of its children based on a query.
allow me to give an example.
this is the structure
<div class='message'>
<div>
<a href='http://www.whatever.com'>Text</a>
</div>
<div>
<img src='image_link.jpg' />
</div>
</div>
<div class='message'>
<div>
<a href='http://www.whatever2.com'>Text2</a>
</div>
<div>
<img src='image_link2.jpg' />
</div>
</div>
So I would like to make a query to match all of those once.
Something like this:
//$dom is the DomDocument() set up after loaded HTML with $dom->loadHTML($html);
$dom_xpath = new DOMXpath($dom);
$elements = $dom_xpath->query('//div[#class="message"], //div[#class="message"] //a, //div[#class="message"] //img');
foreach($elements as $ele){
echo $ele[0]->getAttribute('class'); //it should return 'message'
echo $ele[1]->getAttribute('href'); //it should return 'http://www.whatever.com' in the 1st loop, and 'http://www.whatever2.com' in the second loop
echo $ele[2]->getAttribute('src'); //it should return image_link.jpg in the 1st loop and 'image_link2.jpg' in the second loop
}
Is there some way of doing that using multiple xpath selectors like I did in the example? to avoid making queries all the time and save some CPU.
Use the union operator (|) in a single expression like this:
//div[#class="message"]|//div[#class="message"]//a|//div[#class="message"]//img
Note that this will return a flattened result set (so to speak). In other words, you won't access the elements in groups of three like your example shows. Instead, you'll just iterate everything the expressions matched (in document order). For this reason, it might be even smarter to simply iterate the nodes returned by //div[#class="message"] and use DOM methods to access their children (for the other elements).
Use:
(//div[#class='message'])[$k]//#*
This selects all three attributes that belong to the $k-th div (and any of its descendants) in the document whose class attribute has string value "message"
You can evaluate N such XPath expressions -- for $k from 1 to N, where N is the total count of //div[#class='message']
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="//div[#class='message']">
<xsl:variable name="vPos" select="position()"/>
<xsl:apply-templates select=
"(//div[#class='message'])[0+$vPos]//#*"/>
================
</xsl:for-each>
</xsl:template>
<xsl:template match="#*">
<xsl:value-of select=
"concat('name = ', name(), ' value = ', ., '
')"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (wrapped in a single top element to become well-formed):
<html>
<div class='message'>
<div>
<a href='http://www.whatever.com'>Text</a>
</div>
<div>
<img src='image_link.jpg' />
</div>
</div>
<div class='message'>
<div>
<a href='http://www.whatever2.com'>Text2</a>
</div>
<div>
<img src='image_link2.jpg' />
</div>
</div>
</html>
The XPath expression is evaluated twice and the selected attributes are formatted and output:
name = class value = message
name = href value = http://www.whatever.com
name = src value = image_link.jpg
================
name = class value = message
name = href value = http://www.whatever2.com
name = src value = image_link2.jpg
================

Why is my recursive loop creating too many children?

I'm using a PHP recursive loop to parse through an XML document to create a nested list, however for some reason the loop is broken and creating duplicates of elements within the list, as well as blank elements.
The XML (a list of family tree data) is structured as follows:
<?xml version="1.0" encoding="UTF-8"?>
<family>
<indi>
<id>id1</id>
<fn>Thomas</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
<indi>
<id>id1</id>
<fn>Alexander</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
<indi>
<id>id1</id>
<fn>John</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
<indi>
<id>id1</id>
<fn>George</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
</family>
</indi>
</family>
</indi>
</family>
And here's my PHP loop, which loads the XML file then loops through it to create a nested ul:
<?php
function outputIndi($indi) {
echo '<li>';
$id = $indi->getElementsByTagName('id')->item(0)->nodeValue;
echo '<span class="vcard person" id="' . $id . '">';
$fn = $indi->getElementsByTagName('fn')->item(0)->nodeValue;
$bday = $indi->getElementsByTagName('bday')->item(0)->nodeValue;
echo '<span class="edit fn">' . $fn . '</span>';
echo '<span class="edit bday">' . $bday . '</span>';
// ...
echo '</span>';
echo '<ul>';
$family = $indi->getElementsByTagName('family');
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
echo '</ul></li>';
}
$doc = new DOMDocument();
$doc->load('armstrong.xml');
outputIndi($doc);
?>
EDIT here's the desired outcome (nested lists, with ul's signifying families and li's signifying individuals)
<ul>
<li>
<span class="vcard">
<span class="fn">Thomas</span>
<span class="bday"></span>
<span class="dday"></span>
<ul>
... repeat for all ancestors ...
</ul>
<li>
<ul>
You can see the output at http://chris-armstrong.com/gortin . Any ideas where I'm going wrong? I think it's something to do with the $subIndi value, but anytime I try and change it I get an error. Would really appreciate any help!
Sounds perfect! Could you give me an
example? Does this mean I can save the
data as XML, then load it in as nested
ul's?
Yes, you can do exactly that. Here's an XSL which renders nested UL's:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Family tree</h2>
<ul>
<li><xsl:value-of select="indi/fn" /></li>
<!-- apply-templates will select all the indi/family nodes -->
<xsl:apply-templates select="indi/family" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="family">
<ul>
<li>
<div>
<xsl:value-of select="id" />: <xsl:value-of select="fn" />
(<xsl:variable name="bday" select="bday" />
to
<xsl:variable name="dday" select="dday" />)
</div>
</li>
<!-- This node matches the 'family' nodes, and we're going to apply-templates on the inner 'family' node,
so this is the same thing as recursion. -->
<xsl:apply-templates select="family" />
</ul>
</xsl:template>
</xsl:stylesheet>
I don't know php, but this article will show you how to transform XML using the style sheet above.
You can also link your style sheet by adding a stylesheet directive at the top of your XML file (see for an example).
getElementsByTagName will give you all nodes, not just immediate children:
$family = $indi->getElementsByTagName('family');
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
You will call outputIndi() for grand children, etc repeatedly.
Here is an example (from another stackoverflow question):
for ($n = $indi->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof DOMElement && $n->tagName == "family") {
outputIndi($n);
}
}
Replace this
$family = $indi->getElementsByTagName('family');
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
by this
if(!empty($indi))
foreach($indi as $subIndi){
outputIndi($subIndi);
}
I realize
if($indi->hasChildNodes())
is better than
if(!empty($indi))

Categories