Skip to content

Commit 34c7464

Browse files
committed
added parser tests
1 parent cf8b567 commit 34c7464

File tree

5 files changed

+187
-6
lines changed

5 files changed

+187
-6
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Usage
44

5+
Iterate over all the elements within the xml.
6+
57
```php
68
use DMT\XmlParser\Parser;
79
use DMT\XmlParser\Source\StringParser;
@@ -17,3 +19,16 @@ while ($node = $parser->parse()) {
1719
// iterates: node <books>, node <book>, node <title>, text-node "A book title", node <book> etc
1820
}
1921
```
22+
23+
Select an element and call _parseXml_ to get the current element including its contents.
24+
25+
```php
26+
while ($node = $parser->parse()) {
27+
if ($node->localName !== 'book') {
28+
continue;
29+
}
30+
$book = $parser->parseXml();
31+
break;
32+
}
33+
// book is "<book><title>A book title</title></book>"
34+
```

src/Parser.php

+29-6
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,28 @@
22

33
namespace DMT\XmlParser;
44

5+
use DMT\XmlParser\Node\Element;
56
use DMT\XmlParser\Node\ElementNode;
6-
use DMT\XmlParser\Node\Node;
7+
use DMT\XmlParser\Node\XmlNamespace;
8+
use DMT\XmlParser\Node\XmlNamespaces;
79
use Generator;
810

911
class Parser
1012
{
1113
private ?ElementNode $current = null;
12-
private ?Generator $iterator = null;
14+
private Generator $iterator;
15+
private XmlNamespaces $namespaces;
1316

1417
/**
1518
* @param \DMT\XmlParser\Tokenizer $tokenizer
1619
*/
1720
public function __construct(Tokenizer $tokenizer)
1821
{
19-
if (!$this->iterator) {
20-
$this->iterator = $tokenizer->tokenize();
21-
}
22+
$this->iterator = $tokenizer->tokenize();
23+
$this->namespaces = $tokenizer->namespaces();
2224
}
2325

24-
public function parse(): ?Node
26+
public function parse(): ?ElementNode
2527
{
2628
try {
2729
return $this->current = $this->iterator->current();
@@ -47,6 +49,27 @@ public function parseXml(): string
4749
$this->iterator->next();
4850
}
4951

52+
if ($this->namespaces->count() > 0 && $this->current instanceof Element) {
53+
$this->applyParentNamespaces($this->current);
54+
}
55+
5056
return strval($this->current);
5157
}
58+
59+
private function applyParentNamespaces(Element $element): void
60+
{
61+
$prefixes = [];
62+
foreach ($element->namespaces() As $namespace) {
63+
$prefixes[] = $namespace->prefix;
64+
}
65+
66+
$namespaces = array_filter(
67+
$this->namespaces->getArrayCopy(),
68+
fn (XmlNamespace $namespace) => !in_array($namespace->prefix, $prefixes)
69+
);
70+
71+
foreach ($namespaces as $namespace) {
72+
$element->addAttribute($namespace);
73+
}
74+
}
5275
}

src/Tokenizer.php

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public function __construct(Parser $parser, string $encoding = null, int $flags
4040
xml_set_character_data_handler($this->handle, 'contents');
4141
}
4242

43+
public function namespaces(): XmlNamespaces
44+
{
45+
return $this->namespaces;
46+
}
47+
4348
public function tokenize(): Generator
4449
{
4550
foreach ($this->parser->parse() as $data) {

tests/Node/XmlNamespacesTest.php

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace DMT\Test\XmlParser\Node;
4+
5+
use DMT\XmlParser\Node\XmlNamespace;
6+
use DMT\XmlParser\Node\XmlNamespaces;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class XmlNamespacesTest extends TestCase
10+
{
11+
public function testPop(): void
12+
{
13+
$namespaces = new XmlNamespaces([
14+
new XmlNamespace('http://example.org/ns', ''),
15+
new XmlNamespace('http://example.org/ns1', 'ns1'),
16+
new XmlNamespace('http://example.org/ns2', 'ns2'),
17+
$last = new XmlNamespace('http://example.org/ns', 'ns'),
18+
]);
19+
20+
$this->assertSame($last, $namespaces->pop());
21+
$this->assertNotContains($last, $namespaces);
22+
}
23+
24+
public function testPopEmpty(): void
25+
{
26+
$namespaces = new XmlNamespaces();
27+
$this->assertNull($namespaces->pop());
28+
}
29+
30+
public function testFind(): void
31+
{
32+
$namespaces = new XmlNamespaces([
33+
new XmlNamespace('http://example.org/ns', ''),
34+
new XmlNamespace('http://example.org/ns1', 'ns1'),
35+
$find = new XmlNamespace('http://example.org/ns', 'ns'),
36+
new XmlNamespace('http://example.org/ns2', 'ns2'),
37+
]);
38+
39+
$this->assertSame($find, $namespaces->find('http://example.org/ns'));
40+
}
41+
42+
public function testNotFound()
43+
{
44+
$namespaces = new XmlNamespaces([
45+
new XmlNamespace('http://example.org/ns1', 'ns1'),
46+
new XmlNamespace('http://example.org/ns2', 'ns2'),
47+
]);
48+
49+
$this->assertNull($namespaces->find('http://example.org/ns'));
50+
}
51+
}

tests/ParserTest.php

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace DMT\Test\XmlParser;
4+
5+
use DMT\XmlParser\Node\ElementNode;
6+
use DMT\XmlParser\Node\Text;
7+
use DMT\XmlParser\Parser;
8+
use DMT\XmlParser\Source\FileParser;
9+
use DMT\XmlParser\Source\StringParser;
10+
use DMT\XmlParser\Tokenizer;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class ParserTest extends TestCase
14+
{
15+
public function testParse(): void
16+
{
17+
$parser = new Parser(new Tokenizer(new StringParser('<book><title>A title</title><author/></book>')));
18+
19+
$elements = [];
20+
while ($element = $parser->parse()) {
21+
$this->assertInstanceOf(ElementNode::class, $element);
22+
$elements[] = $element->localName ?? $element->contents;
23+
}
24+
25+
$this->assertSame(['book', 'title', 'A title', 'author'], $elements);
26+
}
27+
28+
public function testParseXml()
29+
{
30+
$parser = new Parser(new Tokenizer(new FileParser(__DIR__ . '/fixtures/books.xml')));
31+
32+
$books = [];
33+
while ($element = $parser->parse()) {
34+
if ($element->localName === 'book') {
35+
$books[] = $parser->parseXml();
36+
}
37+
}
38+
39+
$this->assertCount(2, $books);
40+
}
41+
42+
public function testParseXmlWithInheritNamespaces()
43+
{
44+
$xml = '<books xmlns:ns1="urn:ns-uri" xmlns:x="lang">
45+
<ns1:book xmlns:x="alt-lang">
46+
<ns1:title x:lang="en_US">A title</ns1:title>
47+
<author/>
48+
</ns1:book>
49+
</books>';
50+
51+
$parser = new Parser(new Tokenizer(new StringParser($xml)));
52+
53+
while ($element = $parser->parse()) {
54+
if ($element->localName === 'book') {
55+
$book = $parser->parseXml();
56+
}
57+
}
58+
59+
$this->assertStringContainsString('xmlns:ns1="urn:ns-uri"', $book);
60+
$this->assertStringContainsString('xmlns:x="alt-lang"', $book);
61+
}
62+
63+
public function testDropNamespaces()
64+
{
65+
$xml = '<book xmlns="http://example.org/ns" xmlns:ns1="http://example.org/ns">
66+
<ns1:title>A title</ns1:title>
67+
<ns1:author/>
68+
</book>';
69+
70+
$xml = (new Parser(new Tokenizer(new StringParser($xml), null, Tokenizer::XML_DROP_NAMESPACES)))->parseXml();
71+
72+
$this->assertStringNotContainsString('ns1', $xml);
73+
$this->assertStringNotContainsString('xmlns', $xml);
74+
}
75+
76+
public function testUseCData()
77+
{
78+
$xml = '<book><title>A title</title><author/></book>';
79+
80+
$parser = new Parser(new Tokenizer(new StringParser($xml), null, Tokenizer::XML_USE_CDATA));
81+
do {
82+
$element = $parser->parse();
83+
} while (!$element instanceof Text);
84+
85+
$this->assertMatchesRegularExpression('~\<\!\[CDATA\[.*\]\]\>~', strval($element));
86+
}
87+
}

0 commit comments

Comments
 (0)