XmlTest.php 38 KB

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