XmlTest.php 37 KB

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