Skip to content
Snippets Groups Projects
Commit d2dbcede authored by Aaron Klump's avatar Aaron Klump
Browse files

Cdata handling updates.

parent e15f00aa
No related branches found
No related tags found
No related merge requests found
<h2>Problems with CDATA in SimpleXML</h2>
<p><a href="http://blog.evandavey.com/2008/04/how-to-fix-simplexml-cdata-problem-in-php.html">http://blog.evandavey.com/2008/04/how-to-fix-simplexml-cdata-problem-in-php.html</a>
<a href="http://stackoverflow.com/questions/2970602/php-how-to-handle-cdata-with-simplexmlelement">http://stackoverflow.com/questions/2970602/php-how-to-handle-cdata-with-simplexmlelement</a>
<a href="http://stackoverflow.com/questions/6260224/how-to-write-cdata-using-simplexmlelement">http://stackoverflow.com/questions/6260224/how-to-write-cdata-using-simplexmlelement</a></p>
<h2>Comparison against SimpleXML</h2>
<h3>Doesn't print out the cdata correctly</h3>
<p>This code snippet:</p>
<pre><code>$xml = simplexml_load_string('&lt;demo/&gt;');
$xml-&gt;addChild('cdata', '&lt;![CDATA[&lt;strong&gt;title&lt;/strong&gt;]]&gt;');
print $xml-&gt;asXml();
</code></pre>
<p>Prints this out and transforms the cdata into escaped chars.</p>
<pre><code>&lt;?xml version="1.0"?&gt;
&lt;demo&gt;&lt;cdata&gt;&amp;lt;![CDATA[&amp;lt;strong&amp;gt;title&amp;lt;/strong&amp;gt;]]&amp;gt;&lt;/cdata&gt;&lt;/demo&gt;
</code></pre>
<p>However the extension class in this module handles this, observe:</p>
<pre><code>$xml = xml_field_load_string('&lt;demo/&gt;');
$xml-&gt;addChild('cdata', '&lt;![CDATA[&lt;strong&gt;title&lt;/strong&gt;]]&gt;');
print $xml-&gt;asXml();
</code></pre>
<p>Will yield what you'd expect:</p>
<pre><code>&lt;?xml version="1.0"?&gt;
&lt;demo&gt;&lt;cdata&gt;&lt;![CDATA[&lt;strong&gt;title&lt;/strong&gt;]]&gt;&lt;/cdata&gt;&lt;/demo&gt;
</code></pre>
<h2>Caveat: Accessing the cdata value</h2>
<p>If a node has been wrapped in CDATA, that value cannot be accessed directly, e.g. <code>$obj-&gt;title</code>, but rather you have to run it through like this <code>$obj-&gt;cdata($obj-&gt;title)</code>.</p>
<p>There is no special handling necessary when you use the $obj->asXml() method.</p>
<h3>Automatic CDATA escaping when enabled.</h3>
<p>This code snippet:</p>
<pre><code>&lt;?php
$xml = simplexml_load_string('&lt;demo/&gt;');
$xml-&gt;addChild('special', '&lt;strong&gt;Do&amp;mdash;Re&lt;/strong&gt;');
print $xml-&gt;asXml();
</code></pre>
<p>Yields the following output:</p>
<pre><code>&lt;?xml version="1.0"?&gt;
&lt;demo&gt;&lt;special&gt;&amp;lt;strong&amp;gt;Do&amp;mdash;Re&amp;lt;/strong&amp;gt;&lt;/special&gt;&lt;/demo&gt;
</code></pre>
<p>This code snippet (having enabled via <code>autoCData</code>), using our extension class: <code>xmlFieldXMLElement</code>:</p>
<pre><code>&lt;?php
xmlFieldXMLElement::$autoCData = TRUE;
$xml = xml_field_load_string('&lt;demo/&gt;');
$xml-&gt;addChild('special', '&lt;strong&gt;Do&amp;mdash;Re&lt;/strong&gt;');
print $xml-&gt;asXml();
</code></pre>
<p>Will automatically wrap values that include <a href="http://xml.silmaril.ie/specials.html">the 5 special chars</a> with the CDATA tag:</p>
<pre><code>&lt;?xml version="1.0"?&gt;
&lt;demo&gt;&lt;special&gt;&lt;![CDATA[&lt;strong&gt;Do&amp;mdash;Re&lt;/strong&gt;]]&gt;&lt;/special&gt;&lt;/demo&gt;
</code></pre>
<ul>
<li>Be aware that this is a setting for all instances and should be disabled after use, unless you intend for a constant on.</li>
</ul>
<?php
/**
* @file
* Defines module classes
*
* @ingroup xml_field
* @{
*/
/*
* Class xmlFieldXMLElement
*
* Allows for proper formatting of CDATA
*
* The approach taken is this: any node value that is wrapped in CDATA is
* stashed in the class static variable, keyed by the instance id, and then
* later retrieved during the asXml() method.
*/
class xmlFieldXMLElement extends simpleXMLElement {
public static $cdata_storage = array();
/**
* This is an array of setting keyed by the instance id of all instances
* of this class class. This technique is used to hold instance settings
* that are not propogated to the xml output and is due to the fact that
* we cannot add properties to the SimpleXML object.
*
* Even though this is public it should not be accessed, instead you must
* use the methods setSetting() and getSetting(). This is only public
* because it has to be to make the static work.
*
* @var array
*/
public static $settings = array();
/**
* Returns the cdata value by key.
*
* This must be used to directly access a node that is wrapped in cdata. If
* the passed node is not wrapped in cdata, then the string value will be
* returned; so this can be thought of as getter for the node value that
* casts the node value to a string.
*
* @code
* $xml->addChild('title', '<![CDATA[<h1>My Book</h1>]]>');
* $xml->cdata($xml->title);
* @endcode
*
* @param string|XmlFieldXmlElement $key
* E.g. [CDATA[0]] or just: 0
*/
public function cdata($key = NULL) {
if (!($cdata = $this->getSetting('cdata_storage'))) {
$cdata = array();
}
if (!isset($key)) {
return $cdata;
}
$key = (string) (is_numeric($key) ? "[CDATA[$key]]" : $key);
return isset($cdata[$key]) ? $cdata[$key] : $key;
}
/**
* Public setting for the AutoCData setting
*
* @param bool $state
*/
public function setAutoCData($state) {
return $this->setSetting('autoCData', $state);
}
public function setSetting($settingName, $value) {
$hash = spl_object_hash($this);
self::$settings[$hash][$settingName] = $value;
return $this;
}
public function getSetting($settingName) {
$hash = spl_object_hash($this);
return isset(self::$settings[$hash][$settingName]) ? self::$settings[$hash][$settingName] : NULL;
}
/**
* Add a child element; this corrects CDATA issues
*/
public function addChild($name, $value = NULL, $namespace = NULL) {
// Automatic CDATA handling if enabled.
if ($this->getSetting('autoCData')
&& strpos($value, '<![CDATA[') === FALSE
&& preg_match("/[<&>\"']/", $value)) {
$value = "<![CDATA[$value]]>";
}
// Move CDATA values to $cdata_storage and replace with token
if (strpos($value, '<![CDATA[') === 0) {
$cdata = $this->getSetting('cdata_storage');
$id = count($cdata);
$id = "[CDATA[$id]]";
$cdata[$id] = $value;
$this->setSetting('cdata_storage', $cdata);
$value = $id;
}
switch (func_num_args()) {
case 1:
return parent::addChild($name);
case 2:
return parent::addChild($name, $value);
case 3:
return parent::addChild($name, $value, $namespace);
}
//$parent = new ReflectionObject($this);
//$parent_class = $parent->getParentClass();
//$ref = new ReflectionMethod($parent_class, 'addChild');
//$ref->invoke(new)
// This broke in 5.2.x so the above is wonky but works - aklump
// 02/08/2012 10:41:08
//return call_user_func_array('parent::addChild', $args);
}
/**
* Adds a time value as a child and properly formats the time.
*
* @param string $name
* @param mixed $value This is sent to DateObject class.
* @param string $namespace
*
* @see DateObject
*
* This function depends on the date module being enabled.
*
* @see https://www.drupal.org/project/date
*/
public function addDateChild($name, $value, $namespace = NULL) {
$value = $value instanceof DateObject ? $value : new DateObject($value);
return $this->addChild($name, $value->format('r'), $namespace);
}
/**
* Extends to allow chaining of add attribute
*
* e.g. $xml->addAttribute('size', 'large')->addAttribute('color', 'blue')
*/
public function addAttribute($name, $value = NULL, $namespace = NULL) {
switch (func_num_args()) {
case 1:
parent::addAttribute($name);
break;
case 2:
parent::addAttribute($name, $value);
break;
case 3:
parent::addAttribute($name, $value, $namespace);
break;
}
return $this;
}
/**
* Renders to xml, counterpart to the CDATA issue
*/
public function asXML($filename = NULL) {
switch (func_num_args()) {
case 0:
$string = parent::asXML();
break;
case 1:
$string = parent::asXML($filename);
break;
}
// Restore the cdata
if ($this->cdata() && preg_match_all('/\[CDATA\[(\d+)\]\]/', $string, $found)) {
foreach ($found[0] as $id) {
$string = str_replace($id, $this->cdata($id), $string);
}
}
return $string;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment