I am trying to do a simple append to an existing XML document using SimpleXML in PHP. I have an HTML form that calls a PHP script that tries to append data to an already existing XML document in the same directory.
I have tried the basic implementation of this, but I keep getting the follow error:
Warning: SimpleXMLElement::addChild(): Cannot add child. Parent is
not a permanent member of the XML tree in
/the/directory/to/the/site/generate.php on line
9
Fatal error: Uncaught Error: Call to a member function addChild() on
null...
Here is the already existing XML file that is being used:
<?xml version="1.0" encoding="UTF-8"?>
<tasks>
<users/>
<taskList>
<tasks id="1234">
<activities>
<activity/>
</activities>
</task>
</taskList>
</tasks>
Here is the PHP code:
<?php
$file = 'tasks.xml';
$xml = simplexml_load_file($file);
$activities = $xml->activities;
$activity = $activities->addChild('activity');
$activity->setAttribute('id', '45678');
$activity->addChild('assigned', 'Jon');
$activity->addChild('priority', 'low');
$xml->asXML($file);
I am hard coding the values in just to get the append to work, but eventually these values will be from a submitted html form.
Any ideas as to why this is failing?
There are a couple of problems with your code. Firstly your XML is invalid, your close tag should be </tasks>.
When you try and get the <activities> tag in
$activities = $xml->activities;
this is trying to find the tag just off the root of the document, you could use the full path
$activities = $xml->taskList->tasks->activities;
or use (I have in this code) XPath to find it, this also allows you to pick out (if neccessary) which <tasks> tag you use depending of id.
$activities = $xml->xpath("//activities")[0];
$activity = $activities->addChild('activity');
$activity->addAttribute('id', '45678');
$activity->addChild('assigned', 'Jon');
$activity->addChild('priority', 'low');
You also use setAttribute() which doesn't exist - as the code shows it is addAttribute().
Related
I try to validate this document in PHP using DOMdocument's schemaValidate:
<?xml version="1.0" encoding="UTF-8"?> <works xmlns="http://pbn.nauka.gov.pl/-/ns/bibliography" pbn-unit-id="1388"><article><title>Mukowiscydoza</title></article></works>
by using $domDocument->schemaValidate('pbn-report.xsd')
Link to XSD:
https://pbn.nauka.gov.pl/help/images/files/pbn-report.xsd.zip
... and I always get an error
Error 1871: Element 'article': This element is not expected. Expected
is one of ( {http://pbn.nauka.gov.pl/-/ns/bibliography}article,
{http://pbn.nauka.gov.pl/-/ns/bibliography}book,
{http://pbn.nauka.gov.pl/-/ns/bibliography}chapter ). on line 0
For me it is incomprehensible. Why do I get an error when I pointed out the default namespace?
Solved.
It turns out that when you create a DOMDocument, when you add an Element every time you need to give Namespace. When generating a document (saveXML) will not make any difference, but if you run schemaValidate, the validator checks DOMDocument object, and not the generated XML.
In other words this code:
$domDocument = new DOMDocument('1.0', "UTF-8");
$domWorks = $domDocument->createElementNS("http://pbn.nauka.gov.pl/-/ns/bibliography",'works');
$domWorksId = $domDocument->createAttribute('pbn-unit-id');
$domWorksId->value = PBNID;
$domWorks->appendChild($domWorksId);
$domDocument->appendChild($domWorks);
$domArticle = $domDocument->createElement('article');
$domArticle->appendChild($domDocument->createElement('title','Mukowiscydoza'));
$domWorks->appendChild($domArticle);
echo htmlentities($domDocument->saveXML());
generates the same XML as this code
$domDocument = new DOMDocument('1.0', "UTF-8");
$domWorks = $domDocument->createElementNS("http://pbn.nauka.gov.pl/-/ns/bibliography",'works');
$domWorksId = $domDocument->createAttribute('pbn-unit-id');
$domWorksId->value = PBNID;
$domWorks->appendChild($domWorksId);
$domDocument->appendChild($domWorks);
$domArticle = $domDocument->createElementNS("http://pbn.nauka.gov.pl/-/ns/bibliography",'article');
$domArticle->appendChild($domDocument->createElementNS("http://pbn.nauka.gov.pl/-/ns/bibliography",'title','Mukowiscydoza'));
$domWorks->appendChild($domArticle);
echo htmlentities($domDocument->saveXML());
But if you check schema
$domDocument->schemaValidate('pbn-report.xsd');
, the first code will return an error.
Strange ...
Strange ...
Well not really. As long as the document is in memory, the information about the namespace(s) with the elements is preserved.
In that case the two different methods / parameter here really make a difference even if you don't see a difference in the generated XML (afterwards):
// null namespace
$domArticle = $domDocument->createElement('article');
// vs. concrete namespace
$domArticle = $domDocument->createElementNS(
'http://pbn.nauka.gov.pl/-/ns/bibliography', 'article'
);
You then serialize the document (what you describe as "generates the same XML") as XML and you then load that XML back into memory. Then the elements with no namespace aren't within the null namespace any longer because they inherit their namespace from their parent element.
So you must differ between the document and it's elements in memory (DOM) and in the serialized form (string, file).
You can have similar effects when you do XSLT transformations. So if you experience something strange, it's worth to consider that the document in memory is not representing what you first think even it creates similar - or even exact same - looking XML ;)
Try to put the xmlns inside the article element , then try again.
xmlns="http://pbn.nauka.gov.pl/-/ns/bibliography"
I have a problem rendering a sitemap with laravel.
Generated xml seems ok but when i try to call the url from chrome or firefox i got an error
error on line 2 at column 6: XML declaration allowed only at the start of the document
In fact line 1 of the document is empty and xml declaration starts on line 2
Here is my code :
return Response::view('sitemap.index', ['agences' => $agences])->header('Content-Type', 'application/xml');
i tried that syntax too :
$xml = View::make('sitemap.index', ['agences' => $agences]);
return Response::make($xml, 200)->header('Content-Type', 'application/xml');
That way i could do
dd($xml->render());
and realize the string returned has no empty first line.
So i'm guessing Response::make is the one to blame but i really have no idea where to look from there
Ok I'm gonna post my own answer cause that was tricky and it costs me a day, the good thing is my knowledge of laravel has slightly increased.
So i had my xml sitemap beginning with an empty line, and that created an error on browser.
Xml was first generated using a blade template.
As it didn't work i decided to use RoumenDamianoff/laravel-sitemap
But i had the same problem. So finally i decided to generate Xml myself again using SimpleXmlElement and it changes nothing.
At that point i begun to dig in Laravel internal's to see where that empty line could come from in the request lifecycle.
Basically my sitemap is very simple :
$urlset = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" /><!--?xml version="1.0" encoding="UTF-8"?-->');
datas = MyModel::All();
foreach($datas as $index=>$data){
// generate sitemap
}
$dom = new DomDocument();
$dom->loadXML($urlset->asXML());
$dom->formatOutput = true;
//output xml
$xml = $dom->saveXML();
$response = Response::make($xml, 200, ['Content-Type' => 'application/xml']);
Just to test i decided to change the model i was requesting, and then my xml generated without that first empty line.
So i decided to investigate the model itself and find the error. The model file just had an empty line before php opening tag.
Deleting that empty line has solved my problem ....
I need to traverse a dbpedia's xml resource file to get the abstract and some other basic information like formation year and budget.
An example for this would be the US EPA.(the bottom of the page has links to different data formats of the same file)
I only need the first rdf:Description namespace of the xml file. A snippet of the code
$xml_result = file_get_contents($xml_url);
$xml_data = simplexml_load_string($xml_result);
$namespaces = $xml_data->getNamespaces(true);
//print_r($namespaces);
$current = $xml_data->children($namespaces['rdf']);
This only gets me the rdf elements inside the first rdf:Description. how do i get access to other elements like the dbpedia-owl namespace elements inside the Description element ?
You can use multiple namespaces, see https://stackoverflow.com/a/13350242/865201
Without testing it, I think you can use something like
$xml_data->children($namespaces['rdf'])->Description->children($namespaces['dbpedia-owl'])->anotherElement;
I am trying to import a XML file via API into my php script which then will parse said XML file and extract a string. Ive searched across the webspace for an answer, and though I have found a ton of resources I still cannot get this script to work.
The XML file that I am loading will look something like this
<api version="2">
<currentTime>2012-07-28</currentTime>
<result>
<rowset name="accounts" key="accountID" columns="accountID,accountKey,balance">
<row accountID="555555555" accountKey="6666" balance="7777777777.23"/>
</rowset>
</result>
<cachedUntil>2012-07-28</cachedUntil>
</api>
I am trying to get my php script to fetch the value of the attribute balance.
This is the code that I have put together so far:
<?php
$apiurl = "api.some-arbitrary-api-site.com;
$xml = simplexml_load_file($apiurl);
print_r($xml);
$balance = $xml->balance;
print_r($balance);
?>
This returns:
SimpleXMLElement Object ( )
Also following some other web tutorials I have tried this change
$balance = $xml->row->attributes()->balance;
print_r($balance);
Which spits out
Warning: main() [function.main]: Node no longer exists in C:\xampp\htdocs\EVE\progress\import.php on line 22
Warning: main() [function.main]: Node no longer exists in C:\xampp\htdocs\EVE\progress\import.php on line 22
What am I doing wrong? The end result is for the page to load this API and fetch the balance once every two days and store the data to be used in a chart rendered using highchart.
Any help will be appreciated!
Thanks
Musa answered the question
I don't know simplexml but shouldn't it be
$xml->result->rowset->row->attributes()->balance – Musa
I was being an idiot and didnt check the xml over again when typing in the path.
Thanks
In PHP, I am trying to validate an XML document using a DTD specified by my application - not by the externally fetched XML document. The validate method in the DOMDocument class seems to only validate using the DTD specified by the XML document itself, so this will not work.
Can this be done, and how, or do I have to translate my DTD to an XML schema so I can use the schemaValidate method?
(this seems to have been asked in Validate XML using a custom DTD in PHP but without correct answer, since the solution only relies on DTD speicified by the target XML)
Note: XML validation could be subject to the Billion Laughs attack, and similar DoS vectors.
This essentially does what rojoca mentioned in his comment:
<?php
$xml = <<<END
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo SYSTEM "foo.dtd">
<foo>
<bar>baz</bar>
</foo>
END;
$root = 'foo';
$old = new DOMDocument;
$old->loadXML($xml);
$creator = new DOMImplementation;
$doctype = $creator->createDocumentType($root, null, 'bar.dtd');
$new = $creator->createDocument(null, null, $doctype);
$new->encoding = "utf-8";
$oldNode = $old->getElementsByTagName($root)->item(0);
$newNode = $new->importNode($oldNode, true);
$new->appendChild($newNode);
$new->validate();
?>
This will validate the document against the bar.dtd.
You can't just call $new->loadXML(), because that would just set the DTD to the original, and the doctype property of a DOMDocument object is read-only, so you have to copy the root node (with everything in it) to a new DOM document.
I only just had a go with this myself, so I'm not entirely sure if this covers everything, but it definitely works for the XML in my example.
Of course, the quick-and-dirty solution would be to first get the XML as a string, search and replace the original DTD by your own DTD and then load it.
I think that's only possible with XSD, see:
http://php.net/manual/en/domdocument.schemavalidate#62032