I'm building an XML like:
<?xml ... ?>
<root>
<elements>0</elements>
<list>
<element>test1</element>
<element>test1</element>
<element>test1</element>
</list>
</root>
After appending all <element>s, I want to replace <elements>0</elements> by <elements>3</elements> for example.
I tried DOMNode::replaceChild, but it has no affect.
$numberOfElements = $xml->createElement('numberOfElements', '0');
$root->appendChild($numberOfElements);
/* append elements and count them */
$root->replaceChild($numberOfElements,
$xml->createElement('numberOfElements', $countElements)
);
How to properly use replaceChild or is there a different way?
From the docs:
public DOMNode replaceChild ( DOMNode $newnode , DOMNode $oldnode )
This means that you must specify the new node first, then the node to be replaced. You have it the wrong way around.
EDIT: That said, why not do this?
$numberOfElements->nodeValue = $countElements;
Related
I have xml like:
<root xmlns="urn:test:apis:baseComponents">
<books>
<book>
<name>50 shades of grey</name>
</book>
</books>
<disks>
<disk>
<name>Britney Spears</name>
</disk>
</disks>
</root>
And such php code:
$xml = new SimpleXMLElement($xml);
$books = $xml->books;
$disks = $xml->disks;
$disks->registerXPathNamespace('x', 'urn:test:apis:baseComponents');
$books->registerXPathNamespace('x', 'urn:test:apis:baseComponents');
$b_names = $books->xpath('//x:name');
b_names contains array with 2 values instead of 1. First holds books->book->name, second holds disks->disk->name.
Can you please explain what am I doing wrong and how could I find children of only one element?
The reason that I am using xpath instead of taking manually values using SimpleXMLElement, is that I don't know what value, which I want to search in advance.
Use $books->xpath('.//x:name') to search descendants of your $books variable and not descendants of the root node/document node (which the path //x:name does).
I want to completely remove the size="id" attribute from every <door> element.
<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249" size="30"/>
<door id="1041" entry="6523" size="3094"/>
-- and 1000 more....
</doors>
The PHP code:
$xml = new SimpleXMLElement('http://mysite/doors.xml', NULL, TRUE);
$ids_to_delete = array( 1, 1506 );
foreach ($ids_to_delete as $id) {
$result = $xml->xpath( "//door[#size='$id']" );
foreach ( $result as $node ) {
$dom = dom_import_simplexml($node);
$dom->parentNode->removeChild($dom);
}
}
$xml->saveXml();
I get no errors but it does not delete the size attribute. Why?
I get no errors but it does not delete the size attribute. Why?
There are mulitple reasons why it does not delete the size attribute. The one that popped first into my mind was that attributes are no child nodes. Using a method to remove a child does just not fit to remove an attribute.
Each element node has an associated set of attribute nodes; the element is the parent of each of these attribute nodes; however, an attribute node is not a child of its parent element.
From: Attribute Nodes - XML Path Language (XPath), bold by me.
However, you don't see an error here, because the $result you have is an empty array. You just don't select any nodes to remove - neither elements nor attributes - with your xpath. That is because there is no such element you look for:
//door[#size='1']
You're searching for the id in the size attribute: No match.
These are the reasons why you get no errors and it does not delete any size attribute: 1.) you don't delete attributes here, 2.) you don't query any elements to delete attributes from.
How to delete attributes in SimpleXML queried by Xpath?
You can remove the attribute nodes by selecting them with an Xpath query and then unset the SimpleXMLElement self-reference:
// all size attributes of all doors
$result = $xml->xpath("//door/#size");
foreach ($result as $node) {
unset($node[0]);
}
In this example, all attribute nodes are queried by the Xpath expressions that are size attributes of door elements (which is what you ask for in your question) and then those are removed from the XML.
//door/#size
(see Abbreviated Syntax)
Now here the full example:
<?php
/**
* #link https://eval.in/215817
*/
$buffer = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249" size="30"/>
<door id="1041" entry="6523" size="3094"/>
-- and 1000 more....
</doors>
XML;
$xml = new SimpleXMLElement($buffer);
// all size attributes of all doors
$result = $xml->xpath("//door/#size");
foreach ($result as $node) {
unset($node[0]);
}
$xml->saveXml("php://output");
Output (Online Demo):
<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249"/>
<door id="1041" entry="6523"/>
-- and 1000 more....
</doors>
You can do your whole query in DOMDocument using DOMXPath, rather than switching between SimpleXML and DOM:
$dom = new DOMDocument;
$dom->load('my_xml_file.xml');
# initialise an XPath object to act on the $dom object
$xp = new DOMXPath( $dom );
# run the query
foreach ($xp->query( "//door[#size]" ) as $door) {
# remove the attribute
$door->removeAttribute('size');
}
print $dom->saveXML();
Output for the input you supplied:
<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249"/>
<door id="1041" entry="6523"/>
</doors>
If you do want only to remove the size attribute for the IDs in your list, you should use the code:
foreach ($ids_to_delete as $id) {
# searches for elements with a matching ID and a size attribute
foreach ($xp->query("//door[#id='$id' and #size]") as $door) {
$door->removeAttribute('size');
}
}
Your code wasn't working for several reasons:
it looks like your XPath was wrong, since your array is called $ids_to_delete and your XPATH is looking for door elements with the size attribute equal to the value from $ids_to_delete;
you're converting the nodes to DOMDocument objects ($dom = dom_import_simplexml($node);) to do the deletion, but $xml->saveXml();, which I presume you printed somehow, is a SimpleXML object;
you need to remove the element attribute; removeChild removes the whole element.
I am having trouble getting the text "TestTwo" where the parent has an attribute with Name=SN from the XML below. I am able to get the SimpleXMLElement, but cannot figure out how to get the child.
What is the best way to return the text "TestTwo"?
Thank you!
$xml = simplexml_load_string($XMLBELOW);
$xml->registerXPathNamespace('s','urn:oasis:names:tc:SAML:2.0:assertion');
$result = $xml->xpath("//s:Attribute[#Name='sn']");
var_dump( $result);
$XMLBELOW = <<<XML
<?xml version="1.0" ?>
<saml2:Assertion ID="SAML-4324423" IssueInstant="2012-09-24T17:49:39Z" Version="2.0" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
<saml2:Issuer>
Test
</saml2:Issuer>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" NameQualifier="Test">
Tester
</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData NotBefore="2012-09-24T17:48:39Z" NotOnOrAfter="2012-09-24T17:51:39Z"/>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="2012-09-24T17:48:39Z" NotOnOrAfter="2012-09-24T17:51:39Z"/>
<saml2:AuthnStatement AuthnInstant="2012-09-24T17:49:39Z" SessionNotOnOrAfter="2012-09-24T17:51:39Z">
<saml2:SubjectLocality Address="105.57.487.48"/>
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
<saml2:AttributeStatement>
<saml2:Attribute Name="sn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml2:AttributeValue>
TestTwo
</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="departmentNumber" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml2:AttributeValue>
OPERS
</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
XML;
How about this?
$result = $xml->xpath("//s:Attribute[#Name='sn']/*");
var_dump( $result[0]->__toString() ); // or just `echo $result[0];`
The updated expression will get you all children of your target element (you can narrow the result set further, if you'd like).
As xpath() method returns an array, you need to take some of its elements, and just call 'toString' on these objects to get its text. Here I've done with the first one.
I have an xml file,
I want to open it, edit certain CDATA node with the values from $_POST input and save it as same file,
I've read some online documentation and ended up here,
someone please suggest a nice way of doing this...
regardsh
SimpleXML does not make CDATA elements accessible by default. You can either tell simplexml to skip them (default) or to read them (see: read cdata from a rss feed). If you read them, they are standard text values, so they get merged with other textnodes.
More control is offered by the Document Object ModelDocs, which offers a DOMCdataSection which extends from DOMText, the standard text node model.
Even though this is a different PHP library (DOM vs. SimpleXML), both are compatible to each other. For example a SimpleXMLElement can be converted into a DOMElement by using the dom_import_simplexml function.
If you post some code what you've done so far it should be easy to figure out how to access the CDATA sections you want to modify. Please provide as well some demo XML data so the example is more speaking.
Since I had the same issue just recently, I wanted to let people also see some code, because the linked examples can only add new CDATA sections, but do not remove the old ones. So "my" solutions is merged from the mentioned code example plus deleting the old CDATA node.
// get DOM node
$node = dom_import_simplexml($mySimpleXmlElement);
// remove existing CDATA ($node->childNodes->item(1) does not seem to work)
foreach($node->childNodes as $child) {
if ($child->nodeType == XML_CDATA_SECTION_NODE) {
$node->removeChild($child);
}
}
// add new CDATA
$no = $node->ownerDocument;
$node->appendChild($no->createCDATASection($myNewContent));
// print result
echo $xml->asXML();
I suggest you use this http://www.php.net/manual/en/class.domdocument.php
You can extend class SimpleXMLElement with simples function to do this
class ExSimpleXMLElement extends SimpleXMLElement {
/**
* Add CDATA text in a node
* #param string $cdata_text The CDATA value to add
*/
private function addCData($cdata_text) {
$node = dom_import_simplexml($this);
$no = $node->ownerDocument;
$node->appendChild($no->createCDATASection($cdata_text));
}
/**
* Create a child with CDATA value
* #param string $name The name of the child element to add.
* #param string $cdata_text The CDATA value of the child element.
*/
public function addChildCData($name, $cdata_text) {
$child = $this->addChild($name);
$child->addCData($cdata_text);
return $child;
}
/**
* Modify a value with CDATA value
* #param string $name The name of the node element to modify.
* #param string $cdata_text The CDATA value of the node element.
*/
public function valueChildCData($name, $cdata_text) {
$name->addCData($cdata_text);
return $name;
}
}
usage:
$xml_string = <<<XML
<root>
<item id="foo"/>
</root>
XML;
$xml5 = simplexml_load_string($xml_string, 'ExSimpleXMLElement');
$xml5->valueChildCData($xml5->item, 'mysupertext');
echo $xml5->asXML();
$xml6 = simplexml_load_string($xml_string, 'ExSimpleXMLElement');
$xml6->item->addChildCData('mylittlechild', 'thepunishment');
echo $xml6->asXML();
result:
<?xml version="1.0"?>
<root>
<item id="foo"><![CDATA[mysupertext]]></item>
</root>
<?xml version="1.0"?>
<root>
<item id="foo">
<mylittlechild><![CDATA[thepunishment]]></mylittlechild>
</item>
</root>
How can i get values inside <![CDATA[values]] > using php DOM.
This is few code from my xml.
<Destinations>
<Destination>
<![CDATA[Aghia Paraskevi, Skiatos, Greece]]>
<CountryCode>GR</CountryCode>
</Destination>
<Destination>
<![CDATA[Amettla, Spain]]>
<CountryCode>ES</CountryCode>
</Destination>
<Destination>
<![CDATA[Amoliani, Greece]]>
<CountryCode>GR</CountryCode>
</Destination>
<Destination>
<![CDATA[Boblingen, Germany]]>
<CountryCode>DE</CountryCode>
</Destination>
</Destinations>
Working with PHP DOM is fairly straightforward, and is very similar to Javascript's DOM.
Here are the important classes:
DOMNode — The base class for anything that can be traversed inside an XML/HTML document, including text nodes, comment nodes, and CDATA nodes
DOMElement — The base class for tags.
DOMDocument — The base class for documents. Contains the methods to load/save XML, as well as normal DOM document methods (see below).
There are a few staple methods and properties:
DOMDocument->load() — After creating a new DOMDocument, use this method on that object to load from a file.
DOMDocument->getElementsByTagName() — this method returns a node list of all elements in the document with the given tag name. Then you can iterate (foreach) on this list.
DOMNode->childNodes — A node list of all children of a node. (Remember, a CDATA section is a node!)
DOMNode->nodeType — Get the type of a node. CDATA nodes have type XML_CDATA_SECTION_NODE, which is a constant with the value 4.
DOMNode->textContent — get the text content of any node.
Note: Your CDATA sections are malformed. I don't know why there is an extra ]] in the first one, or an unclosed CDATA section at the end of the line, but I think it should simply be:
<![CDATA[Aghia Paraskevi, Skiatos, Greece]]>
Putting this all together we:
Create a new document object and load the XML
Get all Destination elements by tag name and iterate over the list
Iterate over all child nodes of each Destination element
Check if the node type is XML_CDATA_SECTION_NODE
If it is, echo the textContent of that node.
Code:
$doc = new DOMDocument();
$doc->load('test.xml');
$destinations = $doc->getElementsByTagName("Destination");
foreach ($destinations as $destination) {
foreach($destination->childNodes as $child) {
if ($child->nodeType == XML_CDATA_SECTION_NODE) {
echo $child->textContent . "<br/>";
}
}
}
Result:
Aghia Paraskevi, Skiatos, Greece
Amettla, Spain
Amoliani, Greece
Boblingen, Germany
Use this:
$parseFile = simplexml_load_file($myXML,'SimpleXMLElement', LIBXML_NOCDATA)
and next :
foreach ($parseFile->yourNode as $node ){
etc...
}
Best and easy way
$xml = simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA);
$xmlJson = json_encode($xml);
$xmlArr = json_decode($xmlJson, 1); // Returns associative array
Use replace CDATA before parsing PHP DOM element after that you can get the innerXml or innerHtml:
str_replace(array('<\![CDATA[',']]>'), '', $xml);
I use following code.
Its not only read all xml data with
<![CDATA[values]] >
but also convert xml object to php associative array. So we can apply loop on the data.
$xml_file_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA),true), true);
Hope this will work for you.
function inBetweenOf(string $here, string $there, string $content) : string {
$left_over = strlen(substr($content, strpos($content, $there)));
return substr($content, strpos($content, $here) + strlen($here), -$left_over);
}
Iterate over "Destination" tags and then call inBetweenOf on each iteration.
$doc = inBetweenOf('<![CDATA[', ']]>', $xml);