xml icon indicating copy to clipboard operation
xml copied to clipboard

Attributes for root elements

Open ilyachase opened this issue 7 years ago • 3 comments

Hi guys. Awesome library. But I've faced problem - how can I set attributes for root elements?

Let me give you and example. I needed to build XML with following structure:

<?xml version="1.0" encoding="utf-8" ?>
<uclassify xmlns="http://api.uclassify.com/1/server/RequestSchema" version="1.00">
 <texts>
    <text id="text_1">I am happy sad bad</text>
  </texts>
  <readCalls>
    <classify id="call_1" username="uClassify" classifierName="IAB Taxonomy V2" textId="text_1" />
  </readCalls>
</uclassify>

And I didn't found the way to do two things:

  1. Set encoding attribute for <?xml version="1.0" encoding="utf-8" ?>
  2. Set version for uclassify element. This caused me problems, because server-side validator tells it's required.

I noticed that second argument of \Sabre\Xml\Service::write() function can take and render a lot of things, but as first argument it only takes a string so I couldn't set attributes for it.

ilyachase avatar Oct 08 '18 15:10 ilyachase

Hi @ilyachase ,

The library is optimized for the 'happy path', while still allowing more complex features if needed. What this means is that the common thing should be easy, but the uncommon thing should still be possible.

Writing encoding="utf-8"

This is not done, because it's the default for XML and thus unnecessary. Parsers should not need this, so we won't add a way to add this by default.

To implement this yourself, you can subclass the write() method of the Service class and call $writer->startDocument('1.0', 'utf-8'), or you can choose to use the Writer class directly instead of using the Service class.

Adding an attribute to the root element.

The easiest way to solve this is to not write a PHP array in your write() function, but pass an object instead.

Example class:

class MyRootElem implements \Sabre\Xml\XmlSerializable {
  
    public $value;

    function __construct($value) {
       $this->value = $value;
    }

    public function xmlSerialize(\Sabre\Xml\Writer $writer) {
       $writer->writeAttribute('version', '1.00');
       $writer->write($value);
    }
}

If you called this before:

$service->write('uclassify', $value);

Now you would call:

$service->write('uclassify', new MyRootElem($value));

You don't have to implement Sabre\Xml\XmlSerializable, you could alternatively also give it any other class, and use the classMap as such:

$service->classMap['MyRootElem2'] = function( $writer, $value) {
   $writer->writeAttribute('version', '1.00');
   $writer->write($value);
}
$service->write('uclassify', new MyRootElem2($value));

The difference between these 2 cases is with the classMap the serializer can live outside the class itself.

I think a nice feature request for this library could be to allow callbacks to be passed directly to the serializer, but this does NOT work today:

$service->write('uclassify', function ($writer) {
   $writer->writeAttribute('version', '1.00');
   $writer->write($value);
});

evert avatar Oct 20 '18 23:10 evert

I see. Thanks for the explanation. I can try to do pull request for callback if you want.

ilyachase avatar Oct 21 '18 07:10 ilyachase

@evert do you want me to try to do Pull Request?

ilyachase avatar Oct 25 '18 05:10 ilyachase