<ret2:formFields xsi:type="ret1:FormFieldsType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
I want to know how can I do the following? The namespace of children are getting changed by setting the xsi:type attribute.
My Code:
$ret2FormFields = $ret2FileBody->addChild('ret2:formFields', null, 'http://www.w3.org/2001/XMLSchema-instance');
$ret2FormFields->addAttribute("xsi:type", "ret1:FormFieldsType", 'http://www.w3.org/2001/XMLSchema-instance');
$ret2FormFields->addChild('ret1:isReverseReplace', false);
$ret2FormFields->addChild('ret1:payDayDate', '2018-04-10'); /** #todo date will be dynamic */
Expected XML:
<ret2:formFields xsi:type="ret1:FormFieldsType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ret1:isReverseReplace>false</ret1:isReverseReplace>
<ret1:payDayDate>2018-04-10</ret1:payDayDate>
</ret2:formFields>
My Incorrect XML:
<ret2:formFields xmlns:ret2="http://www.w3.org/2001/XMLSchema-instance" ret2:type="ret1:FormFieldsType">
<ret1:isReverseReplace>false</ret1:isReverseReplace>
<ret1:payDayDate>2018-04-10</ret1:payDayDate>
</ret2:formFields>
I am stuck how to change the children namespaces ret1 without changing the parent namespace ret2
You are passing the wrong namespace to addChild here:
addChild('ret2:formFields', null, 'http://www.w3.org/2001/XMLSchema-instance');
This tells SimpleXML that you want the element to have prefix ret2 and namespace URI http://www.w3.org/2001/XMLSchema-instance, so it will generate:
<ret2:formFields xmlns:ret2="http://www.w3.org/2001/XMLSchema-instance">
You don't show in your example what the prefix ret2 has been assigned elsewhere in the document, but that's what you need to provide, e.g.:
addChild('ret2:formFields', null, 'http://example.org/mynamespaces/ret2');
Related
I have the problem that Properties with SerializedPath attributes, which I convert to an XML via Symfony Serializer (SerializerInterface, symfony/serializer-pack), does not have an expected behavior with arrays. Instead, the AbstractObjectNormalizer throws an exception. Symfony and related packages are using v6.2.5.
My goal is to create an XML like this one, with e.g. the following Object:
<?xml version="1.0"?>
<response>
<payment>
<allowedMethods>
<include name="Method A"/>
<include name="Method B"/>
</allowedMethods>
</payment>
</response>
// This is not working
class Payment
{
#[SerializedPath('[payment][allowedMethods][include][#name]')]
public array $methods = ['Method A', 'Method B'];
}
// ...
$this->serializer->serialize(new Payment(), 'xml');
But it results to the following Exception: The element you are trying to set is already populated: "[payment][allowedMethods][include][#name]".
which is thrown in the AbstractObjectNormalizer of Symfony:
if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) {
$propertyAccessor = PropertyAccess::createPropertyAccessor();
if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) {
throw new LogicException(sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath));
}
$propertyAccessor->setValue($data, $serializedPath, $attributeValue);
return $data;
}
My expected behavior would be that it behaves identically to the scalar values and therefore recognizes the # as an XML attribute and the rest as nodes:
// This is working great!
class Payment
{
#[SerializedPath('[payment][usedMethod][#name]')]
public string $usedMethod = 'Method A';
}
<?xml version="1.0"?>
<response>
<payment>
<usedMethod name="Method A"/>
</payment>
</response>
I've been trying to identify the exact problem and find a solution for some time, but since SerializedPath is very new (since Symfony 6.2), there is no great documentation for it. Internally it uses the PropertyAccessor, but where I could not find a suitable solution either.
Can someone maybe explain me the problem in more detail or even know a solution? Or even an alternative way. I'm trying to do it via the SerializedPath to avoid all the nested DTOs that are just there to map the (not always plausible) data model of the XML.
Another quite interesting fact is, if you comment out the thrown Exception in the AbstractObjectNormalizer it kind of works (for json fully, for xml partially, because the array can only hold one entry because of the annotation key):
class Payment
{
#[SerializedPath('[payment][allowedMethods][include]')]
public array $methods = [
'#name' => 'Method A'
];
}
<?xml version="1.0"?>
<response>
<payment>
<allowedMethods>
<include name="Method A"/>
</allowedMethods>
</payment>
</response>
I recently created a php web service using php native soap. I have created the wsdl, xsd and the php code to construct the response.
In my soapvar when I construct the soap arrayObject using the namespace prefix, some nodes have it and some don't.
What I want is all the nodes have the "ns1:" prefix or none of them.
In order to overcome the issue I removed the namespace from soapvar. So this removed the ns prefix But I always have the message from my wsdl "retrieveDataResponse" node with "ns1:" prefix and and all the rest I constructed without.
In my php I have nested foreach run in every node and children adding "XSD_STRING" or "SOAP_ENC_OBJECT" depending on the enc_type.
My soapvar in php in the foreach is :
$dataStruct[] = new SoapVar($ListOfDataStruct, SOAP_ENC_OBJECT, null, null, 'ListOfData', 'http://localhost/soap/retrieveCstData');
My XML response is
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/soap/retrieveCstData">
<SOAP-ENV:Body>
<ns1:retrieveDataResponse>
<ns1:cstData>
<ns1:Description>Discription N/A</ns1:Description>
<ns1:ListOfData>
<ns1:Customer-Data-Header>
<ns1:AssetDescription>Basic 12 Months</ns1:AssetDescription>
<ns1:AssetId>1-3QGMHQ9</ns1:AssetId>
<ns1:ProductDescription>Basic 12 Months</ns1:ProductDescription>
<ns1:ProductId>1-2E543A</ns1:ProductId>
<ns1:ProductName>Basic Product Subscription</ns1:ProductName>
<ns1:ListOfCstData-Asset>
<ns1:CstData-Asset>
<AssetIntegrationId>1-3Q3KSNI</AssetIntegrationId>
<ProductName>Basic Product Subscription</ProductName>
<ProductPartNumber>SAT0028</ProductPartNumber>
<StartDate>08/19/2015 21:00:00</StartDate>
<Status>Active</Status>
<ListOfProductXA/>
<ListOfAddress/>
<ListOfContact/>
</ns1:CstData-Asset>
</ns1:ListOfCstData-Asset>
</ns1:Customer-Data-Header>
<ns1:Customer-Data-Header>
<ns1:AssetId>1-7MRO-241</ns1:AssetId>
<ns1:ProductDescription>SubProduct A</ns1:ProductDescription>
<ns1:ProductId>1-65TVM</ns1:ProductId>
<ns1:ProductName>SubProduct A Type</ns1:ProductName>
<ns1:ProductType>Product</ns1:ProductType>
<ns1:ListOfCstData-Asset>
<ns1:CstData-Asset>
<AssetIntegrationId>1-5T126KG</AssetIntegrationId>
<ProductName>Asset-Product 1</ProductName>
<ProductPartNumber>N/A</ProductPartNumber>
<StartDate>08/16/2016 21:00:00</StartDate>
<Status>Active</Status>
<ListOfProductXA/>
<ListOfAddress/>
<ListOfContact/>
</ns1:CstData-Asset>
<ns1:CstData-Asset>
<AssetIntegrationId>W-C5PLG-11H-1</AssetIntegrationId>
<ProductName>SubProduct A Type</ProductName>
<ProductPartNumber>Data Packets</ProductPartNumber>
<RegisteredDate>02/21/1978</RegisteredDate>
<ServiceID>#56487%</ServiceID>
<StartDate>02/21/1978 00:00:00</StartDate>
<ListOfProductXA/>
<ListOfAddress>
<CutAddress>
<AddressType>Installation</AddressType>
<TEK>1651</TEK>
<Type>Old</Type>
<Country>US</Country>
<StreetNumberFrom>37</StreetNumberFrom>
<PostalCode>66857</PostalCode>
<State>CA</State>
<StreetName>
<State>JAX Avenue</State>
</StreetName>
</CutAddress>
</ListOfAddress>
<ListOfContact>
<Contact>
<ActiveStatus>Y</ActiveStatus>
<IsPrimaryMVG>Y</IsPrimaryMVG>
<CellularPhone>555687676</CellularPhone>
<FirstName>Jhon</FirstName>
<LastName>Doe</LastName>
<PreferredCommunicationMethod>SMS</PreferredCommunicationMethod>
<ContactType>Technical</ContactType>
</Contact>
</ListOfContact>
</ns1:CstData-Asset>
</ns1:ListOfCstData-Asset>
</ns1:Customer-Data-Header>
<ns1:Customer-Data-Header>
<ns1:AssetDescription>Satelite 80CM</ns1:AssetDescription>
<ns1:AssetId>1-3QGMHX9</ns1:AssetId>
<ns1:ProductDescription>Satelite 80CM</ns1:ProductDescription>
<ns1:ProductId>1-2DIYLT</ns1:ProductId>
<ns1:ProductName>TV SAT</ns1:ProductName>
<ns1:ProductType>Product</ns1:ProductType>
<ns1:ListOfCstData-Asset>
<ns1:CstData-Asset>
<SubscriberId>664668941</SubscriberId>
<Comments>Suspension/Reactivation</Comments>
<AssetIntegrationId>1-3Q3KSNJ</AssetIntegrationId>
<ProductName>TV SAT</ProductName>
<ProductPartNumber>TV_SAT</ProductPartNumber>
<ServiceID>9995654321587</ServiceID>
<StartDate>08/19/2015 21:00:00</StartDate>
<Status>Active</Status>
<ListOfProductXA/>
<ListOfAddress>
<CutAddress>
<AddressType>Installation</AddressType>
<TEK>1651</TEK>
<Type>Old</Type>
<Area>CA</Area>
<Country>US</Country>
<StreetNumberFrom>37</StreetNumberFrom>
<ResidenceType>Business</ResidenceType>
<Floor>0</Floor>
<MailBox>US</MailBox>
<PostalCode>66857</PostalCode>
<State>CA</State>
<StreetName>JAX AVENUE</StreetName>
<District>DownTown</District>
</CutAddress>
</ListOfAddress>
<ListOfContact/>
</ns1:CstData-Asset>
</ns1:ListOfCstData-Asset>
</ns1:Customer-Data-Header>
</ns1:ListOfData>
</ns1:retrieveDataResponse>
</ns1:retrieveDataResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
So as you can see prefix is not in every node.
Well I found the problem.
In one of the many foreach loops I used a previous declared arrayObject instead of the array key.
So soapvar does not ingone the namespace :)
I'm trying to create a rss feed and one element is
<content:encoded></content:encoded>
But, when i use this code:
$item->addChild('content:encoded',htmlspecialchars($itemdata->description));
I get this as a result:
<encoded> .................. </encoded>
I don't get the content namespace, and how would I be able to?
As you can see in the documentation, you need to provide the namespace URI as 3rd argument of addChild() to create element in namespace correctly :
$item->addChild(
'content:encoded',
htmlspecialchars($itemdata->description),
'namespace-URI-for-content-prefix-here'
);
Quick demo :
$raw = '<root xmlns:content="mynamespace"></root>';
$item = new SimpleXMLElement($raw);
$item->addChild(
'content:encoded',
'foo bar baz',
'mynamespace'
);
echo $item->asXML();
eval.in demo
output :
<?xml version="1.0"?>
<root xmlns:content="mynamespace"><content:encoded>foo bar baz</content:encoded></root>
I want to add multiple attributes to a child in SimpleXMLElement so it will look like this:
<data>
<photo>
<file size="3309519" size="JPG">P1270081</file>
</photo>
</data>
As it is right now in my code, I can only add one attribute per child as the code below shows.
$xml = new SimpleXMLElement('<data/>');
$photo = $xml->addChild('photo');
$photo->addChild('file', 'P1270081')->addAttribute('size', '3309519');
$photo->addChild('uploaded', '2013-09-01 15:23:10')->addAttribute('by', 'edgren');
If I change the third line to $photo->addChild('file', 'P1270081')->addAttribute('size', '3309519')->addAttribute('type', 'JPG'); I'm getting this error message:
Fatal error: Call to a member function addAttribute() on a non-object in ...
I am new to creating XML files on the fly with SimpleXMLElement so I don't know how I shall fix this issue. What should I do to fix it?
addAttribute returns void. If you want to add more attributes you have to something like this:
$file = $photo->addChild('file', 'P1270081');
$file->addAttribute('size', '3309519');
$file->addAttribute('type', 'JPG');
$photo->addChild(..) // returns the created XML component .. and you can chain a action(one) directly one it.
But addAttribute(..) returns nothing .. so you get an error if you try to chain event after it.
$photo = $xml->addChild('photo');
$photo->addChild('file', 'P1270081')->addAttribute('size', '3309519');
$theNewChild = $photo->addChild('uploaded', '2013-09-01 15:23:10')
$theNewChild ->addAttribute('by', 'edgren');
$theNewChild ->addAttribute('type', 'JPG');
I'm familiar with the DOMDocument::importNode method for importing a tree of nodes from some other document element.
However, what I was wondering is if I can automatically change the namespace prefix on a tree of nodes as I import them, that is, specify a new prefix for all nodes of that namespace.
Say the nodes, in their existing document, all have names like "name", "identity", and so on. When importing them into my new document they will be alongside other namespaces, so I'd like them to appear as "nicnames:name", "nicnames:identity" and so on. I'd like to be able to change this prefix programmatically so that in another context I may be able to import them as, for instance, "myprefix:name", "myprefix:identity" depending on the document they're imported into.
Edit: as per the explanation in my answer, I figured out I don't actually need to do this. I was misunderstanding namespaces in XML.
You probably have to write your own import code then. E.g.
function importNS(DOMNode $target, DOMNode $source, $fnImportElement, $fnImportAttribute) {
switch($source->nodeType) {
case XML_ELEMENT_NODE:
// invoke the callback that creates the new DOMElement node
$newNode = $fnImportElement($target->ownerDocument, $source);
if ( !is_null($newNode) && !is_null($source->attributes) ) {
foreach( $source->attributes as $attr) {
importNS($newNode, $attr, $fnImportElement, $fnImportAttribute);
}
}
break;
case XML_ATTRIBUTE_NODE:
// invoke the callback that creates the new DOMAttribute node
$newNode = $fnImportAttribute($target->ownerDocument, $source);
break;
default:
// flat copy
$newNode = $target->ownerDocument->importNode($source);
}
if ( !is_null($newNode) ) {
// import all child nodes
if ( !is_null($source->childNodes) ) {
foreach( $source->childNodes as $c) {
importNS($newNode, $c, $fnImportElement, $fnImportAttribute);
}
}
$target->appendChild($newNode);
}
}
$target = new DOMDocument;
$target->loadxml('<foo xmlns:myprefix="myprefixUri"></foo>');
$source = new DOMDocument;
$source->loadxml('<a>
<b x="123">...</b>
</a>');
$fnImportElement = function(DOMDocument $newOwnerDoc, DOMElement $e) {
return $newOwnerDoc->createElement('myprefix:'.$e->localName);
};
$fnImportAttribute = function(DOMDocument $newOwnerDoc, DOMAttr $a) {
// could use namespace here, too....
return $newOwnerDoc->createAttribute($a->name);
};
importNS($target->documentElement, $source->documentElement, $fnImportElement, $fnImportAttribute);
echo $target->savexml();
prints
<?xml version="1.0"?>
<foo xmlns:myprefix="myprefixUri"><myprefix:a>
<myprefix:b x="123">...</myprefix:b>
</myprefix:a></foo>
I discovered I'd misunderstood XML namespaces. They are actually much better than I thought they were.
I'd thought that each XML namespace used in a single document had to have a different namespace prefix. This is not true.
You can use different namespaces throughout your document even without namespace prefixes, just by including the xmlns attribute where appropriate, and that xmlns attribute only has effect for that element and its descendants, overriding the namespace for that prefix which may have been set higher up the tree.
For example, to have one namespace within another, you don't have to do:
<record xmlns="namespace1">
<person:surname xmlns:person="namespace2">Smith</person:surname>
</record>
You can just do
<record xmlns="namespace1">
<surname xmlns="namespace2">Smith</person>
</record>
Namespace prefixes are a good shortcut in certain situations, but not necessary when just including one document inside another of a different namespace.