Util.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. <?PHP
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2002 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stephan Schmidt <schst@php-tools.net> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Util.php,v 1.15 2003/11/22 10:41:08 schst Exp $
  20. /**
  21. * uses PEAR errors
  22. */
  23. require_once 'PEAR.php';
  24. /**
  25. * error code for invalid chars in XML name
  26. */
  27. define("XML_UTIL_ERROR_INVALID_CHARS", 51);
  28. /**
  29. * error code for invalid chars in XML name
  30. */
  31. define("XML_UTIL_ERROR_INVALID_START", 52);
  32. /**
  33. * error code for non-scalar tag content
  34. */
  35. define("XML_UTIL_ERROR_NON_SCALAR_CONTENT", 60);
  36. /**
  37. * replace XML entities
  38. */
  39. define("XML_UTIL_REPLACE_ENTITIES", 1);
  40. /**
  41. * embedd content in a CData Section
  42. */
  43. define("XML_UTIL_CDATA_SECTION", 2);
  44. /**
  45. * utility class for working with XML documents
  46. *
  47. * @category XML
  48. * @package XML_Util
  49. * @version 0.5.2
  50. * @author Stephan Schmidt <schst@php.net>
  51. * @todo method to get doctype declaration
  52. */
  53. class XML_Util {
  54. /**
  55. * return API version
  56. *
  57. * @access public
  58. * @static
  59. * @return string $version API version
  60. */
  61. function apiVersion()
  62. {
  63. return "0.5.2";
  64. }
  65. /**
  66. * replace XML entities
  67. *
  68. * chars that have to be replaced are '&' and '<',
  69. * furthermore '>', ''' and '"' have entities that can be used.
  70. *
  71. * <code>
  72. * require_once 'XML/Util.php';
  73. *
  74. * // replace XML entites:
  75. * $string = XML_Util::replaceEntities("This string contains < & >.");
  76. * </code>
  77. *
  78. * @access public
  79. * @static
  80. * @param string $string string where XML special chars should be replaced
  81. * @return string $string string with replaced chars
  82. * @todo optional parameter to supply additional entities
  83. */
  84. function replaceEntities($string)
  85. {
  86. return strtr($string,array(
  87. '&' => '&amp;',
  88. '>' => '&gt;',
  89. '<' => '&lt;',
  90. '"' => '&quot;',
  91. '\'' => '&apos;' ));
  92. }
  93. /**
  94. * build an xml declaration
  95. *
  96. * <code>
  97. * require_once 'XML/Util.php';
  98. *
  99. * // get an XML declaration:
  100. * $xmlDecl = XML_Util::getXMLDeclaration("1.0", "UTF-8", true);
  101. * </code>
  102. *
  103. * @access public
  104. * @static
  105. * @param string $version xml version
  106. * @param string $encoding character encoding
  107. * @param boolean $standAlone document is standalone (or not)
  108. * @return string $decl xml declaration
  109. * @uses XML_Util::attributesToString() to serialize the attributes of the XML declaration
  110. */
  111. function getXMLDeclaration($version = "1.0", $encoding = null, $standalone = null)
  112. {
  113. $attributes = array(
  114. "version" => $version,
  115. );
  116. // add encoding
  117. if ($encoding !== null) {
  118. $attributes["encoding"] = $encoding;
  119. }
  120. // add standalone, if specified
  121. if ($standalone !== null) {
  122. $attributes["standalone"] = $standalone ? "yes" : "no";
  123. }
  124. return sprintf("<?xml%s?>", XML_Util::attributesToString($attributes, false));
  125. }
  126. /**
  127. * build a document type declaration
  128. *
  129. * <code>
  130. * require_once 'XML/Util.php';
  131. *
  132. * // get a doctype declaration:
  133. * $xmlDecl = XML_Util::getDocTypeDeclaration("rootTag","myDocType.dtd");
  134. * </code>
  135. *
  136. * @access public
  137. * @static
  138. * @param string $root name of the root tag
  139. * @param string $uri uri of the doctype definition (or array with uri and public id)
  140. * @param string $internalDtd internal dtd entries
  141. * @return string $decl doctype declaration
  142. * @since 0.2
  143. */
  144. function getDocTypeDeclaration($root, $uri = null, $internalDtd = null)
  145. {
  146. if (is_array($uri)) {
  147. $ref = sprintf( ' PUBLIC "%s" "%s"', $uri["id"], $uri["uri"] );
  148. } elseif (!empty($uri)) {
  149. $ref = sprintf( ' SYSTEM "%s"', $uri );
  150. } else {
  151. $ref = "";
  152. }
  153. if (empty($internalDtd)) {
  154. return sprintf("<!DOCTYPE %s%s>", $root, $ref);
  155. } else {
  156. return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
  157. }
  158. }
  159. /**
  160. * create string representation of an attribute list
  161. *
  162. * <code>
  163. * require_once 'XML/Util.php';
  164. *
  165. * // build an attribute string
  166. * $att = array(
  167. * "foo" => "bar",
  168. * "argh" => "tomato"
  169. * );
  170. *
  171. * $attList = XML_Util::attributesToString($att);
  172. * </code>
  173. *
  174. * @access public
  175. * @static
  176. * @param array $attributes attribute array
  177. * @param boolean $sort sort attribute list alphabetically
  178. * @param boolean $multiline use linebreaks, if more than one attribute is given
  179. * @param string $indent string used for indentation of multiline attributes
  180. * @param string $linebreak string used for linebreaks of multiline attributes
  181. * @return string $string string representation of the attributes
  182. * @uses XML_Util::replaceEntities() to replace XML entities in attribute values
  183. */
  184. function attributesToString($attributes, $sort = true, $multiline = false, $indent = ' ', $linebreak = "\n")
  185. {
  186. $string = "";
  187. if (is_array($attributes) && !empty($attributes)) {
  188. if ($sort) {
  189. ksort($attributes);
  190. }
  191. if( !$multiline || count($attributes) == 1) {
  192. foreach ($attributes as $key => $value) {
  193. $string .= " ".$key.'="'.XML_Util::replaceEntities($value).'"';
  194. }
  195. } else {
  196. $first = true;
  197. foreach ($attributes as $key => $value) {
  198. if ($first) {
  199. $string .= " ".$key.'="'.XML_Util::replaceEntities($value).'"';
  200. $first = false;
  201. } else {
  202. $string .= $linebreak.$indent.$key.'="'.XML_Util::replaceEntities($value).'"';
  203. }
  204. }
  205. }
  206. }
  207. return $string;
  208. }
  209. /**
  210. * create a tag
  211. *
  212. * This method will call XML_Util::createTagFromArray(), which
  213. * is more flexible.
  214. *
  215. * <code>
  216. * require_once 'XML/Util.php';
  217. *
  218. * // create an XML tag:
  219. * $tag = XML_Util::createTag("myNs:myTag", array("foo" => "bar"), "This is inside the tag", "http://www.w3c.org/myNs#");
  220. * </code>
  221. *
  222. * @access public
  223. * @static
  224. * @param string $qname qualified tagname (including namespace)
  225. * @param array $attributes array containg attributes
  226. * @param mixed $content
  227. * @param string $namespaceUri URI of the namespace
  228. * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both
  229. * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line
  230. * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column)
  231. * @param string $linebreak string used for linebreaks
  232. * @return string $string XML tag
  233. * @see XML_Util::createTagFromArray()
  234. * @uses XML_Util::createTagFromArray() to create the tag
  235. */
  236. function createTag($qname, $attributes = array(), $content = null, $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n")
  237. {
  238. $tag = array(
  239. "qname" => $qname,
  240. "attributes" => $attributes
  241. );
  242. // add tag content
  243. if ($content !== null) {
  244. $tag["content"] = $content;
  245. }
  246. // add namespace Uri
  247. if ($namespaceUri !== null) {
  248. $tag["namespaceUri"] = $namespaceUri;
  249. }
  250. return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $linebreak);
  251. }
  252. /**
  253. * create a tag from an array
  254. * this method awaits an array in the following format
  255. * <pre>
  256. * array(
  257. * "qname" => $qname // qualified name of the tag
  258. * "namespace" => $namespace // namespace prefix (optional, if qname is specified or no namespace)
  259. * "localpart" => $localpart, // local part of the tagname (optional, if qname is specified)
  260. * "attributes" => array(), // array containing all attributes (optional)
  261. * "content" => $content, // tag content (optional)
  262. * "namespaceUri" => $namespaceUri // namespaceUri for the given namespace (optional)
  263. * )
  264. * </pre>
  265. *
  266. * <code>
  267. * require_once 'XML/Util.php';
  268. *
  269. * $tag = array(
  270. * "qname" => "foo:bar",
  271. * "namespaceUri" => "http://foo.com",
  272. * "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ),
  273. * "content" => "I'm inside the tag",
  274. * );
  275. * // creating a tag with qualified name and namespaceUri
  276. * $string = XML_Util::createTagFromArray($tag);
  277. * </code>
  278. *
  279. * @access public
  280. * @static
  281. * @param array $tag tag definition
  282. * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both
  283. * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line
  284. * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column)
  285. * @param string $linebreak string used for linebreaks
  286. * @return string $string XML tag
  287. * @see XML_Util::createTag()
  288. * @uses XML_Util::attributesToString() to serialize the attributes of the tag
  289. * @uses XML_Util::splitQualifiedName() to get local part and namespace of a qualified name
  290. */
  291. function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n" )
  292. {
  293. if (isset($tag["content"]) && !is_scalar($tag["content"])) {
  294. return PEAR::raiseError( "Supplied non-scalar value as tag content", XML_UTIL_ERROR_NON_SCALAR_CONTENT );
  295. }
  296. // if no attributes hav been set, use empty attributes
  297. if (!isset($tag["attributes"]) || !is_array($tag["attributes"])) {
  298. $tag["attributes"] = array();
  299. }
  300. // qualified name is not given
  301. if (!isset($tag["qname"])) {
  302. // check for namespace
  303. if (isset($tag["namespace"]) && !empty($tag["namespace"])) {
  304. $tag["qname"] = $tag["namespace"].":".$tag["localPart"];
  305. } else {
  306. $tag["qname"] = $tag["localPart"];
  307. }
  308. // namespace URI is set, but no namespace
  309. } elseif (isset($tag["namespaceUri"]) && !isset($tag["namespace"])) {
  310. $parts = XML_Util::splitQualifiedName($tag["qname"]);
  311. $tag["localPart"] = $parts["localPart"];
  312. if (isset($parts["namespace"])) {
  313. $tag["namespace"] = $parts["namespace"];
  314. }
  315. }
  316. if (isset($tag["namespaceUri"]) && !empty($tag["namespaceUri"])) {
  317. // is a namespace given
  318. if (isset($tag["namespace"]) && !empty($tag["namespace"])) {
  319. $tag["attributes"]["xmlns:".$tag["namespace"]] = $tag["namespaceUri"];
  320. } else {
  321. // define this Uri as the default namespace
  322. $tag["attributes"]["xmlns"] = $tag["namespaceUri"];
  323. }
  324. }
  325. // check for multiline attributes
  326. if ($multiline === true) {
  327. if ($indent === "_auto") {
  328. $indent = str_repeat(" ", (strlen($tag["qname"])+2));
  329. }
  330. }
  331. // create attribute list
  332. $attList = XML_Util::attributesToString($tag["attributes"], true, $multiline, $indent, $linebreak );
  333. if (!isset($tag["content"]) || (string)$tag["content"] == '') {
  334. $tag = sprintf("<%s%s />", $tag["qname"], $attList);
  335. } else {
  336. if ($replaceEntities == XML_UTIL_REPLACE_ENTITIES) {
  337. $tag["content"] = XML_Util::replaceEntities($tag["content"]);
  338. } elseif ($replaceEntities == XML_UTIL_CDATA_SECTION) {
  339. $tag["content"] = XML_Util::createCDataSection($tag["content"]);
  340. }
  341. $tag = sprintf("<%s%s>%s</%s>", $tag["qname"], $attList, $tag["content"], $tag["qname"] );
  342. }
  343. return $tag;
  344. }
  345. /**
  346. * create a start element
  347. *
  348. * <code>
  349. * require_once 'XML/Util.php';
  350. *
  351. * // create an XML start element:
  352. * $tag = XML_Util::createStartElement("myNs:myTag", array("foo" => "bar") ,"http://www.w3c.org/myNs#");
  353. * </code>
  354. *
  355. * @access public
  356. * @static
  357. * @param string $qname qualified tagname (including namespace)
  358. * @param array $attributes array containg attributes
  359. * @param string $namespaceUri URI of the namespace
  360. * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line
  361. * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column)
  362. * @param string $linebreak string used for linebreaks
  363. * @return string $string XML start element
  364. * @see XML_Util::createEndElement(), XML_Util::createTag()
  365. */
  366. function createStartElement($qname, $attributes = array(), $namespaceUri = null, $multiline = false, $indent = '_auto', $linebreak = "\n")
  367. {
  368. // if no attributes hav been set, use empty attributes
  369. if (!isset($attributes) || !is_array($attributes)) {
  370. $attributes = array();
  371. }
  372. if ($namespaceUri != null) {
  373. $parts = XML_Util::splitQualifiedName($qname);
  374. }
  375. // check for multiline attributes
  376. if ($multiline === true) {
  377. if ($indent === "_auto") {
  378. $indent = str_repeat(" ", (strlen($qname)+2));
  379. }
  380. }
  381. if ($namespaceUri != null) {
  382. // is a namespace given
  383. if (isset($parts["namespace"]) && !empty($parts["namespace"])) {
  384. $attributes["xmlns:".$parts["namespace"]] = $namespaceUri;
  385. } else {
  386. // define this Uri as the default namespace
  387. $attributes["xmlns"] = $namespaceUri;
  388. }
  389. }
  390. // create attribute list
  391. $attList = XML_Util::attributesToString($attributes, true, $multiline, $indent, $linebreak);
  392. $element = sprintf("<%s%s>", $qname, $attList);
  393. return $element;
  394. }
  395. /**
  396. * create an end element
  397. *
  398. * <code>
  399. * require_once 'XML/Util.php';
  400. *
  401. * // create an XML start element:
  402. * $tag = XML_Util::createEndElement("myNs:myTag");
  403. * </code>
  404. *
  405. * @access public
  406. * @static
  407. * @param string $qname qualified tagname (including namespace)
  408. * @return string $string XML end element
  409. * @see XML_Util::createStartElement(), XML_Util::createTag()
  410. */
  411. function createEndElement($qname)
  412. {
  413. $element = sprintf("</%s>", $qname);
  414. return $element;
  415. }
  416. /**
  417. * create an XML comment
  418. *
  419. * <code>
  420. * require_once 'XML/Util.php';
  421. *
  422. * // create an XML start element:
  423. * $tag = XML_Util::createComment("I am a comment");
  424. * </code>
  425. *
  426. * @access public
  427. * @static
  428. * @param string $content content of the comment
  429. * @return string $comment XML comment
  430. */
  431. function createComment($content)
  432. {
  433. $comment = sprintf("<!-- %s -->", $content);
  434. return $comment;
  435. }
  436. /**
  437. * create a CData section
  438. *
  439. * <code>
  440. * require_once 'XML/Util.php';
  441. *
  442. * // create a CData section
  443. * $tag = XML_Util::createCDataSection("I am content.");
  444. * </code>
  445. *
  446. * @access public
  447. * @static
  448. * @param string $data data of the CData section
  449. * @return string $string CData section with content
  450. */
  451. function createCDataSection($data)
  452. {
  453. return sprintf("<![CDATA[%s]]>", $data);
  454. }
  455. /**
  456. * split qualified name and return namespace and local part
  457. *
  458. * <code>
  459. * require_once 'XML/Util.php';
  460. *
  461. * // split qualified tag
  462. * $parts = XML_Util::splitQualifiedName("xslt:stylesheet");
  463. * </code>
  464. * the returned array will contain two elements:
  465. * <pre>
  466. * array(
  467. * "namespace" => "xslt",
  468. * "localPart" => "stylesheet"
  469. * );
  470. * </pre>
  471. *
  472. * @access public
  473. * @static
  474. * @param string $qname qualified tag name
  475. * @param string $defaultNs default namespace (optional)
  476. * @return array $parts array containing namespace and local part
  477. */
  478. function splitQualifiedName($qname, $defaultNs = null)
  479. {
  480. if (strstr($qname, ':')) {
  481. $tmp = explode(":", $qname);
  482. return array(
  483. "namespace" => $tmp[0],
  484. "localPart" => $tmp[1]
  485. );
  486. }
  487. return array(
  488. "namespace" => $defaultNs,
  489. "localPart" => $qname
  490. );
  491. }
  492. /**
  493. * check, whether string is valid XML name
  494. *
  495. * <p>XML names are used for tagname, attribute names and various
  496. * other, lesser known entities.</p>
  497. * <p>An XML name may only consist of alphanumeric characters,
  498. * dashes, undescores and periods, and has to start with a letter
  499. * or an underscore.
  500. * </p>
  501. *
  502. * <code>
  503. * require_once 'XML/Util.php';
  504. *
  505. * // verify tag name
  506. * $result = XML_Util::isValidName("invalidTag?");
  507. * if (XML_Util::isError($result)) {
  508. * print "Invalid XML name: " . $result->getMessage();
  509. * }
  510. * </code>
  511. *
  512. * @access public
  513. * @static
  514. * @param string $string string that should be checked
  515. * @return mixed $valid true, if string is a valid XML name, PEAR error otherwise
  516. * @todo support for other charsets
  517. */
  518. function isValidName($string)
  519. {
  520. // check for invalid chars
  521. if (!preg_match("/^[[:alnum:]_\-.]+$/", $string)) {
  522. return PEAR::raiseError( "XML name may only contain alphanumeric chars, period, hyphen and underscore", XML_UTIL_ERROR_INVALID_CHARS );
  523. }
  524. // check for invalid starting character
  525. if (!preg_match("/[[:alpha:]_]/", $string{0})) {
  526. return PEAR::raiseError( "XML name may only start with letter or underscore", XML_UTIL_ERROR_INVALID_START );
  527. }
  528. // XML name is valid
  529. return true;
  530. }
  531. }
  532. ?>