I have xml with the following structure:
<?xml version="1.0"?>
<ONIXMessage xmlns="http://test.com/test">
...data...
</ONIXMessage>
I need to change xmlns attribute with my own value. How can I do it? Preferably with DOMDocument class.
I need to change xmlns attribute with my own value. How can I do it? Preferably with DOMDocument class.
This by design is not possible. Every DOMDocument has a single root/document element.
In your example XML that root element is:
{http://test.com/test}ONIXMessage
I write the element name as an expanded-name with the convention to put the namespace URI in front enclosed in angle brackets.
Writing the element name in a form that shows it's entire expanded-name also demonstrates that you do not only want to change the value of an attribute here, but you want to change the namespace URI of a specific element. So you want to change the element name. And probably also any child element name it contains if the child is in the same namespace.
As the xmlns attribute only reflects the namespace URI of the element itself, you can not change it. Once it is set in DOMDocument, you can not change it.
You can replace the whole element, but the namespace of the children is not changed either then. Here an example with an XML similar to yours with only textnode children (which aren't namespaced):
$xml = <<<EOD
<?xml version="1.0"?>
<ONIXMessage xmlns="uri:old">
...data...
</ONIXMessage>
EOD;
$doc = new DOMDocument();
$doc->loadXML($xml);
$newNode = $doc->createElementNS('uri:new', $doc->documentElement->tagName);
$oldNode = $doc->replaceChild($newNode, $doc->documentElement);
foreach(iterator_to_array($oldNode->childNodes, true) as $child) {
$doc->documentElement->appendChild($child);
}
Resulting XML output is:
<?xml version="1.0"?>
<ONIXMessage xmlns="uri:new">
...data...
</ONIXMessage>
Changing the input XML now to something that contains children like
<?xml version="1.0"?>
<ONIXMessage xmlns="uri:old">
<data>
...data...
</data>
</ONIXMessage>
Will then create the following output, take note of the old namespace URI that pops up now again:
<?xml version="1.0"?>
<ONIXMessage xmlns="uri:new">
<default:data xmlns:default="uri:old">
...data...
</default:data>
</ONIXMessage>
As you can see DOMDocument does not provide a functionality to replace namespace URIs for existing elements out of the box. But hopefully with the information provided in this answer so far it is more clear why exactly it is not possible to change that attributes value if it already exists.
The expat based parser in the libxml based PHP extension does allow to "change" existing attribute values regardless if it is an xmlns* attribute or not - because it just parses the data and you can process it on the fly with it.
A working example is:
$xml = <<<EOD
<?xml version="1.0" encoding="utf-8"?>
<ONIXMessage xmlns="uri:old">
<data>
...data...
</data>
</ONIXMessage>
EOD;
$uriReplace = [
'uri:old' => 'uri:new',
];
$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_default_handler($parser, function ($parser, $data) {
echo $data;
});
xml_set_element_handler($parser, function ($parser, $name, $attribs) use ($xml, $uriReplace) {
$selfClosing = '/>' === substr($xml, xml_get_current_byte_index($parser), 2);
echo '<', $name;
foreach ($attribs as $name => $value) {
if (substr($name, 0, 5) === 'xmlns' && isset($uriReplace[$value])) {
$value = $uriReplace[$value];
}
printf(' %s="%s"', $name, htmlspecialchars($value, ENT_COMPAT | ENT_XML1));
}
echo $selfClosing ? '/>' : '>';
}, function ($parser, $name) use ($xml) {
$selfClosing = '/>' === substr($xml, xml_get_current_byte_index($parser) - 2, 2);
if ($selfClosing) return;
echo '</', $name, '>';
});
xml_parse($parser, $xml, true);
xml_parser_free($parser);
The output then has transparently changed the namespace URI from uri:old to uri:new:
<ONIXMessage xmlns="uri:new">
<data>
...data...
</data>
</ONIXMessage>
As this example shows, each XML feature you make use of in your XML needs to be handled with the parser. For example the XML declaration is missing. However these can be added by implementing missing handler classbacks (e.g. for CDATA sections) or by outputting missing output (e.g. for the "missing" XML declaration). I hope this is helpful and shows you an alternative way on how to change even these values that are not intended to change.
Related
I'm creating an xml file with PHP.
The file I need to create is this one I show you:
<p:FatturaElettronica versione="FPA12" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12" >
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234567890</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>00001</ProgressivoInvio>
<FormatoTrasmissione>FPA12</FormatoTrasmissione>
<CodiceDestinatario>AAAAAA</CodiceDestinatario>
</DatiTrasmissione>
</FatturaElettronicaHeader>
<p:FatturaElettronica>
This is my code:
$xml = new SimpleXMLElement('<p:FatturazioneElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" />');
$xml->addAttribute("versione","FPA12");
$xml->addAttribute("xmlns:xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
$FatturaElettronicaHeader = $xml->addChild('FatturaElettronicaHeader');
$DatiTrasmissione=$FatturaElettronicaHeader->addChild('DatiTrasmissione');
$IdTrasmittente=$DatiTrasmissione->addChild('IdTrasmittente');
$IdTrasmittente->addChild('IdPaese', 'IT');
$IdTrasmittente->addChild('IdCodice','01234567890');
$ProgressivoInvio=$DatiTrasmissione->addChild('ProgressivoInvio', '00001');
$FormatoTrasmissione=$DatiTrasmissione->addChild('DatiTrasmissione', 'FPA12');
$CodiceDestinatario=$DatiTrasmissione->addChild('CodiceDestinatario', 'AAAAAA');
Because in my created file I initially had the prefix p: in each tag, as shown below
<p:FatturazioneElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12">
<p:FatturaElettronicaHeader>
<p:DatiTrasmissione>
<p:IdTrasmittente>
<p:IdPaese>IT</p:IdPaese>
<p:IdCodice>01234567890</p:IdCodice>
</p:IdTrasmittente>
<p:ProgressivoInvio>00001</p:ProgressivoInvio>
<p:DatiTrasmissione>FPA12</p:DatiTrasmissione>
<p:CodiceDestinatario>AAAAAA</p:CodiceDestinatario>
</p:DatiTrasmissione>
while this prefix p: must be only in the root node (p:FatturaElettronica) I added xmlns="http://dummy.com"
<p:FatturazioneElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12" xmlns="http://dummy.com">
and
$fatturaelettronicaheader = $xml->addChild('FatturaElettronicaHeader', '', 'http://dummy.com');
as it was suggested in this question
Only this 'http://dummy.com' is not present in the original xml file.
How can I solve this problem or possibly eliminate it before actually generating the file?
SimpleXML abstracts nodes and has some automatic logic for namespaces. That works fine for basic/simple XML structures.
For more complex XML structures you want to be explicit - so use DOM. It has specific methods for different node types with and without namespaces.
// define a list with the used namespaces
$namespaces = [
'xmlns' => 'http://www.w3.org/2000/xmlns/',
'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'signature' => 'http://www.w3.org/2000/09/xmldsig#',
'wsdl-types' => 'http://microsoft.com/wsdl/types/'
];
$document = new DOMDocument('1.0', 'UTF-8');
// create and append an element with a namespace
// this will add the namespace definition for the prefix "p" also
$document->appendChild(
$root = $document->createElementNS($namespaces['wsdl-types'], 'p:FatturazioneElettronica')
);
// set an attribute without a namespace
$root->setAttribute('versione', 'FPA12');
// add namespace definitions using the reserved "xmlns" namespace
$root->setAttributeNS($namespaces['xmlns'], 'xmlns:xsi', $namespaces['xsi']);
$root->setAttributeNS($namespaces['xmlns'], 'xmlns:ds', $namespaces['signature']);
// create and append the an element - keep in variable for manipulation
// the element does not have a namespace
$root->appendChild(
$header = $document->createElement('FatturaElettronicaHeader')
);
$header->appendChild(
$dati = $document->createElement('DatiTrasmissione')
);
$dati->appendChild(
$id = $document->createElement('IdTrasmittente')
);
// create and append element, set text content using a chained call
$id
->appendChild($document->createElement('IdPaese'))
->textContent = 'IT';
$id
->appendChild($document->createElement('IdCodice'))
->textContent = '01234567890';
$dati
->appendChild($document->createElement('ProgressivoInvio'))
->textContent = '00001';
$dati
->appendChild($document->createElement('FormatoTrasmissione'))
->textContent = 'FPA12';
$dati
->appendChild($document->createElement('CodiceDestinatario'))
->textContent = 'AAAAAA';
$document->formatOutput = TRUE;
echo $document->saveXML();
Output:
<?xml version="1.0" encoding="UTF-8"?>
<p:FatturazioneElettronica xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" versione="FPA12">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234567890</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>00001</ProgressivoInvio>
<FormatoTrasmissione>FPA12</FormatoTrasmissione>
<CodiceDestinatario>AAAAAA</CodiceDestinatario>
</DatiTrasmissione>
</FatturaElettronicaHeader>
</p:FatturazioneElettronica>
Be aware that in your XML p:FatturazioneElettronica has a namespace. It resolves to {http://microsoft.com/wsdl/types/}FatturazioneElettronica. However I don't think that FatturazioneElettronica is a valid element in the WSDL types namespace.
FatturaElettronicaHeader (and the descandant nodes) do not have a namespace.
First, your desired xml (as well as the one in the question you link to) is not well formed for several reasons.
Second, even after that's fixed (see below), it's not clear to me why you're going about it the way you do.
How about this way:
$string = '<?xml version="1.0" encoding="UTF-8"?>
<root>
<p:FatturaElettronica xmlns:p="http://microsoft.com/wsdl/types/" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234567890</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>00001</ProgressivoInvio>
<FormatoTrasmissione>FPA12</FormatoTrasmissione>
<CodiceDestinatario>AAAAAA</CodiceDestinatario>
</DatiTrasmissione>
</FatturaElettronicaHeader>
</p:FatturaElettronica>
</root>';
$xml = simplexml_load_string($string);
echo $xml->asXML() ."\r\n";
That should echo your well-formed xml.
I need to save <![CDATA[]]> tag when I parse XML document.
For example, I have node:
<Dest><![CDATA[some text...]]></Dest>
In xml file may be present nodes without CDATA.
Then I process all the nodes in loop:
$dom = simplexml_load_file($path);
foreach($dom->children() as $child) {
$nodeValue = (string) $child;
}
As a result, when I process node in example above - $nodeValue = some text...
But I need $nodeValue = <![CDATA[some text...]]>
There is any way to do this?
File example:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
<Params>
<param>text</param>
<anotherParam>text</anotherParam>
</Params>
<Content>
<String>
<Source>some another text</Source>
<Dest>some another text 2</Dest>
</String>
<String>
<Source>some another text 3</Source>
<Dest><![CDATA[some text...]]></Dest>
</String>
</Content>
</Root>
As far as a parser like SimpleXML is concerned, the <![CDATA[ is not part of the text content of the XML element, it's just part of the serialization of that content. A similar confusion is discussed here: PHP, SimpleXML, decoding entities in CDATA
What you need to look at is the "inner XML" of that element, which is tricky in SimpleXML (->asXML() will give you the "outer XML", e.g. <Dest><![CDATA[some text...]]></Dest>).
Your best bet here is to use the DOM which gives you more access to the detailed structure of the document, rather than trying to give you the content, so distinguishes "text nodes" and "CDATA nodes". However, it's worth double-checking that you do actually need this, as for 99.9% of use cases, you shouldn't care whether somebody sent you <foo>bar & baz</foo> or <foo><![CDATA[bar & baz]]></foo>, since by definition they represent the same string.
If you want to add CDATA to all element who doesn't have it, you can do that :
$dom = simplexml_load_file($path);
foreach($dom->children() as $child) {
if(strpos((string) $child,'CDATA')){
$nodeValue = (string) $child)
}
else {
$nodeValue = "<![CDATA[".((string) $child)."]]>";
}
}
with that you will have $nodeValue = '<![CDATA[some text...]]>'
if you want to just have the element where there is CDATA you can do that :
$dom = simplexml_load_file($path);
foreach($dom->children() as $child) {
if(strpos((string) $child,'CDATA')){
$nodeValue = (string) $child;
}
}
with that you will have $nodeValue = '<![CDATA[some text...]]>'
if you want the element without CDATA and add it you can do that :
$dom = simplexml_load_file($path);
foreach($dom->children() as $child) {
if(!strpos((string) $child,'CDATA')){
$notValue ="<![CDATA[".((string) $child)."]]>";
}
}
with that you wil have $nodeValue = '<![CDATA[some another text 3]]>'
I want to create dynamic tags in XML using PHP
like this : <wsse:Username>fqsuser01</wsse:Username>
the main thing is that I want the tags will change the value inside ---> "wsse"
(like this value)
what I need to do? to create this XML file wite PHP?
Thanks,
For this purpose you can use XMLWriter for example (another option is SimpleXML). Both option are in PHP core so any third party libraries aren't needed. wsse is a namespace - more about them you can read here
I also share with you some example code:
<?php
//create a new xmlwriter object
$xml = new XMLWriter();
//using memory for string output
$xml->openMemory();
//set the indentation to true (if false all the xml will be written on one line)
$xml->setIndent(true);
//create the document tag, you can specify the version and encoding here
$xml->startDocument();
//Create an element
$xml->startElement("root");
//Write to the element
$xml->writeElement("r1:id", "1");
$xml->writeElement("r2:id", "2");
$xml->writeElement("r3:id", "3");
$xml->endElement(); //End the element
//output the xml
echo $xml->outputMemory();
?>
Result:
<?xml version="1.0"?>
<root>
<r1:id>1</r1:id>
<r2:id>2</r2:id>
<r3:id>3</r3:id>
</root>
You could use a string and convert it to XML using simplexml_load_string(). The string must be well formed.
<?php
$usernames= array(
'username01',
'username02',
'username03'
);
$xml_string = '<wsse:Usernames>';
foreach($usernames as $username ){
$xml_string .= "<wsse:Username>$username</wsse:Username>";
}
$xml_string .= '</wsse:Usernames>';
$note=
<<<XML
$xml_string
XML; //backspace this line all the way to the left
$xml=simplexml_load_string($note);
?>
If you wanted to be able to change the namespaces on each XML element you would do something very similar to what is shown above. (Form a string with dynamic namespaces)
The XML portion that I instructed you to backspace all of the way has weird behavior. See https://www.w3schools.com/php/func_simplexml_load_string.asp for an example that you can copy & paste.
Using my below code i can read <abcxyz> xml tag easily. but how can i read the data between <abc:xyz> </abc:xml> xml tag..
xml tag using php.pls help....
my php sample code...
$objDOM->load("abc.xml");
$note = $objDOM->getElementsByTagName("note");
foreach( $note as $value )
{
$tasks = $value->getElementsByTagName("tasks");
$task = $tasks->item(0)->nodeValue;
$details = $value->getElementsByTagName("details");
$detail = $details->item(0)->nodeValue;
echo "$task :: $detail<br>";
}
My XML sample code:
<mynotes>
<note>
<tasks>Task 1</tasks>
<details>Detail 1</details>
</note>
<abc:xyz> Cannot Read the XML data between this tag</abc:xyz>
</mynotes>
Pls guide me...
Thanks
Riad
abc:xyz means that the element is named xyz, and the namespace is indicated by abc. The namespace part is actually shorthand for an URI, which is usually also given in the XML file. For example, you may see this:
xmlns:abc="http://www.abc.com/xml"
In this case, elements which have abc before the colon are in the namespace http://www.abc.com/xml.
To retrieve this element, you need to use getElementsByTagNameNS and pass http://www.abc.com/xml as the namespace.
you need DOMDocument::getElementsByTagNameNS
Going with the DOMDocument::getElementsByTagNameNS way like others have suggested, here is a working code (including reading the inner content), assuming that you also have some namespace declaration (like <abc:response xmlns:abc="http://api-url"> ) part as pointed out by #Sjoerd -
$xml = '<?xml version="1.0"?>
<abc:response xmlns:abc="http://api-url">
<mynotes>
<note>
<tasks>Task 1</tasks>
<details>Detail 1</details>
</note>
<abc:xyz> Can Read the XML data between this tag!!</abc:xyz>
</mynotes>
</abc:response>';
$dom = new DOMDocument;
// load the XML string defined above
$dom->loadXML($xml);
foreach ($dom->getElementsByTagNameNS('http://api-url', '*') as $element)
{
//echo 'see - local name: ', $element->localName, ', prefix: ', $element->prefix, "\n";
if($element->localName == "xyz")
echo get_inner_html($element);
}
function get_inner_html( $node )
{
$innerHTML= '';
$children = $node->childNodes;
foreach ($children as $child)
{
$innerHTML .= $child->ownerDocument->saveXML( $child );
}
return $innerHTML;
}
Here is a working link showing the output.
Note that I have just wrapped your xml inside this -
'<?xml version="1.0"?>
<abc:response xmlns:abc="http://api-url">'
.$yourxml
.'</abc:response>';
I used the solution I got from here PHP DOM get nodevalue html? (without stripping tags)... was stuck with a similar problem these days.
A strange thing happened after a supplier changed the XML header a bit. I used to be able to read stuff using xpath, but now I can't even get a reply with
$xml->xpath('/');
They changed it from this...
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE NewsML SYSTEM "http://www.newsml.org/dl.php?fn=NewsML/1.2/specification/NewsML_1.2.dtd" [
<!ENTITY % nitf SYSTEM "http://www.nitf.org/IPTC/NITF/3.4/specification/dtd/nitf-3-4.dtd">
%nitf;
]>
<NewsML>
...
to this:
<?xml version="1.0" encoding="iso-8859-1"?>
<NewsML
xmlns="http://iptc.org/std/NewsML/2003-10-10/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://iptc.org/std/NewsML/2003-10-10/ http://www.iptc.org/std/NewsML/1.2/specification/NewsML_1.2.xsd http://iptc.org/std/NITF/2006-10-18/ http://contentdienst.pressetext.com/misc/nitf-3-4.xsd"
>
...
Most likely this is because they've introduced a default namespace (xmlns="http://iptc.org/std/NewsML/2003-10-10/") into their document. SimpleXML's support for default namespaces is not very good, to put it mildly.
Can you try to explicitly register a namespace prefix:
$xml->registerXPathNamespace("n", "http://iptc.org/std/NewsML/2003-10-10/");
$xml->xpath('/n:NewsML');
You would have to adapt your XPath expressions to use the "n:" prefix on every element. Here is some additional info: http://people.ischool.berkeley.edu/~felix/xml/php-and-xmlns.html.
EDIT: As per the spec:
The registerXPathNamespace() function creates a prefix/ns context for the next XPath query.
This means it would have to be called before every XPath query, thus a function to wrap XPath queries would be the natural thing to do:
function simplexml_xpath_ns($element, $xpath, $xmlns)
{
foreach ($xmlns as $prefix_uri)
{
list($prefix, $uri) = explode("=", $prefix_uri, 2);
$element->registerXPathNamespace($prefix, $uri);
}
return $element->xpath($xpath);
}
Usage:
$xmlns = ["n=http://iptc.org/std/NewsML/2003-10-10/"];
$result = simplexml_xpath_ns($xml, '/n:NewsML', $xmlns);