Skip to content

Commit 8e9f7d8

Browse files
committed
Copied shared filed from Codeception and module-soap
1 parent 18cd622 commit 8e9f7d8

File tree

5 files changed

+322
-1
lines changed

5 files changed

+322
-1
lines changed

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
],
1414
"homepage": "https://codeception.com/",
1515
"require": {
16-
"php": "^8.0"
16+
"php": "^8.0",
17+
"ext-dom": "*",
18+
"codeception/lib-web": "^1.0",
19+
"symfony/css-selector": ">=4.4.24 <7.0"
1720
},
1821
"require-dev": {
1922
"phpunit/phpunit": "^9.5 | ^10.0"

src/Util/Soap.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Util;
6+
7+
/**
8+
* This class is left for BC compatibility.
9+
* Most of its contents moved to parent
10+
*
11+
* Class Soap
12+
* @package Codeception\Util
13+
*/
14+
class Soap extends Xml
15+
{
16+
public static function request(): XmlBuilder
17+
{
18+
return new XmlBuilder();
19+
}
20+
21+
public static function response(): XmlBuilder
22+
{
23+
return new XmlBuilder();
24+
}
25+
}

src/Util/Xml.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Util;
6+
7+
use DOMDocument;
8+
use DOMElement;
9+
use DOMNode;
10+
11+
use function is_array;
12+
13+
class Xml
14+
{
15+
public static function arrayToXml(DOMDocument $xml, DOMNode $domNode, array $array = []): DOMDocument
16+
{
17+
foreach ($array as $el => $val) {
18+
if (is_array($val)) {
19+
self::arrayToXml($xml, $domNode->$el, $val);
20+
} else {
21+
$domNode->appendChild($xml->createElement($el, $val));
22+
}
23+
}
24+
return $xml;
25+
}
26+
27+
public static function toXml(DOMDocument|DOMElement|XmlBuilder|string|null $xml): DOMDocument
28+
{
29+
if ($xml instanceof XmlBuilder) {
30+
return $xml->getDom();
31+
}
32+
if ($xml instanceof DOMDocument) {
33+
return $xml;
34+
}
35+
$dom = new DOMDocument();
36+
$dom->preserveWhiteSpace = false;
37+
if ($xml instanceof DOMNode) {
38+
$xml = $dom->importNode($xml, true);
39+
$dom->appendChild($xml);
40+
return $dom;
41+
}
42+
43+
if (is_array($xml)) {
44+
return self::arrayToXml($dom, $dom, $xml);
45+
}
46+
if (!empty($xml)) {
47+
$dom->loadXML($xml);
48+
}
49+
return $dom;
50+
}
51+
52+
public static function build(): XmlBuilder
53+
{
54+
return new XmlBuilder();
55+
}
56+
}

src/Util/XmlBuilder.php

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Util;
6+
7+
use DOMDocument;
8+
use DOMElement;
9+
use Exception;
10+
11+
/**
12+
* That's a pretty simple yet powerful class to build XML structures in jQuery-like style.
13+
* With no XML line actually written!
14+
* Uses DOM extension to manipulate XML data.
15+
*
16+
* ```php
17+
* <?php
18+
* $xml = new \Codeception\Util\XmlBuilder();
19+
* $xml->users
20+
* ->user
21+
* ->val(1)
22+
* ->email
23+
* ->val('davert@mail.ua')
24+
* ->attr('valid','true')
25+
* ->parent()
26+
* ->cart
27+
* ->attr('empty','false')
28+
* ->items
29+
* ->item
30+
* ->val('useful item');
31+
* ->parents('user')
32+
* ->active
33+
* ->val(1);
34+
* echo $xml;
35+
* ```
36+
*
37+
* This will produce this XML
38+
*
39+
* ```xml
40+
* <?xml version="1.0"?>
41+
* <users>
42+
* <user>
43+
* 1
44+
* <email valid="true">davert@mail.ua</email>
45+
* <cart empty="false">
46+
* <items>
47+
* <item>useful item</item>
48+
* </items>
49+
* </cart>
50+
* <active>1</active>
51+
* </user>
52+
* </users>
53+
* ```
54+
*
55+
* ### Usage
56+
*
57+
* Builder uses chained calls. So each call to builder returns a builder object.
58+
* Except for `getDom` and `__toString` methods.
59+
*
60+
* * `$xml->node` - create new xml node and go inside of it.
61+
* * `$xml->node->val('value')` - sets the inner value of node
62+
* * `$xml->attr('name','value')` - set the attribute of node
63+
* * `$xml->parent()` - go back to parent node.
64+
* * `$xml->parents('user')` - go back through all parents to `user` node.
65+
*
66+
* Export:
67+
*
68+
* * `$xml->getDom` - get a DOMDocument object
69+
* * `$xml->__toString` - get a string representation of XML.
70+
*
71+
* [Source code](https://github.com/Codeception/Codeception/blob/5.0/src/Codeception/Util/XmlBuilder.php)
72+
*/
73+
class XmlBuilder
74+
{
75+
protected DOMDocument $dom;
76+
77+
protected DOMElement|DOMDocument $currentNode;
78+
79+
public function __construct()
80+
{
81+
$this->dom = new DOMDocument();
82+
$this->currentNode = $this->dom;
83+
}
84+
85+
/**
86+
* Appends child node
87+
*/
88+
public function __get(string $tag): XmlBuilder
89+
{
90+
$domElement = $this->dom->createElement($tag);
91+
$this->currentNode->appendChild($domElement);
92+
$this->currentNode = $domElement;
93+
return $this;
94+
}
95+
96+
public function val($val): self
97+
{
98+
$this->currentNode->nodeValue = $val;
99+
return $this;
100+
}
101+
102+
/**
103+
* Sets attribute for current node
104+
*/
105+
public function attr(string $attr, string $val): self
106+
{
107+
$this->currentNode->setAttribute($attr, $val);
108+
return $this;
109+
}
110+
111+
/**
112+
* Traverses to parent
113+
*/
114+
public function parent(): self
115+
{
116+
$this->currentNode = $this->currentNode->parentNode;
117+
return $this;
118+
}
119+
120+
/**
121+
* Traverses to parent with $name
122+
*
123+
* @throws Exception
124+
*/
125+
public function parents(string $tag): self
126+
{
127+
$traverseNode = $this->currentNode;
128+
$elFound = false;
129+
while ($traverseNode->parentNode) {
130+
$traverseNode = $traverseNode->parentNode;
131+
if ($traverseNode->tagName === $tag) {
132+
$this->currentNode = $traverseNode;
133+
$elFound = true;
134+
break;
135+
}
136+
}
137+
138+
if (!$elFound) {
139+
throw new Exception("Parent {$tag} not found in XML");
140+
}
141+
142+
return $this;
143+
}
144+
145+
public function __toString(): string
146+
{
147+
return $this->dom->saveXML();
148+
}
149+
150+
public function getDom(): DOMDocument
151+
{
152+
return $this->dom;
153+
}
154+
}

src/Util/XmlStructure.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Util;
6+
7+
use Codeception\Exception\ElementNotFound;
8+
use Codeception\Exception\MalformedLocatorException;
9+
use Codeception\Util\Soap as SoapXmlUtil;
10+
use DOMDocument;
11+
use DOMNode;
12+
use DOMXPath;
13+
use Symfony\Component\CssSelector\CssSelectorConverter;
14+
15+
class XmlStructure
16+
{
17+
protected DOMDocument|DOMNode $xml;
18+
19+
public function __construct($xml)
20+
{
21+
$this->xml = SoapXmlUtil::toXml($xml);
22+
}
23+
24+
public function matchesXpath(string $xpath): bool
25+
{
26+
$domXpath = new DOMXPath($this->xml);
27+
$res = $domXpath->query($xpath);
28+
if ($res === false) {
29+
throw new MalformedLocatorException($xpath);
30+
}
31+
return $res->length > 0;
32+
}
33+
34+
public function matchElement(string $cssOrXPath): ?DOMNode
35+
{
36+
$domXpath = new DOMXpath($this->xml);
37+
$selector = (new CssSelectorConverter())->toXPath($cssOrXPath);
38+
$els = $domXpath->query($selector);
39+
if ($els) {
40+
return $els->item(0);
41+
}
42+
$els = $domXpath->query($cssOrXPath);
43+
if ($els->length !== 0) {
44+
return $els->item(0);
45+
}
46+
throw new ElementNotFound($cssOrXPath);
47+
}
48+
49+
public function matchXmlStructure($xml): bool
50+
{
51+
$xml = SoapXmlUtil::toXml($xml);
52+
$root = $xml->firstChild;
53+
$els = $this->xml->getElementsByTagName($root->nodeName);
54+
if (empty($els)) {
55+
throw new ElementNotFound($root->nodeName, 'Element');
56+
}
57+
58+
$matches = false;
59+
foreach ($els as $node) {
60+
$matches |= $this->matchForNode($root, $node);
61+
}
62+
return $matches;
63+
}
64+
65+
protected function matchForNode($schema, $xml): bool
66+
{
67+
foreach ($schema->childNodes as $node1) {
68+
$matched = false;
69+
foreach ($xml->childNodes as $node2) {
70+
if ($node1->nodeName == $node2->nodeName) {
71+
$matched = $this->matchForNode($node1, $node2);
72+
if ($matched) {
73+
break;
74+
}
75+
}
76+
}
77+
if (!$matched) {
78+
return false;
79+
}
80+
}
81+
return true;
82+
}
83+
}

0 commit comments

Comments
 (0)