I do have an xml generator written in PHP. sample is given below but few lines only due to space issues.
$output = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
$output .= '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">'."\n";
$output .= '<channel rdf:about="'.$urlfr.'">'."\n";
$output .= '<title>'.$title.'</title>'."\n";
$output .= '<link>'.$urlorg.'</link>'."\n";
$output .= '<description></description>'."\n";
$output .= '<dc:language>'.$lang.'</dc:language>'."\n";
$output .= '<dc:rights>'.$copyright.'</dc:rights>'."\n";
this is saved into a file called content-xml.xml. every day I do have a new content add to this file. what I want is how do I add new content to an existing XML file and show the latest content on top??
The data has to be coming from somewhere, right? How about automating the data retrieval process. Once you have the data you could easily use SimpleXML to add a child node to your root node. :)
Use DomDocument assuming it is available to you
//Create an Instance of DomDocument and load existing XML
$xmlDoc=new DomDocument();
$xmlDoc->loadXML($xmlString);
$xmlDoc->saveXML();
//Create an Instance of DomDocument with xml to be appended
$xmlSnippet=new DomDocument();
$xmlSnippet->loadXML($xmlSnippet);
// get node to insertbefore let say item so first item in rss feed
$item = $xmlSnippet->getElementsByTagName("item")->item(0);
$item = $xmlDoc->importNode($item, true);
//append to channel node
$item = $xmlDoc->documentGetElementByTagName('channel')->item(0)->appendChild($item)
save doc
$xmlDoc->saveXML();
I am pretty sure there is libraries out there that ease the creation of RSS feeds, but if you want to do it with a proper XML extension, here is an example with DOM:
First we define the namespace. This is for laziness only.
$namespaces = array(
'xmlns' => 'http://purl.org/rss/1.0/',
'xmlns:rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'xmlns:slash' => 'http://purl.org/rss/1.0/modules/slash/',
'xmlns:taxo' => 'http://purl.org/rss/1.0/modules/taxonomy/',
'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
'xmlns:syn' => 'http://purl.org/rss/1.0/modules/syndication/',
'xmlns:admin' => 'http://webns.net/mvcb/',
'xmlns:feedburner' => 'http://rssnamespace.org/feedburner/ext/1.0'
);
Next you need to create and setup a new Document. We want nicely formatted UTF8 XML:
// prepare DOMDocument
$dom = new DOMDocument('1.0', 'utf-8');
$dom->formatOutput = TRUE;
$dom->preserveWhitespace = FALSE;
Next you need to create a root element and add all the namespaces to it. Because we have the namespaces in an array, we can simply iterate over the array and add them:
// create root node
$root = $dom->createElement('rdf:RDF');
foreach($namespaces as $ns => $uri) {
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', $ns, $uri);
}
$dom->appendChild($root);
The remainder is creating and adding nodes. This is always the same. Create Node, configure it, append it to the parent element. The code below is equivalent to your concatenated strings:
// create and append Channel
$channel = $dom->createElement('channel');
$channel->setAttribute('rdf:about', 'foo');
$root->appendChild($channel);
// create and append Title and Description
$channel->appendChild($dom->createElement('title', 'Example Feed'));
$channel->appendChild($dom->createElement('description'));
// special chars like & are only automatically encoded when added as DOMText
$link = $dom->createElement('link');
$link->appendChild($dom->createTextNode('http://example.com?foo=1&bar=2'));
$channel->appendChild($link);
// we added namespaces to root, so we can simply add ns'ed elements with
$channel->appendChild($dom->createElement('dc:language', 'en'));
$channel->appendChild($dom->createElement('dc:rights', 'public domain'));
And that's it. Now to output, you do:
// output cleanly formatted XML
echo $dom->saveXML();
Related
I want to save DOM tags value to exist XML, I found replace function but it is in js and I need the function in PHP
I tried save and saveXML function, but this didn't worked. I have tags in XML with colon "iaiext:auction_title". I used getElement and it's work good, next i cut title to 50 characters function work too, but how i can replace old title to this new title if i dont use path like simple_load_file. How to show in my script this path?
$dom = new DOMDocument;
$dom->load('p.xml');
$i = 0;
$tytuly = $dom->getElementsByTagName('auction_title');
foreach ($tytuly as $tytul){
$title = $tytul->nodeValue;
$end_title = doTitleCut($title);
//echo "<pre>";
//echo($end_title);
//echo "<pre>";
$i = $i+1;
}
In your loop, you can update a particular nodes value the same way you fetch it - with nodeValue. So in your loop, just update it each time...
$tytul->nodeValue = doTitleCut($title);
Then after your loop, you can just echo the new XML out using
echo $dom->saveXML();
or save it using
$dom->save("3.xml");
It is the same basic API in PHP. However browsers implement more or other parts of the API. Here are 5 revisions of the API (DOM Level 1 to 4 and DOM LS). DOM 3 added a property to read/write the text content of a node: https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent
The following example prefixes the titles:
$xml = <<<'XML'
<auctions>
<auction_title>World!</auction_title>
<auction_title>World & Universe!</auction_title>
</auctions>
XML;
$document = new DOMDocument();
$document->loadXML($xml);
$titleNodes = $document->getElementsByTagName('auction_title');
foreach ($titleNodes as $titleNode) {
$title = $titleNode->textContent;
$titleNode->textContent = 'Hello '.$title;
}
echo $document->saveXML();
Output:
<?xml version="1.0"?>
<auctions>
<auction_title>Hello World!</auction_title>
<auction_title>Hello World & Universe!</auction_title>
</auctions>
PHPs DOMNode::$nodeValue implementation does not match the W3C API definition. It behaves the same as DOMNode::$textContent for reads and does not fully escape on write.
I'm in the process of writing an XML file:
<?php
$xml2 = "currenttest";
$xml = new DOMDocument("1.0");
$root = $xml->createElement ('tv');
$xml->appendChild($root);
$root->appendChild($xml->createTextNode("\n"));
$root->appendChild($xml->createTextNode($xml2));
$root->appendChild($xml->createTextNode("\n"));
$xml->save('epg.xml');
XML:
<?xml version="1.0"?>
<tv>
test
</tv>
If i change the text and again runs the code, the old content is deleted.
And I want the old text to stay.
Let's say this:
<?xml version="1.0"?>
<tv>
currenttest...
newtest...
</tv>
My previous way was to write the XML with:
file_put_contents($file, $xml2, FILE_APPEND | LOCK_EX);
FILE_APPEND | LOCK_EX, its helped me that the previous text would not be erased
I found a solution in another post:
$doc->loadXML(file_get_contents('epg.xml'));
foreach($doc->getElementsByTagName('***') as $node)
{
}
But how can it fit into my code?
You have nothing particular to do, just to reload your xml string and to append a new text node to your root element:
// your previous code (I only changed the variable names and added a default encoding)
$text = "currenttest";
$dom = new DOMDocument("1.0", "UTF-8");
$root = $dom->createElement('tv');
$dom->appendChild($root);
$root->appendChild($dom->createTextNode("\n"));
$root->appendChild($dom->createTextNode($text));
$root->appendChild($dom->createTextNode("\n"));
$xml = $dom->saveXML();
// let's add a new element
$newtext = 'newtext';
$dom = new DOMDocument;
$dom->loadXML($xml);
$root = $dom->documentElement; // conveniant way to target the root element
// but you can also write:
//$root = $dom->getElementsByTagName('tv')->item(0);
$root->appendChild($dom->createTextNode($newtext));
$newxml = $dom->saveXML();
echo $newxml;
demo
About $doc->loadXML(file_get_contents('epg.xml'));, note that you don't need to use file_get_contents since DOMDocument has already two methods:
DOMDocument::loadXML that loads the xml content from a string.
DOMDocument::load that loads the xml content directly from a file.
In addition to DOMNode::appendChild that adds a node to an element after all the children nodes of this element, you have also DOMNode::insertBefore to add a node to an element before the child node of your choice.
I tryed the code on top, 'cause i was overwriting my data, but when I coded in my application, it didn't worked cause I was trying to add the new node data in the loaded xml, you have to create a root to add data inside.
$xml = new DOMDocument("1.0", "UTF-8");
//an tag root must be first thing to add
$root = $xml->createElement('root');
$xml->appendChild($root);
Then, just add the data when you need
$xml = new DOMDocument("1.0", "UTF-8");
$xml->load($sFilepath);
$root = $xml->getElementsByTagName('root')->item(0);
your structure must looks like this:
<xml version="1.0" encoding="UTF-8">
<root>
</root>
The answer on top is totally correct. This answer is only to help if somebody is having trouble to understand.
I've got an xml like this:
<father>
<son>Text with <b>HTML</b>.</son>
</father>
I'm using simplexml_load_string to parse it into SimpleXmlElement. Then I get my node like this
$xml->father->son->__toString(); //output: "Text with .", but expected "Text with <b>HTML</b>."
I need to handle simple HTML such as:
<b>text</b> or <br/> inside the xml which is sent by many users.
Me problem is that I can't just ask them to use CDATA because they won't be able to handle it properly, and they are already use to do without.
Also, if it's possible I don't want the file to be edited because the information need to be the one sent by the user.
The function simplexml_load_string simply erase anything inside HTML node and the HTML node itself.
How can I keep the information ?
SOLUTION
To handle the problem I used the asXml as explained by #ThW:
$tmp = $xml->father->son->asXml(); //<son>Text with <b>HTML</b>.</son>
I just added a preg_match to erase the node.
A CDATA section is a character node, just like a text node. But it does less encoding/decoding. This is mostly a downside, actually. On the upside something in a CDATA section might be more readable for a human and it allows for some BC in special cases. (Think HTML script tags.)
For an XML API they are nearly the same. Here is a small DOM example (SimpleXML abstracts to much).
$document = new DOMDocument();
$father = $document->appendChild(
$document->createElement('father')
);
$son = $father->appendChild(
$document->createElement('son')
);
$son->appendChild(
$document->createTextNode('With <b>HTML</b><br>It\'s so nice.')
);
$son = $father->appendChild(
$document->createElement('son')
);
$son->appendChild(
$document->createCDataSection('With <b>HTML</b><br>It\'s so nice.')
);
$document->formatOutput = TRUE;
echo $document->saveXml();
Output:
<?xml version="1.0"?>
<father>
<son>With <b>HTML</b><br>It's so nice.</son>
<son><![CDATA[With <b>HTML</b><br>It's so nice.]]></son>
</father>
As you can see they are serialized very differently - but from the API view they are basically exchangeable. If you're using an XML parser the value you get back should be the same in both cases.
So the first possibility is just letting the HTML fragment be stored in a character node. It is just a string value for the outer XML document itself.
The other way would be using XHTML. XHTML is XML compatible HTML. You can mix an match different XML formats, so you could add the XHTML fragment as part of the outer XML.
That seems to be what you're receiving. But SimpleXML has some problems with mixed nodes. So here is an example how you can read it in DOM.
$xml = <<<'XML'
<father>
<son>With <b>HTML</b><br/>It's so nice.</son>
</father>
XML;
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$result = '';
foreach ($xpath->evaluate('/father/son[1]/node()') as $child) {
$result .= $document->saveXml($child);
}
echo $result;
Output:
With <b>HTML</b><br/>It's so nice.
Basically you need to save each child of the son element as XML.
SimpleXML is based on the same DOM library internally. That allows you to convert a SimpleXMLElement into a DOM node. From there you can again save each child as XML.
$father = new SimpleXMLElement($xml);
$sonNode = dom_import_simplexml($father->son);
$document = $sonNode->ownerDocument;
$result = '';
foreach ($sonNode->childNodes as $child) {
$result .= $document->saveXml($child);
}
echo $result;
I am building up an xml file and need to include a segment of xml saved in a database (yeah, I wish that wasn't the case too).
// parent element
$parent = $dom->createElement('RecipeIngredients');
// the xml string I want to include
$xmlStr = $row['ingredientSectionXml'];
// load xml string into domDocument
$dom->loadXML( $xmlStr );
// add all Ingredient Sections from xmlStr as children of $parent
$xmlList = $dom->getElementsByTagName( 'IngredientSection' );
for ($i = $xmlList->length; --$i >= 0; ) {
$elem = $xmlList->item($i);
$parent->appendChild( $elem );
}
// add the parent to the $dom doc
$dom->appendChild( $parent );
Right now, I get the following error when I hit the line $parent->appendChild( $elem );
Fatal error: Uncaught exception 'DOMException' with message 'Wrong Document Error'
The XML in the string might look something like the following example. An important point is that there may be multiple IngredientSections, all of which need to be appended to the $parent element.
<IngredientSection name="Herbed Cheese">
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit>cups</Unit>
<Item>yogurt cheese</Item>
<Note>(see Tip)</Note>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit/>
<Item>scallions</Item>
<Note>, trimmed and minced</Note>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<IngredientSection name="Cracked-Wheat Crackers">
</IngredientSection>
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit>teaspoon</Unit>
<Item>salt</Item>
<Note/>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<RecipeIngredient>
<Quantity>1 1/4</Quantity>
<Unit>cups</Unit>
<Item>cracked wheat</Item>
<Note/>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
</IngredientSection>
Here a two possible solutions:
Import From A Source Document
This works only if the XML string is a valid document. You need to import the document element, or any descendant of it. Depends on the part you would like to add to the target document.
$xml = "<child>text</child>";
$source = new DOMDocument();
$source->loadXml($xml);
$target = new DOMDocument();
$root = $target->appendChild($target->createElement('root'));
$root->appendChild($target->importNode($source->documentElement, TRUE));
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<root><child>text</child></root>
Use A Document Fragment
This works for any valid XML fragment. Even if it has no root node.
$xml = "text<child>text</child>";
$target = new DOMDocument();
$root = $target->appendChild($target->createElement('root'));
$fragment = $target->createDocumentFragment();
$fragment->appendXml($xml);
$root->appendChild($fragment);
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<root>text<child>text</child></root>
You need to use ->importNode() instead of ->appendChild(). Your XML snippets are coming from a completely different XML document, and appendChild will only accept nodes which are part of the SAME xml tree. importNode() will accept "foreign" nodes and incorporate them into the main tree.
If I have three sets of data, say:
<note><from>Me</from><to>someone</to><message>hello</message></note>
<note><from>Me</from><to></to><message>Need milk & eggs</message></note>
<note><from>Me</from><message>Need milk & eggs</message></note>
and I'm using simplexml is there a way to have simple xml check that there's an empty/absent tag automatically?
I would like the output to be:
FROM TO MESSAGE
Me someone hello
Me NULL Need milk & eggs
Me NULL Need milk & eggs
Right now I'm doing it manually and I quickly realised that it's going to take a very long time to do it for long xml files.
My current sample code:
$xml = simplexml_load_string($string);
if ($xml->from != "") {$out .= $xml->from."\t"} else {$out .= "NULL\t";}
//repeat for all children, checking by name
Sometimes the order is different as well, there might be a xml with:
<note><message>pick up cd</message><from>me</from></note>
so iterating through the children and checking by index count doesn't work.
The actual xml files I'm working with are thousands of lines each, so I obviously can't just code in every tag.
It sounds like you need a DTD (Document Type Definition), which will define the required format of the XML file, and specify which elements are required, optional, what they can contain, etc.
DTDs can be used to validate an XML file before you do any processing with it.
Unfortunately, PHP's simplexml library doesn't do anything with DTD, but the DomDocument library does, so you may want to use that instead.
I'll leave it as a separate excersise for you to research how to create a DTD file. If you need more help with that, I'd suggest asking it as a separate question.
You could use the DOMDocument instead. I have created a quick demo that splits the <note> elements into an array using the XML tag names as keys. You could then iterate the resultant array to create your output.
I corrected the invalid XML by replacing the ampersand with the HTML entity equivalent (&).
<?php
libxml_use_internal_errors(true);
$xml = <<<XML
<notes>
<note><from>Me</from><to>someone</to><message>hello</message></note>
<note><from>Me</from><to></to><message>Need milk & eggs</message></note>
<note><from>Me</from><message>Need milk & eggs</message></note>
<note><message>pick up cd</message><from>me</from></note>
</notes>
XML;
function getNotes($nodelist) {
$notes = array();
foreach ($nodelist as $node) {
$noteParts = array();
foreach ($node->childNodes as $child) {
$noteParts[$child->tagName] = $child->nodeValue;
}
$notes[] = $noteParts;
}
return $notes;
}
$dom = new DOMDocument();
$dom->recover = true;
$dom->loadXML($xml);
$xpath = new DOMXPath($dom);
$nodelist = $xpath->query("//note");
$notes = getNotes($nodelist);
print_r($notes);
?>
Edit: If you change to $noteParts = array(); to $noteParts = array('from' => null, 'to' => null, 'message' => null); then it will always create the full set of keys.