Jump to content

Adding Elements To An Existing Xml File


chibineku

Recommended Posts

I have learned the basics of fetching and displaying xml data and want to play with saving new records to a test file, called books.xml, which looks like:

<?xml version="1.0" ?><!--Sample XML Document --><Books><book>	   <title>A Very Good Book</title>	   <author>Jane Doe</author>	   <publishingInfo>			<publisher>Sams Publishing</publisher>			<publisherCity>Indianapolis</publisherCity>			<publishedYear>2008</publishedYear>	   </publishingInfo></book><book>	   <title>Another Very Good Book</title>	   <author>John Doe</author>	   <publishingInfo>			<publisher>Sams Publishing</publisher>			<publisherCity>Indianapolis</publisherCity>			<publishedYear>2008</publishedYear>	   </publishingInfo></book></Books>

My php file to display these records is:

<?php$dom = new DomDocument;$dom->load("books.xml");foreach($dom->documentElement->childNodes as $books) {  if (($books->nodeType ==1) && ($books->nodeName == "book")) {		foreach($books->childNodes as $theBook) {	  if(($theBook->nodeType ==1) && ($theBook->nodeName == "title")) {		$theBookTitle = $theBook->textContent;	  }	  if(($theBook->nodeType ==1) && ($theBook->nodeName == "author")) {		$theBookAuthor = $theBook->textContent;	  }	  if(($theBook->nodeType == 1) && ($theBook->nodeName == "publishingInfo")) {		 foreach($theBook->childNodes as $thePublishingInfo) {			 if(($thePublishingInfo->nodeType == 1) && ($thePublishingInfo->nodeName == "publisher")) {			 $theBookPublisher = $thePublishingInfo->textContent;		   }		   if(($thePublishingInfo->nodeType == 1) && ($thePublishingInfo->nodeName == "publishedYear")) {			 $theBookPublishedYear = $thePublishingInfo->textContent;		   }		 }	  }	}	echo "	<p><span style=\"text-decoration:underline \">".$theBookTitle."</span>	by ".$theBookAuthor."<br />	published by ".$theBookPublisher." in ".$theBookPublishedYear."</p>";	unset($theBookTitle);	unset($theBookAuthor);	unset($theBookPublisher);	unset($theBookPublishedYear);  }}?>

All fine and dandy. To make matters simple, I added this to the top, after the first 3 lines:

foreach($dom->childNodes as $elem) {  if($elem->nodeName = "Books") {$new = $dom->createElement('book');$dom->appendChild($new);$title = $dom->createElement('title');$dom->appendChild($title);$titleText = $dom->createTextNode('New Book');$dom->appendChild($titleText);$author = $dom->createElement('author');$dom->appendChild($author);$authorText = $dom->createTextNode('Jackie O');$dom->appendChild($authorText);$publisher = $dom->createElement('publisher');$dom->appendChild($publisher);$publisherText = $dom->createTextNode('Penguin');$dom->appendChild($publisherText);$publisherCity = $dom->createElement('publisherCity');$dom->appendChild($publisherCity);$publisherCityText = $dom->createTextNode('Atlanta');$dom->appendChild($publisherCityText);$publishedYear = $dom->createElement('publishedYear');$dom->appendChild($publishedYear);$publishedYearText = $dom->createTextNode('1998');$dom->appendChild($publishedYearText);$saveDom = $dom->save("/books.xml");  }}

This produces a fatal error, saying I "cannot write property..." at line 7, which is this one:

  if($elem->nodeName = "Books") {

I admit I'm not sure if what I have put ought to work, but it doesn't, and the php.net documentation for the save() function is sparse to say the least. I wonder if it is a file permissions problem?

Link to comment
Share on other sites

Oh man! I'm not such a temporal newb that I can afford these intellectually noobie mistakes...Now I get an error about not having permission...Hm, when I try it on my local test server, I don't get any errors, and nothing in my apache error logs, it just plain doesn't work. That said, saving changes to files in the htdocs folder isn't possible, you have to save outside and copy it in with administrator privelages... I guess I'll get on to my webhost about sorting my permissions...

Link to comment
Share on other sites

I think on Linux, when your path begins with "/", it treats it as the system root, not your document root. Try it as:

$dom->save("books.xml");

for the same folder, or

$dom->save($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . "books.xml");

for the document root.Either way, it's a good think your host doesn't let you (or anyone) write in the system root folder.

Link to comment
Share on other sites

chuckthumb.jpgHowever! I am now getting new errors, but I believe that is likely to be some problem with my node creation and append commands:
Warning: DOMDocument::load() [domdocument.load]: Extra content at the end of the document in /homepages/6/d203988372/htdocs/sinaesthesia/xml/books.xml, line: 23 in /homepages/6/d203988372/htdocs/sinaesthesia/xml/domexample.php5 on line 3Warning: Invalid argument supplied for foreach() in /homepages/6/d203988372/htdocs/sinaesthesia/xml/domexample.php5 on line 46
I get an error when I try to display the raw xml data, stating that there is junk after the end of the original file content.Edit: Would I be better reading the file as a string, using php to insert a new string, and then saving it back to xml? If I read it as a string, does the string include the tags or just text content?Edit 2: I am appending the new information, but it's being added after the root element, so it's outside the tree.
Link to comment
Share on other sites

Edit: Would I be better reading the file as a string, using php to insert a new string, and then saving it back to xml? If I read it as a string, does the string include the tags or just text content?Edit 2: I am appending the new information, but it's being added after the root element, so it's outside the tree.
Your second edit basically anwers yours first one. You need to use DOM instead of plain text appending in orde to keep the XML well formed, and one of those requirements for well formdness is that you have just one root element.
Link to comment
Share on other sites

Yep, but I can't see why it is being appended to the end of the root element? Surely if you append a child node to the root element, it should become either the first or last child, but still within the tree.

Link to comment
Share on other sites

Yep, but I can't see why it is being appended to the end of the root element? Surely if you append a child node to the root element, it should become either the first or last child, but still within the tree.
...or give you an error when you try to append a second element at the root level, since that's invalid. I'd consider the fact it doesn't give you an error a bug. If you aren't using the latest PHP version (5.3 - ideally, the latest snapshot), try it with the latest, and if it happens there too - report it as a bug.You can always append to $dom->documentElement explicitly anyway.At the document level, there can be several node types, which is why the appendChild() and the other similar methods are still available. You can have PIs, you can have a DTD and comments... all above the root element. And below it, you can have comments.
Link to comment
Share on other sites

Okie dokie, sitrep:I got it working, but when it creates a new element, it is only creating the opening tag, not the closing one. That seems a little hinky, since you need closing tags, so why wouldn't the php developers write that into the method? To be honest, I am only learning PHP XML DOM out of interest. I am much happier working with MySQL for data storage and retrieval, and only see myself, in the near future at least, using XML via AJAX if I am creating a site for someone who opts for a hosting package that doesn't support PHP (which seems unlikely to the point of being preposterous).

Link to comment
Share on other sites

It should be creating both the opening and closing tags... unless there's an error along the way, in which case the results are unpredictable. Otherwise

$dom = new DOMDocument('1.0', 'UTF-8');$dom->appendChild($dom->createElement('root'));echo htmlspecialchars($dom->saveXML(), ENT_NOQUOTES, 'UTF-8');

should create an element called "root" at the root level, making the output:

<?xml version="1.0" encoding="UTF-8"?><root/>

and if we continue down further,

$dom->documentElement->appendChild($dom->createElement('childOfRoot', 'contents of this child...'));echo htmlspecialchars($dom->saveXML(), ENT_NOQUOTES, 'UTF-8');

should then output

<?xml version="1.0" encoding="UTF-8"?><root><childOfRoot>contents of this child...</childOfRoot></root>

Link to comment
Share on other sites

Hm...what is this echo htmlspecialcharacters business (and the ENT NOQUOTES)? I am guessing this is the right way to do it, but I am brand new to it all. Do you need to specify the encoding each time you create an element? Does echoing it just output the changes so you can see if it is working correctly or generate errors if it isn't and returns false? Could I possibly bombard you with any more questions?? Tune in next week to find out!Edit: Hm, when I look at the XML tree, the new elements do have end tags...in fact, the tags that precede each element's content IS the end tag...perhaps if I swap the order??

Link to comment
Share on other sites

There's more time 'till next week, so feel free to bombard right away :).htmlspecialchars() escapes special characters in HTML to a form suitable for insertion in HTML as plain text. In other words,

<root/>

is translated into

<root/>

When the latter is viewed in a browser, it is displayed as the first. I used it so that you can just copy&paste&see it for yourself. If I have just used:

echo $dom->saveXML();

You'd have to right click, then "View Source" to see the result, because the browser will try to read the XML document as HTML, and therefore not show you the tags.The other two arguments affect the way htmlspecialchars() behaves. The second one specifies how should quotes be treated. ENT_NOQUOTES means that " and ' are escaped along with the other special characters. The third argument tells PHP the encoding in which the text ($dom->saveXML()) is. If you omit that argument or specify wrong encoding, and have non-latin text in the XML, the text may be misread by browsers after the htmlspecialchars() function is executed. In this case, it didn't made a difference, but I added just in case you decide to expand from this example.

Link to comment
Share on other sites

Almost everything is working when I use this:

<?php$dom = new DomDocument;$dom->load("books.xml");foreach($dom->childNodes as $elem) {  if($elem->nodeName == "Books") {$new = $dom->createElement('book');$dom->documentElement->appendChild($new);$title = $dom->createElement('title', 'New Book');$dom->documentElement->appendChild($title);$author = $dom->createElement('author', 'Jackie O');$dom->documentElement->appendChild($author);$publisher = $dom->createElement('publisher', 'Penguin');$dom->documentElement->appendChild($publisher);$publisherCity = $dom->createElement('publisherCity', 'Atlanta');$dom->documentElement->appendChild($publisherCity);$publishedYear = $dom->createElement('publishedYear', '1998');$dom->documentElement->appendChild($publishedYear);$saveDom = $dom->save("books.xml");  }}

Except that this code still creates an empty <book/> tag and then adds the elements that should be nested inside the <book></book> tags afterwards - which makes sense, because I've effectively added all the elements as if they were of equal standing. Can you show me how you would have added a child to childOfRoot in the example script you posted?

Link to comment
Share on other sites

OK, I think I finally see your initial problem... you're always appending at one point, which was initially the document root, and was later changed to the root element. You need to append each node to its parent, like in the example above, where the "childOfRoot" was appended to "root". All further levels go the same way:

$dom = new DOMDocument('1.0', 'UTF-8');//At this point, the XML is//<?xml version="1.0" encoding="UTF-8"?>$dom->appendChild($dom->createElement('root'));//Now the XML is//<?xml version="1.0" encoding="UTF-8"?>//<root/>$childOfRoot = $dom->createElement('childOfRoot');//The XML is unchanged... the "childOfRoot" element is just hanging around in the form of//<childOfRoot/>$childOfRoot->appendChild($dom->createElement('descendantOfRoot', 'contents of the descendant'));//The XML is still unchanged, but now the "childOfRoot" element has a new element appended to it,//making its "hanging in" form look like//<childOfRoot><descendantOfRoot>contents of the descendant</descendantOfRoot></childOfRoot>$dom->documentElement->appendChild($childOfRoot);//The "hanging in" form of the "childOfRoot" element is appended to the XML's document element.//As we defined earlier, that element is "root".echo $dom->saveXML();

would output (if you "View Source"):

<?xml version="1.0" encoding="UTF-8"?><root><childOfRoot><descendantOfRoot>contents of the descendant</descendantOfRoot></childOfRoot></root>

Link to comment
Share on other sites

I am victorious!Thank you for your help, gracious code samurai! It is easy when you know what you're doing! The two step process of create node, then append node, is the sort of thing that can screw with a novice. It's the same as getting results from MySQL....you need to make the query, but that doesn't return the array you need; you fetch the array, but still, that isn't good enough! - you must then while your way through it.Thanks again!

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...