1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\Xml; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehler/** 6*a1a3b679SAndreas Boehler * XML parsing and writing service. 7*a1a3b679SAndreas Boehler * 8*a1a3b679SAndreas Boehler * You are encouraged to make a instance of this for your application and 9*a1a3b679SAndreas Boehler * potentially extend it, as a central API point for dealing with xml and 10*a1a3b679SAndreas Boehler * configuring the reader and writer. 11*a1a3b679SAndreas Boehler * 12*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). 13*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 14*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 15*a1a3b679SAndreas Boehler */ 16*a1a3b679SAndreas Boehlerclass Service { 17*a1a3b679SAndreas Boehler 18*a1a3b679SAndreas Boehler /** 19*a1a3b679SAndreas Boehler * This is the element map. It contains a list of XML elements (in clark 20*a1a3b679SAndreas Boehler * notation) as keys and PHP class names as values. 21*a1a3b679SAndreas Boehler * 22*a1a3b679SAndreas Boehler * The PHP class names must implement Sabre\Xml\Element. 23*a1a3b679SAndreas Boehler * 24*a1a3b679SAndreas Boehler * Values may also be a callable. In that case the function will be called 25*a1a3b679SAndreas Boehler * directly. 26*a1a3b679SAndreas Boehler * 27*a1a3b679SAndreas Boehler * @var array 28*a1a3b679SAndreas Boehler */ 29*a1a3b679SAndreas Boehler public $elementMap = []; 30*a1a3b679SAndreas Boehler 31*a1a3b679SAndreas Boehler /** 32*a1a3b679SAndreas Boehler * This is a list of namespaces that you want to give default prefixes. 33*a1a3b679SAndreas Boehler * 34*a1a3b679SAndreas Boehler * You must make sure you create this entire list before starting to write. 35*a1a3b679SAndreas Boehler * They should be registered on the root element. 36*a1a3b679SAndreas Boehler * 37*a1a3b679SAndreas Boehler * @var array 38*a1a3b679SAndreas Boehler */ 39*a1a3b679SAndreas Boehler public $namespaceMap = []; 40*a1a3b679SAndreas Boehler 41*a1a3b679SAndreas Boehler /** 42*a1a3b679SAndreas Boehler * Returns a fresh XML Reader 43*a1a3b679SAndreas Boehler * 44*a1a3b679SAndreas Boehler * @return Reader 45*a1a3b679SAndreas Boehler */ 46*a1a3b679SAndreas Boehler function getReader() { 47*a1a3b679SAndreas Boehler 48*a1a3b679SAndreas Boehler $r = new Reader(); 49*a1a3b679SAndreas Boehler $r->elementMap = $this->elementMap; 50*a1a3b679SAndreas Boehler return $r; 51*a1a3b679SAndreas Boehler 52*a1a3b679SAndreas Boehler } 53*a1a3b679SAndreas Boehler 54*a1a3b679SAndreas Boehler /** 55*a1a3b679SAndreas Boehler * Returns a fresh xml writer 56*a1a3b679SAndreas Boehler * 57*a1a3b679SAndreas Boehler * @return Writer 58*a1a3b679SAndreas Boehler */ 59*a1a3b679SAndreas Boehler function getWriter() { 60*a1a3b679SAndreas Boehler 61*a1a3b679SAndreas Boehler $w = new Writer(); 62*a1a3b679SAndreas Boehler $w->namespaceMap = $this->namespaceMap; 63*a1a3b679SAndreas Boehler return $w; 64*a1a3b679SAndreas Boehler 65*a1a3b679SAndreas Boehler } 66*a1a3b679SAndreas Boehler 67*a1a3b679SAndreas Boehler /** 68*a1a3b679SAndreas Boehler * Parses a document in full. 69*a1a3b679SAndreas Boehler * 70*a1a3b679SAndreas Boehler * Input may be specified as a string or readable stream resource. 71*a1a3b679SAndreas Boehler * The returned value is the value of the root document. 72*a1a3b679SAndreas Boehler * 73*a1a3b679SAndreas Boehler * Specifying the $contextUri allows the parser to figure out what the URI 74*a1a3b679SAndreas Boehler * of the document was. This allows relative URIs within the document to be 75*a1a3b679SAndreas Boehler * expanded easily. 76*a1a3b679SAndreas Boehler * 77*a1a3b679SAndreas Boehler * The $rootElementName is specified by reference and will be populated 78*a1a3b679SAndreas Boehler * with the root element name of the document. 79*a1a3b679SAndreas Boehler * 80*a1a3b679SAndreas Boehler * @param string|resource $input 81*a1a3b679SAndreas Boehler * @param string|null $contextUri 82*a1a3b679SAndreas Boehler * @param string|null $rootElementName 83*a1a3b679SAndreas Boehler * @throws ParseException 84*a1a3b679SAndreas Boehler * @return array|object|string 85*a1a3b679SAndreas Boehler */ 86*a1a3b679SAndreas Boehler function parse($input, $contextUri = null, &$rootElementName = null) { 87*a1a3b679SAndreas Boehler 88*a1a3b679SAndreas Boehler if (is_resource($input)) { 89*a1a3b679SAndreas Boehler // Unfortunately the XMLReader doesn't support streams. When it 90*a1a3b679SAndreas Boehler // does, we can optimize this. 91*a1a3b679SAndreas Boehler $input = stream_get_contents($input); 92*a1a3b679SAndreas Boehler } 93*a1a3b679SAndreas Boehler $r = $this->getReader(); 94*a1a3b679SAndreas Boehler $r->contextUri = $contextUri; 95*a1a3b679SAndreas Boehler $r->xml($input); 96*a1a3b679SAndreas Boehler 97*a1a3b679SAndreas Boehler $result = $r->parse(); 98*a1a3b679SAndreas Boehler $rootElementName = $result['name']; 99*a1a3b679SAndreas Boehler return $result['value']; 100*a1a3b679SAndreas Boehler 101*a1a3b679SAndreas Boehler } 102*a1a3b679SAndreas Boehler 103*a1a3b679SAndreas Boehler /** 104*a1a3b679SAndreas Boehler * Parses a document in full, and specify what the expected root element 105*a1a3b679SAndreas Boehler * name is. 106*a1a3b679SAndreas Boehler * 107*a1a3b679SAndreas Boehler * This function works similar to parse, but the difference is that the 108*a1a3b679SAndreas Boehler * user can specify what the expected name of the root element should be, 109*a1a3b679SAndreas Boehler * in clark notation. 110*a1a3b679SAndreas Boehler * 111*a1a3b679SAndreas Boehler * This is useful in cases where you expected a specific document to be 112*a1a3b679SAndreas Boehler * passed, and reduces the amount of if statements. 113*a1a3b679SAndreas Boehler * 114*a1a3b679SAndreas Boehler * @param string $rootElementName 115*a1a3b679SAndreas Boehler * @param string|resource $input 116*a1a3b679SAndreas Boehler * @param string|null $contextUri 117*a1a3b679SAndreas Boehler * @return void 118*a1a3b679SAndreas Boehler */ 119*a1a3b679SAndreas Boehler function expect($rootElementName, $input, $contextUri = null) { 120*a1a3b679SAndreas Boehler 121*a1a3b679SAndreas Boehler if (is_resource($input)) { 122*a1a3b679SAndreas Boehler // Unfortunately the XMLReader doesn't support streams. When it 123*a1a3b679SAndreas Boehler // does, we can optimize this. 124*a1a3b679SAndreas Boehler $input = stream_get_contents($input); 125*a1a3b679SAndreas Boehler } 126*a1a3b679SAndreas Boehler $r = $this->getReader(); 127*a1a3b679SAndreas Boehler $r->contextUri = $contextUri; 128*a1a3b679SAndreas Boehler $r->xml($input); 129*a1a3b679SAndreas Boehler 130*a1a3b679SAndreas Boehler $result = $r->parse(); 131*a1a3b679SAndreas Boehler if ($rootElementName !== $result['name']) { 132*a1a3b679SAndreas Boehler throw new ParseException('Expected ' . $rootElementName . ' but received ' . $result['name'] . ' as the root element'); 133*a1a3b679SAndreas Boehler } 134*a1a3b679SAndreas Boehler return $result['value']; 135*a1a3b679SAndreas Boehler 136*a1a3b679SAndreas Boehler } 137*a1a3b679SAndreas Boehler 138*a1a3b679SAndreas Boehler /** 139*a1a3b679SAndreas Boehler * Generates an XML document in one go. 140*a1a3b679SAndreas Boehler * 141*a1a3b679SAndreas Boehler * The $rootElement must be specified in clark notation. 142*a1a3b679SAndreas Boehler * The value must be a string, an array or an object implementing 143*a1a3b679SAndreas Boehler * XmlSerializable. Basically, anything that's supported by the Writer 144*a1a3b679SAndreas Boehler * object. 145*a1a3b679SAndreas Boehler * 146*a1a3b679SAndreas Boehler * $contextUri can be used to specify a sort of 'root' of the PHP application, 147*a1a3b679SAndreas Boehler * in case the xml document is used as a http response. 148*a1a3b679SAndreas Boehler * 149*a1a3b679SAndreas Boehler * This allows an implementor to easily create URI's relative to the root 150*a1a3b679SAndreas Boehler * of the domain. 151*a1a3b679SAndreas Boehler * 152*a1a3b679SAndreas Boehler * @param string $rootElementName 153*a1a3b679SAndreas Boehler * @param string|array|XmlSerializable $value 154*a1a3b679SAndreas Boehler * @param string|null $contextUri 155*a1a3b679SAndreas Boehler */ 156*a1a3b679SAndreas Boehler function write($rootElementName, $value, $contextUri = null) { 157*a1a3b679SAndreas Boehler 158*a1a3b679SAndreas Boehler $w = $this->getWriter(); 159*a1a3b679SAndreas Boehler $w->openMemory(); 160*a1a3b679SAndreas Boehler $w->contextUri = $contextUri; 161*a1a3b679SAndreas Boehler $w->setIndent(true); 162*a1a3b679SAndreas Boehler $w->startDocument(); 163*a1a3b679SAndreas Boehler $w->writeElement($rootElementName, $value); 164*a1a3b679SAndreas Boehler return $w->outputMemory(); 165*a1a3b679SAndreas Boehler 166*a1a3b679SAndreas Boehler } 167*a1a3b679SAndreas Boehler 168*a1a3b679SAndreas Boehler 169*a1a3b679SAndreas Boehler /** 170*a1a3b679SAndreas Boehler * Parses a clark-notation string, and returns the namespace and element 171*a1a3b679SAndreas Boehler * name components. 172*a1a3b679SAndreas Boehler * 173*a1a3b679SAndreas Boehler * If the string was invalid, it will throw an InvalidArgumentException. 174*a1a3b679SAndreas Boehler * 175*a1a3b679SAndreas Boehler * @param string $str 176*a1a3b679SAndreas Boehler * @throws InvalidArgumentException 177*a1a3b679SAndreas Boehler * @return array 178*a1a3b679SAndreas Boehler */ 179*a1a3b679SAndreas Boehler static function parseClarkNotation($str) { 180*a1a3b679SAndreas Boehler 181*a1a3b679SAndreas Boehler if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) { 182*a1a3b679SAndreas Boehler throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string'); 183*a1a3b679SAndreas Boehler } 184*a1a3b679SAndreas Boehler 185*a1a3b679SAndreas Boehler return [ 186*a1a3b679SAndreas Boehler $matches[1], 187*a1a3b679SAndreas Boehler $matches[2] 188*a1a3b679SAndreas Boehler ]; 189*a1a3b679SAndreas Boehler 190*a1a3b679SAndreas Boehler } 191*a1a3b679SAndreas Boehler 192*a1a3b679SAndreas Boehler 193*a1a3b679SAndreas Boehler} 194