XmlTest.php 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Utility;
  16. use Cake\Collection\Collection;
  17. use Cake\Core\Configure;
  18. use Cake\ORM\Entity;
  19. use Cake\TestSuite\TestCase;
  20. use Cake\Utility\Exception\XmlException;
  21. use Cake\Utility\Xml;
  22. /**
  23. * XmlTest class
  24. */
  25. class XmlTest extends TestCase
  26. {
  27. /**
  28. * autoFixtures property
  29. *
  30. * @var bool
  31. */
  32. public $autoFixtures = false;
  33. /**
  34. * fixtures property
  35. * @var array
  36. */
  37. public $fixtures = [
  38. 'core.articles', 'core.users'
  39. ];
  40. /**
  41. * setUp method
  42. *
  43. * @return void
  44. */
  45. public function setUp()
  46. {
  47. parent::setUp();
  48. $this->_appEncoding = Configure::read('App.encoding');
  49. Configure::write('App.encoding', 'UTF-8');
  50. }
  51. /**
  52. * tearDown method
  53. *
  54. * @return void
  55. */
  56. public function tearDown()
  57. {
  58. parent::tearDown();
  59. Configure::write('App.encoding', $this->_appEncoding);
  60. }
  61. public function testExceptionChainingForInvalidInput()
  62. {
  63. try {
  64. $value = "invalid-xml-input<<";
  65. Xml::build($value);
  66. $this->fail('This line should not be executed because of exception above.');
  67. } catch (XmlException $exception) {
  68. $cause = $exception->getPrevious();
  69. $this->assertNotNull($cause);
  70. $this->assertInstanceOf(\Exception::class, $cause);
  71. }
  72. }
  73. /**
  74. * testBuild method
  75. *
  76. * @return void
  77. */
  78. public function testBuild()
  79. {
  80. $xml = '<tag>value</tag>';
  81. $obj = Xml::build($xml);
  82. $this->assertInstanceOf(\SimpleXMLElement::class, $obj);
  83. $this->assertEquals('tag', (string)$obj->getName());
  84. $this->assertEquals('value', (string)$obj);
  85. $xml = '<?xml version="1.0" encoding="UTF-8"?><tag>value</tag>';
  86. $this->assertEquals($obj, Xml::build($xml));
  87. $obj = Xml::build($xml, ['return' => 'domdocument']);
  88. $this->assertInstanceOf(\DOMDocument::class, $obj);
  89. $this->assertEquals('tag', $obj->firstChild->nodeName);
  90. $this->assertEquals('value', $obj->firstChild->nodeValue);
  91. $xml = CORE_TESTS . 'Fixture/sample.xml';
  92. $obj = Xml::build($xml);
  93. $this->assertEquals('tags', $obj->getName());
  94. $this->assertEquals(2, count($obj));
  95. $this->assertEquals(Xml::build($xml), Xml::build(file_get_contents($xml)));
  96. $obj = Xml::build($xml, ['return' => 'domdocument']);
  97. $this->assertEquals('tags', $obj->firstChild->nodeName);
  98. $this->assertEquals(
  99. Xml::build($xml, ['return' => 'domdocument']),
  100. Xml::build(file_get_contents($xml), ['return' => 'domdocument'])
  101. );
  102. $xml = ['tag' => 'value'];
  103. $obj = Xml::build($xml);
  104. $this->assertEquals('tag', $obj->getName());
  105. $this->assertEquals('value', (string)$obj);
  106. $obj = Xml::build($xml, ['return' => 'domdocument']);
  107. $this->assertEquals('tag', $obj->firstChild->nodeName);
  108. $this->assertEquals('value', $obj->firstChild->nodeValue);
  109. $obj = Xml::build($xml, ['return' => 'domdocument', 'encoding' => null]);
  110. $this->assertNotRegExp('/encoding/', $obj->saveXML());
  111. }
  112. /**
  113. * test build() method with huge option
  114. *
  115. * @return void
  116. */
  117. public function testBuildHuge()
  118. {
  119. $xml = '<tag>value</tag>';
  120. $obj = Xml::build($xml, ['parseHuge' => true]);
  121. $this->assertEquals('tag', $obj->getName());
  122. $this->assertEquals('value', (string)$obj);
  123. }
  124. /**
  125. * Test that the readFile option disables local file parsing.
  126. *
  127. * @return void
  128. */
  129. public function testBuildFromFileWhenDisabled()
  130. {
  131. $this->expectException(\Cake\Utility\Exception\XmlException::class);
  132. $xml = CORE_TESTS . 'Fixture/sample.xml';
  133. $obj = Xml::build($xml, ['readFile' => false]);
  134. }
  135. /**
  136. * Test build() with a Collection instance.
  137. *
  138. * @return void
  139. */
  140. public function testBuildCollection()
  141. {
  142. $xml = new Collection(['tag' => 'value']);
  143. $obj = Xml::build($xml);
  144. $this->assertEquals('tag', $obj->getName());
  145. $this->assertEquals('value', (string)$obj);
  146. $xml = new Collection([
  147. 'response' => [
  148. 'users' => new Collection(['leonardo', 'raphael'])
  149. ]
  150. ]);
  151. $obj = Xml::build($xml);
  152. $this->assertContains('<users>leonardo</users>', $obj->saveXML());
  153. }
  154. /**
  155. * Test build() with ORM\Entity instances wrapped in a Collection.
  156. *
  157. * @return void
  158. */
  159. public function testBuildOrmEntity()
  160. {
  161. $user = new Entity(['username' => 'mark', 'email' => 'mark@example.com']);
  162. $xml = new Collection([
  163. 'response' => [
  164. 'users' => new Collection([$user])
  165. ]
  166. ]);
  167. $obj = Xml::build($xml);
  168. $output = $obj->saveXML();
  169. $this->assertContains('<username>mark</username>', $output);
  170. $this->assertContains('<email>mark@example.com</email>', $output);
  171. }
  172. /**
  173. * data provider function for testBuildInvalidData
  174. *
  175. * @return array
  176. */
  177. public static function invalidDataProvider()
  178. {
  179. return [
  180. [null],
  181. [false],
  182. [''],
  183. ['http://localhost/notthere.xml'],
  184. ];
  185. }
  186. /**
  187. * testBuildInvalidData
  188. *
  189. * @dataProvider invalidDataProvider
  190. * @return void
  191. */
  192. public function testBuildInvalidData($value)
  193. {
  194. $this->expectException(\RuntimeException::class);
  195. Xml::build($value);
  196. }
  197. /**
  198. * Test that building SimpleXmlElement with invalid XML causes the right exception.
  199. *
  200. * @return void
  201. */
  202. public function testBuildInvalidDataSimpleXml()
  203. {
  204. $this->expectException(\Cake\Utility\Exception\XmlException::class);
  205. $input = '<derp';
  206. Xml::build($input, ['return' => 'simplexml']);
  207. }
  208. /**
  209. * test build with a single empty tag
  210. *
  211. * @return void
  212. */
  213. public function testBuildEmptyTag()
  214. {
  215. try {
  216. Xml::build('<tag>');
  217. $this->fail('No exception');
  218. } catch (\Exception $e) {
  219. $this->assertTrue(true, 'An exception was raised');
  220. }
  221. }
  222. /**
  223. * testFromArray method
  224. *
  225. * @return void
  226. */
  227. public function testFromArray()
  228. {
  229. $xml = ['tag' => 'value'];
  230. $obj = Xml::fromArray($xml);
  231. $this->assertEquals('tag', $obj->getName());
  232. $this->assertEquals('value', (string)$obj);
  233. $xml = ['tag' => null];
  234. $obj = Xml::fromArray($xml);
  235. $this->assertEquals('tag', $obj->getName());
  236. $this->assertEquals('', (string)$obj);
  237. $xml = ['tag' => ['@' => 'value']];
  238. $obj = Xml::fromArray($xml);
  239. $this->assertEquals('tag', $obj->getName());
  240. $this->assertEquals('value', (string)$obj);
  241. $xml = [
  242. 'tags' => [
  243. 'tag' => [
  244. [
  245. 'id' => '1',
  246. 'name' => 'defect'
  247. ],
  248. [
  249. 'id' => '2',
  250. 'name' => 'enhancement'
  251. ]
  252. ]
  253. ]
  254. ];
  255. $obj = Xml::fromArray($xml, 'attributes');
  256. $this->assertInstanceOf(\SimpleXMLElement::class, $obj);
  257. $this->assertEquals('tags', $obj->getName());
  258. $this->assertEquals(2, count($obj));
  259. $xmlText = <<<XML
  260. <?xml version="1.0" encoding="UTF-8"?>
  261. <tags>
  262. <tag id="1" name="defect"/>
  263. <tag id="2" name="enhancement"/>
  264. </tags>
  265. XML;
  266. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  267. $obj = Xml::fromArray($xml);
  268. $this->assertInstanceOf(\SimpleXMLElement::class, $obj);
  269. $this->assertEquals('tags', $obj->getName());
  270. $this->assertEquals(2, count($obj));
  271. $xmlText = <<<XML
  272. <?xml version="1.0" encoding="UTF-8"?>
  273. <tags>
  274. <tag>
  275. <id>1</id>
  276. <name>defect</name>
  277. </tag>
  278. <tag>
  279. <id>2</id>
  280. <name>enhancement</name>
  281. </tag>
  282. </tags>
  283. XML;
  284. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  285. $xml = [
  286. 'tags' => [
  287. ]
  288. ];
  289. $obj = Xml::fromArray($xml);
  290. $this->assertEquals('tags', $obj->getName());
  291. $this->assertEquals('', (string)$obj);
  292. $xml = [
  293. 'tags' => [
  294. 'bool' => true,
  295. 'int' => 1,
  296. 'float' => 10.2,
  297. 'string' => 'ok',
  298. 'null' => null,
  299. 'array' => []
  300. ]
  301. ];
  302. $obj = Xml::fromArray($xml, 'tags');
  303. $this->assertEquals(6, count($obj));
  304. $this->assertSame((string)$obj->bool, '1');
  305. $this->assertSame((string)$obj->int, '1');
  306. $this->assertSame((string)$obj->float, '10.2');
  307. $this->assertSame((string)$obj->string, 'ok');
  308. $this->assertSame((string)$obj->null, '');
  309. $this->assertSame((string)$obj->array, '');
  310. $xml = [
  311. 'tags' => [
  312. 'tag' => [
  313. [
  314. '@id' => '1',
  315. 'name' => 'defect'
  316. ],
  317. [
  318. '@id' => '2',
  319. 'name' => 'enhancement'
  320. ]
  321. ]
  322. ]
  323. ];
  324. $obj = Xml::fromArray($xml, 'tags');
  325. $xmlText = <<<XML
  326. <?xml version="1.0" encoding="UTF-8"?>
  327. <tags>
  328. <tag id="1">
  329. <name>defect</name>
  330. </tag>
  331. <tag id="2">
  332. <name>enhancement</name>
  333. </tag>
  334. </tags>
  335. XML;
  336. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  337. $xml = [
  338. 'tags' => [
  339. 'tag' => [
  340. [
  341. '@id' => '1',
  342. 'name' => 'defect',
  343. '@' => 'Tag 1'
  344. ],
  345. [
  346. '@id' => '2',
  347. 'name' => 'enhancement'
  348. ],
  349. ],
  350. '@' => 'All tags'
  351. ]
  352. ];
  353. $obj = Xml::fromArray($xml, 'tags');
  354. $xmlText = <<<XML
  355. <?xml version="1.0" encoding="UTF-8"?>
  356. <tags>All tags<tag id="1">Tag 1<name>defect</name></tag><tag id="2"><name>enhancement</name></tag></tags>
  357. XML;
  358. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  359. $xml = [
  360. 'tags' => [
  361. 'tag' => [
  362. 'id' => 1,
  363. '@' => 'defect'
  364. ]
  365. ]
  366. ];
  367. $obj = Xml::fromArray($xml, 'attributes');
  368. $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?><tags><tag id="1">defect</tag></tags>';
  369. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  370. }
  371. /**
  372. * Test fromArray() with zero values.
  373. *
  374. * @return void
  375. */
  376. public function testFromArrayZeroValue()
  377. {
  378. $xml = [
  379. 'tag' => [
  380. '@' => 0,
  381. '@test' => 'A test'
  382. ]
  383. ];
  384. $obj = Xml::fromArray($xml);
  385. $xmlText = <<<XML
  386. <?xml version="1.0" encoding="UTF-8"?>
  387. <tag test="A test">0</tag>
  388. XML;
  389. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  390. $xml = [
  391. 'tag' => ['0']
  392. ];
  393. $obj = Xml::fromArray($xml);
  394. $xmlText = <<<XML
  395. <?xml version="1.0" encoding="UTF-8"?>
  396. <tag>0</tag>
  397. XML;
  398. $this->assertXmlStringEqualsXmlString($xmlText, $obj->asXML());
  399. }
  400. /**
  401. * Test non-sequential keys in list types.
  402. *
  403. * @return void
  404. */
  405. public function testFromArrayNonSequentialKeys()
  406. {
  407. $xmlArray = [
  408. 'Event' => [
  409. [
  410. 'id' => '235',
  411. 'Attribute' => [
  412. 0 => [
  413. 'id' => '9646',
  414. ],
  415. 2 => [
  416. 'id' => '9647',
  417. ]
  418. ]
  419. ]
  420. ]
  421. ];
  422. $obj = Xml::fromArray($xmlArray);
  423. $expected = <<<XML
  424. <?xml version="1.0" encoding="UTF-8"?>
  425. <Event>
  426. <id>235</id>
  427. <Attribute>
  428. <id>9646</id>
  429. </Attribute>
  430. <Attribute>
  431. <id>9647</id>
  432. </Attribute>
  433. </Event>
  434. XML;
  435. $this->assertXmlStringEqualsXmlString($expected, $obj->asXML());
  436. }
  437. /**
  438. * testFromArrayPretty method
  439. *
  440. * @return void
  441. */
  442. public function testFromArrayPretty()
  443. {
  444. $xml = [
  445. 'tags' => [
  446. 'tag' => [
  447. [
  448. 'id' => '1',
  449. 'name' => 'defect'
  450. ],
  451. [
  452. 'id' => '2',
  453. 'name' => 'enhancement'
  454. ]
  455. ]
  456. ]
  457. ];
  458. $expected = <<<XML
  459. <?xml version="1.0" encoding="UTF-8"?>
  460. <tags><tag><id>1</id><name>defect</name></tag><tag><id>2</id><name>enhancement</name></tag></tags>
  461. XML;
  462. $xmlResponse = Xml::fromArray($xml, ['pretty' => false]);
  463. $this->assertTextEquals($expected, $xmlResponse->asXML());
  464. $expected = <<<XML
  465. <?xml version="1.0" encoding="UTF-8"?>
  466. <tags>
  467. <tag>
  468. <id>1</id>
  469. <name>defect</name>
  470. </tag>
  471. <tag>
  472. <id>2</id>
  473. <name>enhancement</name>
  474. </tag>
  475. </tags>
  476. XML;
  477. $xmlResponse = Xml::fromArray($xml, ['pretty' => true]);
  478. $this->assertTextEquals($expected, $xmlResponse->asXML());
  479. $xml = [
  480. 'tags' => [
  481. 'tag' => [
  482. [
  483. 'id' => '1',
  484. 'name' => 'defect'
  485. ],
  486. [
  487. 'id' => '2',
  488. 'name' => 'enhancement'
  489. ]
  490. ]
  491. ]
  492. ];
  493. $expected = <<<XML
  494. <?xml version="1.0" encoding="UTF-8"?>
  495. <tags><tag id="1" name="defect"/><tag id="2" name="enhancement"/></tags>
  496. XML;
  497. $xmlResponse = Xml::fromArray($xml, ['pretty' => false, 'format' => 'attributes']);
  498. $this->assertTextEquals($expected, $xmlResponse->asXML());
  499. $expected = <<<XML
  500. <?xml version="1.0" encoding="UTF-8"?>
  501. <tags>
  502. <tag id="1" name="defect"/>
  503. <tag id="2" name="enhancement"/>
  504. </tags>
  505. XML;
  506. $xmlResponse = Xml::fromArray($xml, ['pretty' => true, 'format' => 'attributes']);
  507. $this->assertTextEquals($expected, $xmlResponse->asXML());
  508. }
  509. /**
  510. * data provider for fromArray() failures
  511. *
  512. * @return array
  513. */
  514. public static function invalidArrayDataProvider()
  515. {
  516. return [
  517. [''],
  518. [null],
  519. [false],
  520. [[]],
  521. [['numeric key as root']],
  522. [['item1' => '', 'item2' => '']],
  523. [['items' => ['item1', 'item2']]],
  524. [[
  525. 'tags' => [
  526. 'tag' => [
  527. [
  528. [
  529. 'string'
  530. ]
  531. ]
  532. ]
  533. ]
  534. ]],
  535. [[
  536. 'tags' => [
  537. '@tag' => [
  538. [
  539. '@id' => '1',
  540. 'name' => 'defect'
  541. ],
  542. [
  543. '@id' => '2',
  544. 'name' => 'enhancement'
  545. ]
  546. ]
  547. ]
  548. ]],
  549. [new \DateTime()]
  550. ];
  551. }
  552. /**
  553. * testFromArrayFail method
  554. *
  555. * @dataProvider invalidArrayDataProvider
  556. * @return void
  557. */
  558. public function testFromArrayFail($value)
  559. {
  560. $this->expectException(\Exception::class);
  561. Xml::fromArray($value);
  562. }
  563. /**
  564. * Test that there are not unterminated errors when building xml
  565. *
  566. * @return void
  567. */
  568. public function testFromArrayUnterminatedError()
  569. {
  570. $data = [
  571. 'product_ID' => 'GENERT-DL',
  572. 'deeplink' => 'http://example.com/deep',
  573. 'image_URL' => 'http://example.com/image',
  574. 'thumbnail_image_URL' => 'http://example.com/thumb',
  575. 'brand' => 'Malte Lange & Co',
  576. 'availability' => 'in stock',
  577. 'authors' => [
  578. 'author' => ['Malte Lange & Co']
  579. ]
  580. ];
  581. $xml = Xml::fromArray(['products' => $data], 'tags');
  582. $expected = <<<XML
  583. <?xml version="1.0" encoding="UTF-8"?>
  584. <products>
  585. <product_ID>GENERT-DL</product_ID>
  586. <deeplink>http://example.com/deep</deeplink>
  587. <image_URL>http://example.com/image</image_URL>
  588. <thumbnail_image_URL>http://example.com/thumb</thumbnail_image_URL>
  589. <brand>Malte Lange &amp; Co</brand>
  590. <availability>in stock</availability>
  591. <authors>
  592. <author>Malte Lange &amp; Co</author>
  593. </authors>
  594. </products>
  595. XML;
  596. $this->assertXmlStringEqualsXmlString($expected, $xml->asXML());
  597. }
  598. /**
  599. * testToArray method
  600. *
  601. * @return void
  602. */
  603. public function testToArray()
  604. {
  605. $xml = '<tag>name</tag>';
  606. $obj = Xml::build($xml);
  607. $this->assertEquals(['tag' => 'name'], Xml::toArray($obj));
  608. $xml = CORE_TESTS . 'Fixture/sample.xml';
  609. $obj = Xml::build($xml);
  610. $expected = [
  611. 'tags' => [
  612. 'tag' => [
  613. [
  614. '@id' => '1',
  615. 'name' => 'defect'
  616. ],
  617. [
  618. '@id' => '2',
  619. 'name' => 'enhancement'
  620. ]
  621. ]
  622. ]
  623. ];
  624. $this->assertEquals($expected, Xml::toArray($obj));
  625. $array = [
  626. 'tags' => [
  627. 'tag' => [
  628. [
  629. 'id' => '1',
  630. 'name' => 'defect'
  631. ],
  632. [
  633. 'id' => '2',
  634. 'name' => 'enhancement'
  635. ]
  636. ]
  637. ]
  638. ];
  639. $this->assertEquals(Xml::toArray(Xml::fromArray($array, 'tags')), $array);
  640. $expected = [
  641. 'tags' => [
  642. 'tag' => [
  643. [
  644. '@id' => '1',
  645. '@name' => 'defect'
  646. ],
  647. [
  648. '@id' => '2',
  649. '@name' => 'enhancement'
  650. ]
  651. ]
  652. ]
  653. ];
  654. $this->assertEquals($expected, Xml::toArray(Xml::fromArray($array, 'attributes')));
  655. $this->assertEquals($expected, Xml::toArray(Xml::fromArray($array, ['return' => 'domdocument', 'format' => 'attributes'])));
  656. $this->assertEquals(Xml::toArray(Xml::fromArray($array)), $array);
  657. $this->assertEquals(Xml::toArray(Xml::fromArray($array, ['return' => 'domdocument'])), $array);
  658. $array = [
  659. 'tags' => [
  660. 'tag' => [
  661. 'id' => '1',
  662. 'posts' => [
  663. ['id' => '1'],
  664. ['id' => '2']
  665. ]
  666. ],
  667. 'tagOther' => [
  668. 'subtag' => [
  669. 'id' => '1'
  670. ]
  671. ]
  672. ]
  673. ];
  674. $expected = [
  675. 'tags' => [
  676. 'tag' => [
  677. '@id' => '1',
  678. 'posts' => [
  679. ['@id' => '1'],
  680. ['@id' => '2']
  681. ]
  682. ],
  683. 'tagOther' => [
  684. 'subtag' => [
  685. '@id' => '1'
  686. ]
  687. ]
  688. ]
  689. ];
  690. $this->assertEquals($expected, Xml::toArray(Xml::fromArray($array, 'attributes')));
  691. $this->assertEquals($expected, Xml::toArray(Xml::fromArray($array, ['format' => 'attributes', 'return' => 'domdocument'])));
  692. $xml = <<<XML
  693. <root>
  694. <tag id="1">defect</tag>
  695. </root>
  696. XML;
  697. $obj = Xml::build($xml);
  698. $expected = [
  699. 'root' => [
  700. 'tag' => [
  701. '@id' => 1,
  702. '@' => 'defect'
  703. ]
  704. ]
  705. ];
  706. $this->assertEquals($expected, Xml::toArray($obj));
  707. $xml = <<<XML
  708. <root>
  709. <table xmlns="http://www.w3.org/TR/html4/"><tr><td>Apples</td><td>Bananas</td></tr></table>
  710. <table xmlns="http://www.cakephp.org"><name>CakePHP</name><license>MIT</license></table>
  711. <table>The book is on the table.</table>
  712. </root>
  713. XML;
  714. $obj = Xml::build($xml);
  715. $expected = [
  716. 'root' => [
  717. 'table' => [
  718. ['tr' => ['td' => ['Apples', 'Bananas']]],
  719. ['name' => 'CakePHP', 'license' => 'MIT'],
  720. 'The book is on the table.'
  721. ]
  722. ]
  723. ];
  724. $this->assertEquals($expected, Xml::toArray($obj));
  725. $xml = <<<XML
  726. <root xmlns:cake="http://www.cakephp.org/">
  727. <tag>defect</tag>
  728. <cake:bug>1</cake:bug>
  729. </root>
  730. XML;
  731. $obj = Xml::build($xml);
  732. $expected = [
  733. 'root' => [
  734. 'tag' => 'defect',
  735. 'cake:bug' => 1
  736. ]
  737. ];
  738. $this->assertEquals($expected, Xml::toArray($obj));
  739. $xml = '<tag type="myType">0</tag>';
  740. $obj = Xml::build($xml);
  741. $expected = [
  742. 'tag' => [
  743. '@type' => 'myType',
  744. '@' => 0
  745. ]
  746. ];
  747. $this->assertEquals($expected, Xml::toArray($obj));
  748. }
  749. /**
  750. * testRss
  751. *
  752. * @return void
  753. */
  754. public function testRss()
  755. {
  756. $rss = file_get_contents(CORE_TESTS . 'Fixture/rss.xml');
  757. $rssAsArray = Xml::toArray(Xml::build($rss));
  758. $this->assertEquals('2.0', $rssAsArray['rss']['@version']);
  759. $this->assertCount(2, $rssAsArray['rss']['channel']['item']);
  760. $atomLink = ['@href' => 'http://bakery.cakephp.org/articles/rss', '@rel' => 'self', '@type' => 'application/rss+xml'];
  761. $this->assertEquals($rssAsArray['rss']['channel']['atom:link'], $atomLink);
  762. $this->assertEquals('http://bakery.cakephp.org/', $rssAsArray['rss']['channel']['link']);
  763. $expected = [
  764. 'title' => 'Alertpay automated sales via IPN',
  765. 'link' => 'http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn',
  766. 'description' => 'I\'m going to show you how I implemented a payment module via the Alertpay payment processor.',
  767. 'pubDate' => 'Tue, 31 Aug 2010 01:42:00 -0500',
  768. 'guid' => 'http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn'
  769. ];
  770. $this->assertSame($expected, $rssAsArray['rss']['channel']['item'][1]);
  771. $rss = [
  772. 'rss' => [
  773. 'xmlns:atom' => 'http://www.w3.org/2005/Atom',
  774. '@version' => '2.0',
  775. 'channel' => [
  776. 'atom:link' => [
  777. '@href' => 'http://bakery.cakephp.org/articles/rss',
  778. '@rel' => 'self',
  779. '@type' => 'application/rss+xml'
  780. ],
  781. 'title' => 'The Bakery: ',
  782. 'link' => 'http://bakery.cakephp.org/',
  783. 'description' => 'Recent Articles at The Bakery.',
  784. 'pubDate' => 'Sun, 12 Sep 2010 04:18:26 -0500',
  785. 'item' => [
  786. [
  787. 'title' => 'CakePHP 1.3.4 released',
  788. 'link' => 'http://bakery.cakephp.org/articles/view/cakephp-1-3-4-released'
  789. ],
  790. [
  791. 'title' => 'Wizard Component 1.2 Tutorial',
  792. 'link' => 'http://bakery.cakephp.org/articles/view/wizard-component-1-2-tutorial'
  793. ]
  794. ]
  795. ]
  796. ]
  797. ];
  798. $rssAsSimpleXML = Xml::fromArray($rss);
  799. $xmlText = <<<XML
  800. <?xml version="1.0" encoding="UTF-8"?>
  801. <rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  802. <channel>
  803. <atom:link href="http://bakery.cakephp.org/articles/rss" rel="self" type="application/rss+xml"/>
  804. <title>The Bakery: </title>
  805. <link>http://bakery.cakephp.org/</link>
  806. <description>Recent Articles at The Bakery.</description>
  807. <pubDate>Sun, 12 Sep 2010 04:18:26 -0500</pubDate>
  808. <item>
  809. <title>CakePHP 1.3.4 released</title>
  810. <link>http://bakery.cakephp.org/articles/view/cakephp-1-3-4-released</link>
  811. </item>
  812. <item>
  813. <title>Wizard Component 1.2 Tutorial</title>
  814. <link>http://bakery.cakephp.org/articles/view/wizard-component-1-2-tutorial</link>
  815. </item>
  816. </channel>
  817. </rss>
  818. XML;
  819. $this->assertXmlStringEqualsXmlString($xmlText, $rssAsSimpleXML->asXML());
  820. }
  821. /**
  822. * testXmlRpc
  823. *
  824. * @return void
  825. */
  826. public function testXmlRpc()
  827. {
  828. $xml = Xml::build('<methodCall><methodName>test</methodName><params /></methodCall>');
  829. $expected = [
  830. 'methodCall' => [
  831. 'methodName' => 'test',
  832. 'params' => ''
  833. ]
  834. ];
  835. $this->assertSame($expected, Xml::toArray($xml));
  836. $xml = Xml::build('<methodCall><methodName>test</methodName><params><param><value><array><data><value><int>12</int></value><value><string>Egypt</string></value><value><boolean>0</boolean></value><value><int>-31</int></value></data></array></value></param></params></methodCall>');
  837. $expected = [
  838. 'methodCall' => [
  839. 'methodName' => 'test',
  840. 'params' => [
  841. 'param' => [
  842. 'value' => [
  843. 'array' => [
  844. 'data' => [
  845. 'value' => [
  846. ['int' => '12'],
  847. ['string' => 'Egypt'],
  848. ['boolean' => '0'],
  849. ['int' => '-31']
  850. ]
  851. ]
  852. ]
  853. ]
  854. ]
  855. ]
  856. ]
  857. ];
  858. $this->assertSame($expected, Xml::toArray($xml));
  859. $xmlText = <<<XML
  860. <?xml version="1.0" encoding="UTF-8"?>
  861. <methodResponse>
  862. <params>
  863. <param>
  864. <value>
  865. <array>
  866. <data>
  867. <value>
  868. <int>1</int>
  869. </value>
  870. <value>
  871. <string>testing</string>
  872. </value>
  873. </data>
  874. </array>
  875. </value>
  876. </param>
  877. </params>
  878. </methodResponse>
  879. XML;
  880. $xml = Xml::build($xmlText);
  881. $expected = [
  882. 'methodResponse' => [
  883. 'params' => [
  884. 'param' => [
  885. 'value' => [
  886. 'array' => [
  887. 'data' => [
  888. 'value' => [
  889. ['int' => '1'],
  890. ['string' => 'testing']
  891. ]
  892. ]
  893. ]
  894. ]
  895. ]
  896. ]
  897. ]
  898. ];
  899. $this->assertSame($expected, Xml::toArray($xml));
  900. $xml = Xml::fromArray($expected, 'tags');
  901. $this->assertXmlStringEqualsXmlString($xmlText, $xml->asXML());
  902. }
  903. /**
  904. * testSoap
  905. *
  906. * @return void
  907. */
  908. public function testSoap()
  909. {
  910. $xmlRequest = Xml::build(CORE_TESTS . 'Fixture/soap_request.xml');
  911. $expected = [
  912. 'Envelope' => [
  913. '@soap:encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding',
  914. 'soap:Body' => [
  915. 'm:GetStockPrice' => [
  916. 'm:StockName' => 'IBM'
  917. ]
  918. ]
  919. ]
  920. ];
  921. $this->assertEquals($expected, Xml::toArray($xmlRequest));
  922. $xmlResponse = Xml::build(CORE_TESTS . DS . 'Fixture/soap_response.xml');
  923. $expected = [
  924. 'Envelope' => [
  925. '@soap:encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding',
  926. 'soap:Body' => [
  927. 'm:GetStockPriceResponse' => [
  928. 'm:Price' => '34.5'
  929. ]
  930. ]
  931. ]
  932. ];
  933. $this->assertEquals($expected, Xml::toArray($xmlResponse));
  934. $xml = [
  935. 'soap:Envelope' => [
  936. 'xmlns:soap' => 'http://www.w3.org/2001/12/soap-envelope',
  937. '@soap:encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding',
  938. 'soap:Body' => [
  939. 'xmlns:m' => 'http://www.example.org/stock',
  940. 'm:GetStockPrice' => [
  941. 'm:StockName' => 'IBM'
  942. ]
  943. ]
  944. ]
  945. ];
  946. $xmlRequest = Xml::fromArray($xml, ['encoding' => null]);
  947. $xmlText = <<<XML
  948. <?xml version="1.0"?>
  949. <soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope" soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
  950. <soap:Body xmlns:m="http://www.example.org/stock">
  951. <m:GetStockPrice>
  952. <m:StockName>IBM</m:StockName>
  953. </m:GetStockPrice>
  954. </soap:Body>
  955. </soap:Envelope>
  956. XML;
  957. $this->assertXmlStringEqualsXmlString($xmlText, $xmlRequest->asXML());
  958. }
  959. /**
  960. * testNamespace
  961. *
  962. * @return void
  963. */
  964. public function testNamespace()
  965. {
  966. $xml = <<<XML
  967. <root xmlns:ns="http://cakephp.org">
  968. <ns:tag id="1">
  969. <child>good</child>
  970. <otherchild>bad</otherchild>
  971. </ns:tag>
  972. <tag>Tag without ns</tag>
  973. </root>
  974. XML;
  975. $xmlResponse = Xml::build($xml);
  976. $expected = [
  977. 'root' => [
  978. 'ns:tag' => [
  979. '@id' => '1',
  980. 'child' => 'good',
  981. 'otherchild' => 'bad'
  982. ],
  983. 'tag' => 'Tag without ns'
  984. ]
  985. ];
  986. $this->assertEquals($expected, Xml::toArray($xmlResponse));
  987. $xmlResponse = Xml::build('<root xmlns:ns="http://cakephp.org"><ns:tag id="1" /><tag><id>1</id></tag></root>');
  988. $expected = [
  989. 'root' => [
  990. 'ns:tag' => [
  991. '@id' => '1'
  992. ],
  993. 'tag' => [
  994. 'id' => '1'
  995. ]
  996. ]
  997. ];
  998. $this->assertEquals($expected, Xml::toArray($xmlResponse));
  999. $xmlResponse = Xml::build('<root xmlns:ns="http://cakephp.org"><ns:attr>1</ns:attr></root>');
  1000. $expected = [
  1001. 'root' => [
  1002. 'ns:attr' => '1'
  1003. ]
  1004. ];
  1005. $this->assertEquals($expected, Xml::toArray($xmlResponse));
  1006. $xmlResponse = Xml::build('<root><ns:attr xmlns:ns="http://cakephp.org">1</ns:attr></root>');
  1007. $this->assertEquals($expected, Xml::toArray($xmlResponse));
  1008. $xml = [
  1009. 'root' => [
  1010. 'ns:attr' => [
  1011. 'xmlns:ns' => 'http://cakephp.org',
  1012. '@' => 1
  1013. ]
  1014. ]
  1015. ];
  1016. $expected = '<' . '?xml version="1.0" encoding="UTF-8"?><root><ns:attr xmlns:ns="http://cakephp.org">1</ns:attr></root>';
  1017. $xmlResponse = Xml::fromArray($xml);
  1018. $this->assertEquals($expected, str_replace(["\r", "\n"], '', $xmlResponse->asXML()));
  1019. $xml = [
  1020. 'root' => [
  1021. 'tag' => [
  1022. 'xmlns:pref' => 'http://cakephp.org',
  1023. 'pref:item' => [
  1024. 'item 1',
  1025. 'item 2'
  1026. ]
  1027. ]
  1028. ]
  1029. ];
  1030. $expected = <<<XML
  1031. <?xml version="1.0" encoding="UTF-8"?>
  1032. <root>
  1033. <tag xmlns:pref="http://cakephp.org">
  1034. <pref:item>item 1</pref:item>
  1035. <pref:item>item 2</pref:item>
  1036. </tag>
  1037. </root>
  1038. XML;
  1039. $xmlResponse = Xml::fromArray($xml);
  1040. $this->assertXmlStringEqualsXmlString($expected, $xmlResponse->asXML());
  1041. $xml = [
  1042. 'root' => [
  1043. 'tag' => [
  1044. 'xmlns:' => 'http://cakephp.org'
  1045. ]
  1046. ]
  1047. ];
  1048. $expected = '<' . '?xml version="1.0" encoding="UTF-8"?><root><tag xmlns="http://cakephp.org"/></root>';
  1049. $xmlResponse = Xml::fromArray($xml);
  1050. $this->assertXmlStringEqualsXmlString($expected, $xmlResponse->asXML());
  1051. $xml = [
  1052. 'root' => [
  1053. 'xmlns:' => 'http://cakephp.org'
  1054. ]
  1055. ];
  1056. $expected = '<' . '?xml version="1.0" encoding="UTF-8"?><root xmlns="http://cakephp.org"/>';
  1057. $xmlResponse = Xml::fromArray($xml);
  1058. $this->assertXmlStringEqualsXmlString($expected, $xmlResponse->asXML());
  1059. $xml = [
  1060. 'root' => [
  1061. 'xmlns:ns' => 'http://cakephp.org'
  1062. ]
  1063. ];
  1064. $expected = '<' . '?xml version="1.0" encoding="UTF-8"?><root xmlns:ns="http://cakephp.org"/>';
  1065. $xmlResponse = Xml::fromArray($xml);
  1066. $this->assertXmlStringEqualsXmlString($expected, $xmlResponse->asXML());
  1067. }
  1068. /**
  1069. * test that CDATA blocks don't get screwed up by SimpleXml
  1070. *
  1071. * @return void
  1072. */
  1073. public function testCdata()
  1074. {
  1075. $xml = '<' . '?xml version="1.0" encoding="UTF-8"?>' .
  1076. '<people><name><![CDATA[ Mark ]]></name></people>';
  1077. $result = Xml::build($xml);
  1078. $this->assertEquals(' Mark ', (string)$result->name);
  1079. }
  1080. /**
  1081. * data provider for toArray() failures
  1082. *
  1083. * @return array
  1084. */
  1085. public static function invalidToArrayDataProvider()
  1086. {
  1087. return [
  1088. [new \DateTime()],
  1089. [[]]
  1090. ];
  1091. }
  1092. /**
  1093. * testToArrayFail method
  1094. *
  1095. * @dataProvider invalidToArrayDataProvider
  1096. * @return void
  1097. */
  1098. public function testToArrayFail($value)
  1099. {
  1100. $this->expectException(\Cake\Utility\Exception\XmlException::class);
  1101. Xml::toArray($value);
  1102. }
  1103. /**
  1104. * Test ampersand in text elements.
  1105. *
  1106. * @return void
  1107. */
  1108. public function testAmpInText()
  1109. {
  1110. $data = [
  1111. 'outer' => [
  1112. 'inner' => ['name' => 'mark & mark']
  1113. ]
  1114. ];
  1115. $obj = Xml::build($data);
  1116. $result = $obj->asXml();
  1117. $this->assertContains('mark &amp; mark', $result);
  1118. }
  1119. /**
  1120. * Test that entity loading is disabled by default.
  1121. *
  1122. * @return void
  1123. */
  1124. public function testNoEntityLoading()
  1125. {
  1126. $file = str_replace(' ', '%20', CAKE . 'VERSION.txt');
  1127. $xml = <<<XML
  1128. <!DOCTYPE cakephp [
  1129. <!ENTITY payload SYSTEM "file://$file" >]>
  1130. <request>
  1131. <xxe>&payload;</xxe>
  1132. </request>
  1133. XML;
  1134. $result = Xml::build($xml);
  1135. $this->assertEquals('', (string)$result->xxe);
  1136. }
  1137. /**
  1138. * Test building Xml with valid class-name in value.
  1139. *
  1140. * @see https://github.com/cakephp/cakephp/pull/9754
  1141. * @return void
  1142. */
  1143. public function testClassnameInValueRegressionTest()
  1144. {
  1145. $classname = self::class; // Will always be a valid class name
  1146. $data = [
  1147. 'outer' => [
  1148. 'inner' => $classname
  1149. ]
  1150. ];
  1151. $obj = Xml::build($data);
  1152. $result = $obj->asXml();
  1153. $this->assertContains('<inner>' . $classname . '</inner>', $result);
  1154. }
  1155. /**
  1156. * Needed function for testClassnameInValueRegressionTest.
  1157. *
  1158. * @ignore
  1159. * @return array
  1160. */
  1161. public function toArray()
  1162. {
  1163. return [];
  1164. }
  1165. }