1<?php 2 3declare(strict_types=1); 4 5namespace Sabre\Xml\Element; 6 7use Sabre\Xml\Element; 8use Sabre\Xml\Reader; 9use Sabre\Xml\Writer; 10 11/** 12 * The XmlFragment element allows you to extract a portion of your xml tree, 13 * and get a well-formed xml string. 14 * 15 * This goes a bit beyond `innerXml` and friends, as we'll also match all the 16 * correct namespaces. 17 * 18 * Please note that the XML fragment: 19 * 20 * 1. Will not have an <?xml declaration. 21 * 2. Or a DTD 22 * 3. It will have all the relevant xmlns attributes. 23 * 4. It may not have a root element. 24 */ 25class XmlFragment implements Element 26{ 27 /** 28 * The inner XML value. 29 * 30 * @var string 31 */ 32 protected $xml; 33 34 /** 35 * Constructor. 36 */ 37 public function __construct(string $xml) 38 { 39 $this->xml = $xml; 40 } 41 42 /** 43 * Returns the inner XML document. 44 */ 45 public function getXml(): string 46 { 47 return $this->xml; 48 } 49 50 /** 51 * The xmlSerialize metod is called during xml writing. 52 * 53 * Use the $writer argument to write its own xml serialization. 54 * 55 * An important note: do _not_ create a parent element. Any element 56 * implementing XmlSerializble should only ever write what's considered 57 * its 'inner xml'. 58 * 59 * The parent of the current element is responsible for writing a 60 * containing element. 61 * 62 * This allows serializers to be re-used for different element names. 63 * 64 * If you are opening new elements, you must also close them again. 65 */ 66 public function xmlSerialize(Writer $writer) 67 { 68 $reader = new Reader(); 69 70 // Wrapping the xml in a container, so root-less values can still be 71 // parsed. 72 $xml = <<<XML 73<?xml version="1.0"?> 74<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment> 75XML; 76 77 $reader->xml($xml); 78 79 while ($reader->read()) { 80 if ($reader->depth < 1) { 81 // Skipping the root node. 82 continue; 83 } 84 85 switch ($reader->nodeType) { 86 case Reader::ELEMENT: 87 $writer->startElement( 88 (string) $reader->getClark() 89 ); 90 $empty = $reader->isEmptyElement; 91 while ($reader->moveToNextAttribute()) { 92 switch ($reader->namespaceURI) { 93 case '': 94 $writer->writeAttribute($reader->localName, $reader->value); 95 break; 96 case 'http://www.w3.org/2000/xmlns/': 97 // Skip namespace declarations 98 break; 99 default: 100 $writer->writeAttribute((string) $reader->getClark(), $reader->value); 101 break; 102 } 103 } 104 if ($empty) { 105 $writer->endElement(); 106 } 107 break; 108 case Reader::CDATA: 109 case Reader::TEXT: 110 $writer->text( 111 $reader->value 112 ); 113 break; 114 case Reader::END_ELEMENT: 115 $writer->endElement(); 116 break; 117 } 118 } 119 } 120 121 /** 122 * The deserialize method is called during xml parsing. 123 * 124 * This method is called statictly, this is because in theory this method 125 * may be used as a type of constructor, or factory method. 126 * 127 * Often you want to return an instance of the current class, but you are 128 * free to return other data as well. 129 * 130 * You are responsible for advancing the reader to the next element. Not 131 * doing anything will result in a never-ending loop. 132 * 133 * If you just want to skip parsing for this element altogether, you can 134 * just call $reader->next(); 135 * 136 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to 137 * the next element. 138 * 139 * @return mixed 140 */ 141 public static function xmlDeserialize(Reader $reader) 142 { 143 $result = new self($reader->readInnerXml()); 144 $reader->next(); 145 146 return $result; 147 } 148} 149