PHP function to compare equality of XML elements - php

I have an XML file which is supposed to be my phones contacts backup and I am trying to create a php file to retrieve only the contacts that have a phone number assigned to them. The file contains contacts from different applications.
The XML has these elements:
<Contact>
<Id>5238</Id>
<GivenName>friend1</GivenName>
<FullName>friendA</FullName>
<CreateTime>0001-01-01T00:00:00+00:00</CreateTime>
<ModifyTime>0001-01-01T00:00:00+00:00</ModifyTime>
<Starred>false</Starred>
<AccountName>SIM</AccountName>
<AccountType>com.anddroid.contacts.sim</AccountType>
</Contact>
<PhoneNumbers>
<Id>53</Id>
<ContactId>1380</ContactId>
<Name>2</Name>
<Value>07123456789</Value>
<Primary>2</Primary>
</PhoneNumbers>
<Contact>
<Id>328</Id>
<FamilyName>tee</FamilyName>
<GivenName>friend2</GivenName>
<FullName>friend2 tee</FullName>
<CreateTime>0001-01-01T00:00:00+00:00</CreateTime>
<ModifyTime>0001-01-01T00:00:00+00:00</ModifyTime>
<Picture>18948</Picture>
<Starred>false</Starred>
<AccountName>xxxxxxx#hotmail.com</AccountName>
<AccountType>com.htc.socialnetwork.facebook</AccountType>
</Contact>
And I want to make a php file that will retrieve the FullName from Contact and the Value from PhoneNumbers where the Contact/Id matches the PhoneNumbers/ContactId.
I created this code:
<?php
$xml = simplexml_load_file("Contact.xml");
$i=0;
$k=0;
foreach ($xml->Contact as $contact) {
if ($contact->AccountName == "SIM"){
echo "Contact: " . $k . "<br /> "; echo $contact->nodeValue[$k] . "<br /> " . $contact->FullName . "<br /> ";
$k++;
}
}
foreach ($xml->PhoneNumbers as $number) {
echo "Contact: " . $i . "<br /> "; echo $number->Value . "<br /> ";
$i++;
}
?>
It outputs 53 contacts and 173 numbers. If I dont put the if ($contact->AccountName == "SIM") it outputs the same numbers but 700++ contacts. I just want some help producing a function or something to output only the contacts that I already have their phone number.
Any help is appreciated.
Thank you

I would suggest to use a XSL-stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/">
<ul><xsl:apply-templates/></ul>
</xsl:template>
<xsl:template match="Contact">
<!-- select phonenumbers with the matching ContactId -->
<xsl:variable name="numbers" select="//PhoneNumbers[ContactId=current()/Id]"/>
<!-- when any matching PhoneNumber has been found, continue -->
<xsl:if test="count($numbers) > 0">
<li>
<xsl:value-of select="FullName"/>
<ul>
<!-- call a named template with the matching PhoneNumbers as param -->
<xsl:call-template name="printNumbers">
<xsl:with-param name="numbers" select="$numbers" />
</xsl:call-template>
</ul>
</li>
</xsl:if>
</xsl:template>
<xsl:template name="printNumbers">
<xsl:param name="numbers" />
<!-- loop through PhoneNumbers and print the Value -->
<xsl:for-each select="$numbers">
<li><xsl:value-of select="Value" /></li>
</xsl:for-each>
</xsl:template>
<xsl:template match="PhoneNumbers"/>
</xsl:stylesheet>
How to use the stylesheet:
<?php
$doc = new DOMDocument();
$xsl = new XSLTProcessor();
$doc->load('path/to/stylesheet.xsl');
$xsl->importStyleSheet($doc);
$doc->load('Contact.xml');
echo $xsl->transformToXML($doc);
?>

Related

Preg replace divs with li but keep class active

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();

How to set xsl attribute using processing instruction php

I'm having trouble trying to set a value into the attribute using a PHP processing instruction:
XSLT
<li itemprop="startDate">
<xsl:attribute name="content">
<xsl:processing-instruction name="php">
echo "Monday";
?</xsl:processing-instruction>
</xsl:attribute>
Monday
</li>
The page renders fine but the attribute is always empty.
Output
<li itemprop="startDate" content="">Monday</li>
I'm expecting the PHP to echo out a value into the attribute
If your using PHP to transform the XML through XSLT you can use in php:
$proc->setParameter(null, 'day', 'Monday');
$proc->transformToXML($xml);
Then in your XSLT to use this variable:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
exclude-result-prefixes="php"
xsl:extension-element-prefixes="php">
<xsl:param name="day"/> <!-- Set the parameter -->
<xsl:attribute name='content'>
<xsl:value-of select="$day"/>
</xsl:attribute>
All the best!
You did not say how you open the XML. But because of the echo I assume it could/should have php instruction include.
The xsl:processing-instruction does not make sense here. Try this:
<li itemprop="startDate">
<xsl:attribute name="content">
<?php
echo "Monday";
?>
</xsl:attribute>
Monday
</li>

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
================

Transform a multidimensional array to XML and then use a recursive XSLT function

I have a function in PHP that retrieves all the directories and files from a given path. This returns me an array like:
array(
"dirname1" => array(
"dirname2" => array(
"dirname3" => array(
"0" => "file1",
"1" => "file2",
"2" => "file3"
),
"0" => "file4",
"1" => "file5",
"2" => "file6",
"dirname4" => array(
"0" => "file7",
"1" => "file8"
)
),
"0" => "file9",
"1" => "file10"
),
"0" => "file11",
"1" => "file12",
"2" => "file13"
);
What I finally need is a multidimensional (don't know if is the exactly word) list with <ul /> and <li /> generated with XSLT 1.0 from a XML file, like:
<ul>
<li class="dirname">
dirname1
<ul>
<li class="dirname">
Dirname2
<ul>
<li class="dirname">
Dirname3
<ul>
<li>file1</li>
<li>file2</li>
<li>file3</li>
</ul>
</li>
<li>file4</li>
<li>file5</li>
<li>file6</li>
<li class="dirname">
dirname4
<ul>
<li>file7</li>
<li>file8</li>
</ul>
</li>
</ul>
</li>
<li>file9</li>
<li>file10</li>
</ul>
</li>
<li>file11</li>
<li>file12</li>
<li>file13</li>
</ul>
And finally, inside every <li /> I need the path in a <a />, like:
<li class="dirname">dirname1</li>
<li>file1</li>
<li>file9</li>
Actually I don't have the XML that I need to convert because I don't know what can be a nice structure for then convert it to XSLT 1.0. I have the paths inside the <a />. I can do it with PHP if necessary and I can detect in PHP when it is a directory or when not and we can also add something on the XML to detect the class="dirname".
I hope I've given sufficient information to understand me.
Thank you in advance!
Tunneling parameter:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:param name="pPath"/>
<xsl:copy>
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="li[#class='dirname']">
<xsl:param name="pPath"/>
<xsl:call-template name="identity">
<xsl:with-param name="pPath"
select="concat($pPath,'/',normalize-space(text()[1]))"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="text()">
<xsl:param name="pPath"/>
<xsl:call-template name="text">
<xsl:with-param name="pPath"
select="concat($pPath,'/',normalize-space())"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="li[#class='dirname']/text()" name="text">
<xsl:param name="pPath"/>
<a href="{$pPath}">
<xsl:value-of select="."/>
</a>
</xsl:template>
</xsl:stylesheet>
Output:
<ul>
<li class="dirname">
<a href="/dirname1">
dirname1
</a>
<ul>
<li class="dirname">
<a href="/dirname1/Dirname2">
Dirname2
</a>
<ul>
<li class="dirname">
<a href="/dirname1/Dirname2/Dirname3">
Dirname3
</a>
<ul>
<li>
<a href="/dirname1/Dirname2/Dirname3/file1"
>file1</a>
</li>
<li>
<a href="/dirname1/Dirname2/Dirname3/file2"
>file2</a>
</li>
<li>
<a href="/dirname1/Dirname2/Dirname3/file3"
>file3</a>
</li>
</ul>
</li>
<li>
file4
</li>
<li>
file5
</li>
<li>
file6
</li>
<li class="dirname">
<a href="/dirname1/Dirname2/dirname4">
dirname4
</a>
<ul>
<li>
<a href="/dirname1/Dirname2/dirname4/file7"
>file7</a>
</li>
<li>
<a href="/dirname1/Dirname2/dirname4/file8"
>file8</a>
</li>
</ul>
</li>
</ul>
</li>
<li>
file9
</li>
<li>
file10
</li>
</ul>
</li>
<li>
file11
</li>
<li>
file12
</li>
<li>
file13
</li>
</ul>
PHP part
function ulRenderer($dirs, $path = array()) {
echo '<ul>';
foreach ($dirs as $key => $value) {
if (is_array($value)) {
printf(
'<li class="dirname">%s',
implode('/', $path),
$key
);
ulRenderer($value, array_merge(
$path, array($key)
));
echo '</li>';
}
else {
printf(
'<li>%s</li>',
implode('/', $path),
$value, $value
);
}
}
echo '</ul>';
}
$dirs = array(
"dirname1" => array(
"dirname2" => array(
"dirname3" => array(
"0" => "file1",
"1" => "file2",
"2" => "file3"
),
"0" => "file4",
"1" => "file5",
"2" => "file6",
"dirname4" => array(
"0" => "file7",
"1" => "file8"
)
),
"0" => "file9",
"1" => "file10"
),
"0" => "file11",
"1" => "file12",
"2" => "file13"
);
ulRenderer($dirs);
XML/XSL part
dirs.xml
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<dir name="dirname1">
<dir name="dirname2">
<dir name="dirname3">
<file>file1</file>
<file>file2</file>
<file>file3</file>
</dir>
<dir name="dirname4">
<file>file4</file>
<file>file5</file>
</dir>
<file>file6</file>
<file>file7</file>
<file>file8</file>
</dir>
<file>file11</file>
<file>file12</file>
<file>file13</file>
</dir>
</root>
dirs.xslt
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="dir">
<ul>
<li>
<a>
<xsl:attribute name="href">
<xsl:call-template name="path" />
</xsl:attribute>
<xsl:value-of select="#name" />
</a>
<xsl:apply-templates />
</li>
</ul>
</xsl:template>
<xsl:template match="file">
<li>
<a>
<xsl:attribute name="href">
<xsl:call-template name="path" />
</xsl:attribute>
<xsl:value-of select="." />
</a>
</li>
</xsl:template>
<xsl:template name="path">
<xsl:if test="parent::dir">
<xsl:for-each select="ancestor::dir">
<xsl:sort select="position()" order="ascending"/>
<xsl:value-of select="#name" />
<xsl:text>/</xsl:text>
</xsl:for-each>
</xsl:if>
<xsl:choose>
<xsl:when test="name() = 'dir'">
<xsl:value-of select="#name" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
and php again
$dom = new DOMDocument;
$dom->load('dirs.xml');
$xsl = new DOMDocument;
$xsl->load('dirs.xslt', LIBXML_NOCDATA);
$xslt = new XSLTProcessor();
$xslt->importStylesheet($xsl);
echo $xslt->transformToXML($dom);

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