Util.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. <?php
  2. /**
  3. * XML_Util
  4. *
  5. * XML Utilities package
  6. *
  7. * PHP versions 4 and 5
  8. *
  9. * LICENSE:
  10. *
  11. * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net>
  12. * All rights reserved.
  13. *
  14. * Redistribution and use in source and binary forms, with or without
  15. * modification, are permitted provided that the following conditions
  16. * are met:
  17. *
  18. * * Redistributions of source code must retain the above copyright
  19. * notice, this list of conditions and the following disclaimer.
  20. * * Redistributions in binary form must reproduce the above copyright
  21. * notice, this list of conditions and the following disclaimer in the
  22. * documentation and/or other materials provided with the distribution.
  23. * * The name of the author may not be used to endorse or promote products
  24. * derived from this software without specific prior written permission.
  25. *
  26. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  27. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  28. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  29. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  30. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  31. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  32. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  33. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  34. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  35. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  36. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  37. *
  38. * @category XML
  39. * @package XML_Util
  40. * @author Stephan Schmidt <schst@php.net>
  41. * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
  42. * @license http://opensource.org/licenses/bsd-license New BSD License
  43. * @version CVS: $Id$
  44. * @link http://pear.php.net/package/XML_Util
  45. */
  46. /**
  47. * Error code for invalid chars in XML name
  48. */
  49. define('XML_UTIL_ERROR_INVALID_CHARS', 51);
  50. /**
  51. * Error code for invalid chars in XML name
  52. */
  53. define('XML_UTIL_ERROR_INVALID_START', 52);
  54. /**
  55. * Error code for non-scalar tag content
  56. */
  57. define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60);
  58. /**
  59. * Error code for missing tag name
  60. */
  61. define('XML_UTIL_ERROR_NO_TAG_NAME', 61);
  62. /**
  63. * Replace XML entities
  64. */
  65. define('XML_UTIL_REPLACE_ENTITIES', 1);
  66. /**
  67. * Embedd content in a CData Section
  68. */
  69. define('XML_UTIL_CDATA_SECTION', 5);
  70. /**
  71. * Do not replace entitites
  72. */
  73. define('XML_UTIL_ENTITIES_NONE', 0);
  74. /**
  75. * Replace all XML entitites
  76. * This setting will replace <, >, ", ' and &
  77. */
  78. define('XML_UTIL_ENTITIES_XML', 1);
  79. /**
  80. * Replace only required XML entitites
  81. * This setting will replace <, " and &
  82. */
  83. define('XML_UTIL_ENTITIES_XML_REQUIRED', 2);
  84. /**
  85. * Replace HTML entitites
  86. * @link http://www.php.net/htmlentities
  87. */
  88. define('XML_UTIL_ENTITIES_HTML', 3);
  89. /**
  90. * Do not collapse any empty tags.
  91. */
  92. define('XML_UTIL_COLLAPSE_NONE', 0);
  93. /**
  94. * Collapse all empty tags.
  95. */
  96. define('XML_UTIL_COLLAPSE_ALL', 1);
  97. /**
  98. * Collapse only empty XHTML tags that have no end tag.
  99. */
  100. define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2);
  101. /**
  102. * Utility class for working with XML documents
  103. *
  104. * @category XML
  105. * @package XML_Util
  106. * @author Stephan Schmidt <schst@php.net>
  107. * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
  108. * @license http://opensource.org/licenses/bsd-license New BSD License
  109. * @version Release: 1.4.3
  110. * @link http://pear.php.net/package/XML_Util
  111. */
  112. class XML_Util
  113. {
  114. /**
  115. * Return API version
  116. *
  117. * @return string $version API version
  118. */
  119. public static function apiVersion()
  120. {
  121. return '1.4';
  122. }
  123. /**
  124. * Replace XML entities
  125. *
  126. * With the optional second parameter, you may select, which
  127. * entities should be replaced.
  128. *
  129. * <code>
  130. * require_once 'XML/Util.php';
  131. *
  132. * // replace XML entites:
  133. * $string = XML_Util::replaceEntities('This string contains < & >.');
  134. * </code>
  135. *
  136. * With the optional third parameter, you may pass the character encoding
  137. * <code>
  138. * require_once 'XML/Util.php';
  139. *
  140. * // replace XML entites in UTF-8:
  141. * $string = XML_Util::replaceEntities(
  142. * 'This string contains < & > as well as ä, ö, ß, à and ê',
  143. * XML_UTIL_ENTITIES_HTML,
  144. * 'UTF-8'
  145. * );
  146. * </code>
  147. *
  148. * @param string $string string where XML special chars
  149. * should be replaced
  150. * @param int $replaceEntities setting for entities in attribute values
  151. * (one of XML_UTIL_ENTITIES_XML,
  152. * XML_UTIL_ENTITIES_XML_REQUIRED,
  153. * XML_UTIL_ENTITIES_HTML)
  154. * @param string $encoding encoding value (if any)...
  155. * must be a valid encoding as determined
  156. * by the htmlentities() function
  157. *
  158. * @return string string with replaced chars
  159. * @see reverseEntities()
  160. */
  161. public static function replaceEntities(
  162. $string, $replaceEntities = XML_UTIL_ENTITIES_XML, $encoding = 'ISO-8859-1'
  163. ) {
  164. switch ($replaceEntities) {
  165. case XML_UTIL_ENTITIES_XML:
  166. return strtr(
  167. $string,
  168. array(
  169. '&' => '&amp;',
  170. '>' => '&gt;',
  171. '<' => '&lt;',
  172. '"' => '&quot;',
  173. '\'' => '&apos;'
  174. )
  175. );
  176. break;
  177. case XML_UTIL_ENTITIES_XML_REQUIRED:
  178. return strtr(
  179. $string,
  180. array(
  181. '&' => '&amp;',
  182. '<' => '&lt;',
  183. '"' => '&quot;'
  184. )
  185. );
  186. break;
  187. case XML_UTIL_ENTITIES_HTML:
  188. return htmlentities($string, ENT_COMPAT, $encoding);
  189. break;
  190. }
  191. return $string;
  192. }
  193. /**
  194. * Reverse XML entities
  195. *
  196. * With the optional second parameter, you may select, which
  197. * entities should be reversed.
  198. *
  199. * <code>
  200. * require_once 'XML/Util.php';
  201. *
  202. * // reverse XML entites:
  203. * $string = XML_Util::reverseEntities('This string contains &lt; &amp; &gt;.');
  204. * </code>
  205. *
  206. * With the optional third parameter, you may pass the character encoding
  207. * <code>
  208. * require_once 'XML/Util.php';
  209. *
  210. * // reverse XML entites in UTF-8:
  211. * $string = XML_Util::reverseEntities(
  212. * 'This string contains &lt; &amp; &gt; as well as'
  213. * . ' &auml;, &ouml;, &szlig;, &agrave; and &ecirc;',
  214. * XML_UTIL_ENTITIES_HTML,
  215. * 'UTF-8'
  216. * );
  217. * </code>
  218. *
  219. * @param string $string string where XML special chars
  220. * should be replaced
  221. * @param int $replaceEntities setting for entities in attribute values
  222. * (one of XML_UTIL_ENTITIES_XML,
  223. * XML_UTIL_ENTITIES_XML_REQUIRED,
  224. * XML_UTIL_ENTITIES_HTML)
  225. * @param string $encoding encoding value (if any)...
  226. * must be a valid encoding as determined
  227. * by the html_entity_decode() function
  228. *
  229. * @return string string with replaced chars
  230. * @see replaceEntities()
  231. */
  232. public static function reverseEntities(
  233. $string, $replaceEntities = XML_UTIL_ENTITIES_XML, $encoding = 'ISO-8859-1'
  234. ) {
  235. switch ($replaceEntities) {
  236. case XML_UTIL_ENTITIES_XML:
  237. return strtr(
  238. $string,
  239. array(
  240. '&amp;' => '&',
  241. '&gt;' => '>',
  242. '&lt;' => '<',
  243. '&quot;' => '"',
  244. '&apos;' => '\''
  245. )
  246. );
  247. break;
  248. case XML_UTIL_ENTITIES_XML_REQUIRED:
  249. return strtr(
  250. $string,
  251. array(
  252. '&amp;' => '&',
  253. '&lt;' => '<',
  254. '&quot;' => '"'
  255. )
  256. );
  257. break;
  258. case XML_UTIL_ENTITIES_HTML:
  259. return html_entity_decode($string, ENT_COMPAT, $encoding);
  260. break;
  261. }
  262. return $string;
  263. }
  264. /**
  265. * Build an xml declaration
  266. *
  267. * <code>
  268. * require_once 'XML/Util.php';
  269. *
  270. * // get an XML declaration:
  271. * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true);
  272. * </code>
  273. *
  274. * @param string $version xml version
  275. * @param string $encoding character encoding
  276. * @param bool $standalone document is standalone (or not)
  277. *
  278. * @return string xml declaration
  279. * @uses attributesToString() to serialize the attributes of the
  280. * XML declaration
  281. */
  282. public static function getXMLDeclaration(
  283. $version = '1.0', $encoding = null, $standalone = null
  284. ) {
  285. $attributes = array(
  286. 'version' => $version,
  287. );
  288. // add encoding
  289. if ($encoding !== null) {
  290. $attributes['encoding'] = $encoding;
  291. }
  292. // add standalone, if specified
  293. if ($standalone !== null) {
  294. $attributes['standalone'] = $standalone ? 'yes' : 'no';
  295. }
  296. return sprintf(
  297. '<?xml%s?>',
  298. XML_Util::attributesToString($attributes, false)
  299. );
  300. }
  301. /**
  302. * Build a document type declaration
  303. *
  304. * <code>
  305. * require_once 'XML/Util.php';
  306. *
  307. * // get a doctype declaration:
  308. * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd');
  309. * </code>
  310. *
  311. * @param string $root name of the root tag
  312. * @param string $uri uri of the doctype definition
  313. * (or array with uri and public id)
  314. * @param string $internalDtd internal dtd entries
  315. *
  316. * @return string doctype declaration
  317. * @since 0.2
  318. */
  319. public static function getDocTypeDeclaration(
  320. $root, $uri = null, $internalDtd = null
  321. ) {
  322. if (is_array($uri)) {
  323. $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']);
  324. } elseif (!empty($uri)) {
  325. $ref = sprintf(' SYSTEM "%s"', $uri);
  326. } else {
  327. $ref = '';
  328. }
  329. if (empty($internalDtd)) {
  330. return sprintf('<!DOCTYPE %s%s>', $root, $ref);
  331. } else {
  332. return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
  333. }
  334. }
  335. /**
  336. * Create string representation of an attribute list
  337. *
  338. * <code>
  339. * require_once 'XML/Util.php';
  340. *
  341. * // build an attribute string
  342. * $att = array(
  343. * 'foo' => 'bar',
  344. * 'argh' => 'tomato'
  345. * );
  346. *
  347. * $attList = XML_Util::attributesToString($att);
  348. * </code>
  349. *
  350. * @param array $attributes attribute array
  351. * @param bool|array $sort sort attribute list alphabetically,
  352. * may also be an assoc array containing
  353. * the keys 'sort', 'multiline', 'indent',
  354. * 'linebreak' and 'entities'
  355. * @param bool $multiline use linebreaks, if more than
  356. * one attribute is given
  357. * @param string $indent string used for indentation of
  358. * multiline attributes
  359. * @param string $linebreak string used for linebreaks of
  360. * multiline attributes
  361. * @param int $entities setting for entities in attribute values
  362. * (one of XML_UTIL_ENTITIES_NONE,
  363. * XML_UTIL_ENTITIES_XML,
  364. * XML_UTIL_ENTITIES_XML_REQUIRED,
  365. * XML_UTIL_ENTITIES_HTML)
  366. *
  367. * @return string string representation of the attributes
  368. * @uses replaceEntities() to replace XML entities in attribute values
  369. * @todo allow sort also to be an options array
  370. */
  371. public static function attributesToString(
  372. $attributes, $sort = true, $multiline = false,
  373. $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML
  374. ) {
  375. /*
  376. * second parameter may be an array
  377. */
  378. if (is_array($sort)) {
  379. if (isset($sort['multiline'])) {
  380. $multiline = $sort['multiline'];
  381. }
  382. if (isset($sort['indent'])) {
  383. $indent = $sort['indent'];
  384. }
  385. if (isset($sort['linebreak'])) {
  386. $multiline = $sort['linebreak'];
  387. }
  388. if (isset($sort['entities'])) {
  389. $entities = $sort['entities'];
  390. }
  391. if (isset($sort['sort'])) {
  392. $sort = $sort['sort'];
  393. } else {
  394. $sort = true;
  395. }
  396. }
  397. $string = '';
  398. if (is_array($attributes) && !empty($attributes)) {
  399. if ($sort) {
  400. ksort($attributes);
  401. }
  402. if (!$multiline || count($attributes) == 1) {
  403. foreach ($attributes as $key => $value) {
  404. if ($entities != XML_UTIL_ENTITIES_NONE) {
  405. if ($entities === XML_UTIL_CDATA_SECTION) {
  406. $entities = XML_UTIL_ENTITIES_XML;
  407. }
  408. $value = XML_Util::replaceEntities($value, $entities);
  409. }
  410. $string .= ' ' . $key . '="' . $value . '"';
  411. }
  412. } else {
  413. $first = true;
  414. foreach ($attributes as $key => $value) {
  415. if ($entities != XML_UTIL_ENTITIES_NONE) {
  416. $value = XML_Util::replaceEntities($value, $entities);
  417. }
  418. if ($first) {
  419. $string .= ' ' . $key . '="' . $value . '"';
  420. $first = false;
  421. } else {
  422. $string .= $linebreak . $indent . $key . '="' . $value . '"';
  423. }
  424. }
  425. }
  426. }
  427. return $string;
  428. }
  429. /**
  430. * Collapses empty tags.
  431. *
  432. * @param string $xml XML
  433. * @param int $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL)
  434. * or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
  435. *
  436. * @return string XML
  437. */
  438. public static function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL)
  439. {
  440. if (preg_match('~<([^>])+/>~s', $xml, $matches)) {
  441. // it's already an empty tag
  442. return $xml;
  443. }
  444. switch ($mode) {
  445. case XML_UTIL_COLLAPSE_ALL:
  446. $preg1 =
  447. '~<' .
  448. '(?:' .
  449. '(https?://[^:\s]+:\w+)' . // <http://foo.com:bar ($1)
  450. '|(\w+:\w+)' . // <foo:bar ($2)
  451. '|(\w+)' . // <foo ($3)
  452. ')+' .
  453. '([^>]*)' . // attributes ($4)
  454. '>' .
  455. '<\/(\1|\2|\3)>' . // 1, 2, or 3 again ($5)
  456. '~s'
  457. ;
  458. $preg2 =
  459. '<' .
  460. '${1}${2}${3}' . // tag (only one should have been populated)
  461. '${4}' . // attributes
  462. ' />'
  463. ;
  464. return (preg_replace($preg1, $preg2, $xml)?:$xml);
  465. break;
  466. case XML_UTIL_COLLAPSE_XHTML_ONLY:
  467. return (
  468. preg_replace(
  469. '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|'
  470. . 'param)([^>]*)><\/\\1>/s',
  471. '<\\1\\2 />',
  472. $xml
  473. ) ?: $xml
  474. );
  475. break;
  476. case XML_UTIL_COLLAPSE_NONE:
  477. // fall thru
  478. default:
  479. return $xml;
  480. }
  481. }
  482. /**
  483. * Create a tag
  484. *
  485. * This method will call XML_Util::createTagFromArray(), which
  486. * is more flexible.
  487. *
  488. * <code>
  489. * require_once 'XML/Util.php';
  490. *
  491. * // create an XML tag:
  492. * $tag = XML_Util::createTag('myNs:myTag',
  493. * array('foo' => 'bar'),
  494. * 'This is inside the tag',
  495. * 'http://www.w3c.org/myNs#');
  496. * </code>
  497. *
  498. * @param string $qname qualified tagname (including namespace)
  499. * @param array $attributes array containg attributes
  500. * @param mixed $content the content
  501. * @param string $namespaceUri URI of the namespace
  502. * @param int $replaceEntities whether to replace XML special chars in
  503. * content, embedd it in a CData section
  504. * or none of both
  505. * @param bool $multiline whether to create a multiline tag where
  506. * each attribute gets written to a single line
  507. * @param string $indent string used to indent attributes
  508. * (_auto indents attributes so they start
  509. * at the same column)
  510. * @param string $linebreak string used for linebreaks
  511. * @param bool $sortAttributes Whether to sort the attributes or not
  512. * @param int $collapseTagMode How to handle a content-less, and thus collapseable, tag
  513. *
  514. * @return string XML tag
  515. * @see createTagFromArray()
  516. * @uses createTagFromArray() to create the tag
  517. */
  518. public static function createTag(
  519. $qname, $attributes = array(), $content = null,
  520. $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
  521. $multiline = false, $indent = '_auto', $linebreak = "\n",
  522. $sortAttributes = true, $collapseTagMode = XML_UTIL_COLLAPSE_ALL
  523. ) {
  524. $tag = array(
  525. 'qname' => $qname,
  526. 'attributes' => $attributes
  527. );
  528. // add tag content
  529. if ($content !== null) {
  530. $tag['content'] = $content;
  531. }
  532. // add namespace Uri
  533. if ($namespaceUri !== null) {
  534. $tag['namespaceUri'] = $namespaceUri;
  535. }
  536. return XML_Util::createTagFromArray(
  537. $tag, $replaceEntities, $multiline,
  538. $indent, $linebreak, $sortAttributes,
  539. $collapseTagMode
  540. );
  541. }
  542. /**
  543. * Create a tag from an array.
  544. * This method awaits an array in the following format
  545. * <pre>
  546. * array(
  547. * // qualified name of the tag
  548. * 'qname' => $qname
  549. *
  550. * // namespace prefix (optional, if qname is specified or no namespace)
  551. * 'namespace' => $namespace
  552. *
  553. * // local part of the tagname (optional, if qname is specified)
  554. * 'localpart' => $localpart,
  555. *
  556. * // array containing all attributes (optional)
  557. * 'attributes' => array(),
  558. *
  559. * // tag content (optional)
  560. * 'content' => $content,
  561. *
  562. * // namespaceUri for the given namespace (optional)
  563. * 'namespaceUri' => $namespaceUri
  564. * )
  565. * </pre>
  566. *
  567. * <code>
  568. * require_once 'XML/Util.php';
  569. *
  570. * $tag = array(
  571. * 'qname' => 'foo:bar',
  572. * 'namespaceUri' => 'http://foo.com',
  573. * 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'),
  574. * 'content' => 'I\'m inside the tag',
  575. * );
  576. * // creating a tag with qualified name and namespaceUri
  577. * $string = XML_Util::createTagFromArray($tag);
  578. * </code>
  579. *
  580. * @param array $tag tag definition
  581. * @param int $replaceEntities whether to replace XML special chars in
  582. * content, embedd it in a CData section
  583. * or none of both
  584. * @param bool $multiline whether to create a multiline tag where each
  585. * attribute gets written to a single line
  586. * @param string $indent string used to indent attributes
  587. * (_auto indents attributes so they start
  588. * at the same column)
  589. * @param string $linebreak string used for linebreaks
  590. * @param bool $sortAttributes Whether to sort the attributes or not
  591. * @param int $collapseTagMode How to handle a content-less, and thus collapseable, tag
  592. *
  593. * @return string XML tag
  594. *
  595. * @see createTag()
  596. * @uses attributesToString() to serialize the attributes of the tag
  597. * @uses splitQualifiedName() to get local part and namespace of a qualified name
  598. * @uses createCDataSection()
  599. * @uses collapseEmptyTags()
  600. * @uses raiseError()
  601. */
  602. public static function createTagFromArray(
  603. $tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
  604. $multiline = false, $indent = '_auto', $linebreak = "\n",
  605. $sortAttributes = true, $collapseTagMode = XML_UTIL_COLLAPSE_ALL
  606. ) {
  607. if (isset($tag['content']) && !is_scalar($tag['content'])) {
  608. return XML_Util::raiseError(
  609. 'Supplied non-scalar value as tag content',
  610. XML_UTIL_ERROR_NON_SCALAR_CONTENT
  611. );
  612. }
  613. if (!isset($tag['qname']) && !isset($tag['localPart'])) {
  614. return XML_Util::raiseError(
  615. 'You must either supply a qualified name '
  616. . '(qname) or local tag name (localPart).',
  617. XML_UTIL_ERROR_NO_TAG_NAME
  618. );
  619. }
  620. // if no attributes hav been set, use empty attributes
  621. if (!isset($tag['attributes']) || !is_array($tag['attributes'])) {
  622. $tag['attributes'] = array();
  623. }
  624. if (isset($tag['namespaces'])) {
  625. foreach ($tag['namespaces'] as $ns => $uri) {
  626. $tag['attributes']['xmlns:' . $ns] = $uri;
  627. }
  628. }
  629. if (!isset($tag['qname'])) {
  630. // qualified name is not given
  631. // check for namespace
  632. if (isset($tag['namespace']) && !empty($tag['namespace'])) {
  633. $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart'];
  634. } else {
  635. $tag['qname'] = $tag['localPart'];
  636. }
  637. } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) {
  638. // namespace URI is set, but no namespace
  639. $parts = XML_Util::splitQualifiedName($tag['qname']);
  640. $tag['localPart'] = $parts['localPart'];
  641. if (isset($parts['namespace'])) {
  642. $tag['namespace'] = $parts['namespace'];
  643. }
  644. }
  645. if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) {
  646. // is a namespace given
  647. if (isset($tag['namespace']) && !empty($tag['namespace'])) {
  648. $tag['attributes']['xmlns:' . $tag['namespace']]
  649. = $tag['namespaceUri'];
  650. } else {
  651. // define this Uri as the default namespace
  652. $tag['attributes']['xmlns'] = $tag['namespaceUri'];
  653. }
  654. }
  655. if (!array_key_exists('content', $tag)) {
  656. $tag['content'] = '';
  657. }
  658. // check for multiline attributes
  659. if ($multiline === true) {
  660. if ($indent === '_auto') {
  661. $indent = str_repeat(' ', (strlen($tag['qname'])+2));
  662. }
  663. }
  664. // create attribute list
  665. $attList = XML_Util::attributesToString(
  666. $tag['attributes'],
  667. $sortAttributes, $multiline, $indent, $linebreak
  668. );
  669. switch ($replaceEntities) {
  670. case XML_UTIL_ENTITIES_NONE:
  671. break;
  672. case XML_UTIL_CDATA_SECTION:
  673. $tag['content'] = XML_Util::createCDataSection($tag['content']);
  674. break;
  675. default:
  676. $tag['content'] = XML_Util::replaceEntities(
  677. $tag['content'], $replaceEntities
  678. );
  679. break;
  680. }
  681. $tag = sprintf(
  682. '<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'],
  683. $tag['qname']
  684. );
  685. return self::collapseEmptyTags($tag, $collapseTagMode);
  686. }
  687. /**
  688. * Create a start element
  689. *
  690. * <code>
  691. * require_once 'XML/Util.php';
  692. *
  693. * // create an XML start element:
  694. * $tag = XML_Util::createStartElement('myNs:myTag',
  695. * array('foo' => 'bar') ,'http://www.w3c.org/myNs#');
  696. * </code>
  697. *
  698. * @param string $qname qualified tagname (including namespace)
  699. * @param array $attributes array containg attributes
  700. * @param string $namespaceUri URI of the namespace
  701. * @param bool $multiline whether to create a multiline tag where each
  702. * attribute gets written to a single line
  703. * @param string $indent string used to indent attributes (_auto indents
  704. * attributes so they start at the same column)
  705. * @param string $linebreak string used for linebreaks
  706. * @param bool $sortAttributes Whether to sort the attributes or not
  707. *
  708. * @return string XML start element
  709. * @see createEndElement(), createTag()
  710. */
  711. public static function createStartElement(
  712. $qname, $attributes = array(), $namespaceUri = null,
  713. $multiline = false, $indent = '_auto', $linebreak = "\n",
  714. $sortAttributes = true
  715. ) {
  716. // if no attributes hav been set, use empty attributes
  717. if (!isset($attributes) || !is_array($attributes)) {
  718. $attributes = array();
  719. }
  720. if ($namespaceUri != null) {
  721. $parts = XML_Util::splitQualifiedName($qname);
  722. }
  723. // check for multiline attributes
  724. if ($multiline === true) {
  725. if ($indent === '_auto') {
  726. $indent = str_repeat(' ', (strlen($qname)+2));
  727. }
  728. }
  729. if ($namespaceUri != null) {
  730. // is a namespace given
  731. if (isset($parts['namespace']) && !empty($parts['namespace'])) {
  732. $attributes['xmlns:' . $parts['namespace']] = $namespaceUri;
  733. } else {
  734. // define this Uri as the default namespace
  735. $attributes['xmlns'] = $namespaceUri;
  736. }
  737. }
  738. // create attribute list
  739. $attList = XML_Util::attributesToString(
  740. $attributes, $sortAttributes,
  741. $multiline, $indent, $linebreak
  742. );
  743. $element = sprintf('<%s%s>', $qname, $attList);
  744. return $element;
  745. }
  746. /**
  747. * Create an end element
  748. *
  749. * <code>
  750. * require_once 'XML/Util.php';
  751. *
  752. * // create an XML start element:
  753. * $tag = XML_Util::createEndElement('myNs:myTag');
  754. * </code>
  755. *
  756. * @param string $qname qualified tagname (including namespace)
  757. *
  758. * @return string XML end element
  759. * @see createStartElement(), createTag()
  760. */
  761. public static function createEndElement($qname)
  762. {
  763. $element = sprintf('</%s>', $qname);
  764. return $element;
  765. }
  766. /**
  767. * Create an XML comment
  768. *
  769. * <code>
  770. * require_once 'XML/Util.php';
  771. *
  772. * // create an XML start element:
  773. * $tag = XML_Util::createComment('I am a comment');
  774. * </code>
  775. *
  776. * @param string $content content of the comment
  777. *
  778. * @return string XML comment
  779. */
  780. public static function createComment($content)
  781. {
  782. $comment = sprintf('<!-- %s -->', $content);
  783. return $comment;
  784. }
  785. /**
  786. * Create a CData section
  787. *
  788. * <code>
  789. * require_once 'XML/Util.php';
  790. *
  791. * // create a CData section
  792. * $tag = XML_Util::createCDataSection('I am content.');
  793. * </code>
  794. *
  795. * @param string $data data of the CData section
  796. *
  797. * @return string CData section with content
  798. */
  799. public static function createCDataSection($data)
  800. {
  801. return sprintf(
  802. '<![CDATA[%s]]>',
  803. preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data))
  804. );
  805. }
  806. /**
  807. * Split qualified name and return namespace and local part
  808. *
  809. * <code>
  810. * require_once 'XML/Util.php';
  811. *
  812. * // split qualified tag
  813. * $parts = XML_Util::splitQualifiedName('xslt:stylesheet');
  814. * </code>
  815. * the returned array will contain two elements:
  816. * <pre>
  817. * array(
  818. * 'namespace' => 'xslt',
  819. * 'localPart' => 'stylesheet'
  820. * );
  821. * </pre>
  822. *
  823. * @param string $qname qualified tag name
  824. * @param string $defaultNs default namespace (optional)
  825. *
  826. * @return array array containing namespace and local part
  827. */
  828. public static function splitQualifiedName($qname, $defaultNs = null)
  829. {
  830. if (strstr($qname, ':')) {
  831. $tmp = explode(':', $qname);
  832. return array(
  833. 'namespace' => $tmp[0],
  834. 'localPart' => $tmp[1]
  835. );
  836. }
  837. return array(
  838. 'namespace' => $defaultNs,
  839. 'localPart' => $qname
  840. );
  841. }
  842. /**
  843. * Check, whether string is valid XML name
  844. *
  845. * <p>XML names are used for tagname, attribute names and various
  846. * other, lesser known entities.</p>
  847. * <p>An XML name may only consist of alphanumeric characters,
  848. * dashes, undescores and periods, and has to start with a letter
  849. * or an underscore.</p>
  850. *
  851. * <code>
  852. * require_once 'XML/Util.php';
  853. *
  854. * // verify tag name
  855. * $result = XML_Util::isValidName('invalidTag?');
  856. * if (is_a($result, 'PEAR_Error')) {
  857. * print 'Invalid XML name: ' . $result->getMessage();
  858. * }
  859. * </code>
  860. *
  861. * @param string $string string that should be checked
  862. *
  863. * @return mixed true, if string is a valid XML name, PEAR error otherwise
  864. *
  865. * @todo support for other charsets
  866. * @todo PEAR CS - unable to avoid 85-char limit on second preg_match
  867. */
  868. public static function isValidName($string)
  869. {
  870. // check for invalid chars
  871. if (!preg_match('/^[[:alpha:]_]\\z/', $string{0})) {
  872. return XML_Util::raiseError(
  873. 'XML names may only start with letter or underscore',
  874. XML_UTIL_ERROR_INVALID_START
  875. );
  876. }
  877. // check for invalid chars
  878. $match = preg_match(
  879. '/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?'
  880. . '[[:alpha:]_]([[:alnum:]\_\-\.]+)?\\z/',
  881. $string
  882. );
  883. if (!$match) {
  884. return XML_Util::raiseError(
  885. 'XML names may only contain alphanumeric '
  886. . 'chars, period, hyphen, colon and underscores',
  887. XML_UTIL_ERROR_INVALID_CHARS
  888. );
  889. }
  890. // XML name is valid
  891. return true;
  892. }
  893. /**
  894. * Replacement for XML_Util::raiseError
  895. *
  896. * Avoids the necessity to always require
  897. * PEAR.php
  898. *
  899. * @param string $msg error message
  900. * @param int $code error code
  901. *
  902. * @return PEAR_Error
  903. * @todo PEAR CS - should this use include_once instead?
  904. */
  905. public static function raiseError($msg, $code)
  906. {
  907. include_once 'PEAR.php';
  908. return PEAR::raiseError($msg, $code);
  909. }
  910. }
  911. ?>