Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ QueryPath Changelog
- Fix `QPXML::createElement()` emitting an "Only variables should be passed by reference" notice for a namespace-prefixed element name without an explicit namespace URI (issue #82)
- Fix the `:text` pseudo-class so it matches jQuery: it selects `input` elements whose `type` attribute is absent or is `text` (case-insensitively). It never indicated, and still does not indicate, whether a node is a text node
- `find('*')` now matches the nodes in the match set as well as their descendants, so that a selector can be tested against an element already in hand
- Fix `QPXML::createElement()` throwing "Undefined namespace" for a prefixed element name whose prefix is declared on the matched element or an ancestor below the document element. The prefix is now resolved against the namespaces in scope on the first matched element (issue #94)
- `writeHTML5()` now returns the `DOMQuery` like the other `write*()` methods, and throws `QueryPath\IOException` instead of a `TypeError` when the file cannot be written (issue #80)
- Fix `QueryPath\ParseException` and `QueryPath\IOException` losing the PHP warning they were raised from (issue #91). The message was the error number, as in `2 (/path/to/src/DOMQuery.php: 1343)`, and the code was always `0`. The message now carries the warning text and the code is the PHP error level (for example `E_WARNING`), so code that matches on the old message text will need updating
- Fix `tag()` emitting an "Undefined property" warning and returning `NULL` when the first match is not an element, such as a text, comment or processing instruction node returned by `contents()`. It now returns an empty string, as it does for an empty match set (issue #93)
Expand Down
43 changes: 22 additions & 21 deletions src/Extension/QPXML.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,40 +174,41 @@ public function createNilElement($text, $value)
/**
* Create an element with the given namespace.
*
* The element is created in the document of the first matched element. If
* $text has a prefix and no $nsUri is given, the prefix is resolved against
* the namespaces in scope on that element.
*
* @param string $text
* @param string $nsUri
* The namespace URI for the given element.
*
* @return DOMQuery
* The new element, or an empty DOMQuery if there are no matches.
* @throws Exception
* If the prefix of $text is not bound to a namespace on the first match.
*/
public function createElement($text, $nsUri = null)
{
if (isset($text)) {
foreach ($this->qp->get() as $element) {
if ($nsUri === null && strpos($text, ':') !== false) {
$ns = strstr($text, ':', true);
$nsUri = $element->ownerDocument->lookupNamespaceURI($ns);

if ($nsUri === null) {
throw new Exception("Undefined namespace for: " . $text);
}
}
$element = $this->qp->get(0);
if (! isset($text) || $element === null) {
return new DOMQuery();
}

$node = null;
if ($nsUri !== null) {
$node = $element->ownerDocument->createElementNS(
$nsUri,
$text
);
} else {
$node = $element->ownerDocument->createElement($text);
}
if ($nsUri === null && strpos($text, ':') !== false) {
$nsUri = $element->lookupNamespaceURI(strstr($text, ':', true));

return QueryPath::with($node);
if ($nsUri === null) {
throw new Exception("Undefined namespace for: " . $text);
}
}

return new DOMQuery();
if ($nsUri !== null) {
$node = $element->ownerDocument->createElementNS($nsUri, $text);
} else {
$node = $element->ownerDocument->createElement($text);
}

return QueryPath::with($node);
}

/**
Expand Down
37 changes: 37 additions & 0 deletions tests/QueryPath/Extension/QPXMLTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,43 @@ public function testCreateElementWithPrefix()
$this->assertEquals('x:item', $element->nodeName);
}

public function testCreateElementWithPrefixDeclaredBelowRoot()
{
$xml = '<?xml version="1.0"?><root xmlns:x="urn:x"><child xmlns:y="urn:y"/></root>';

$element = qp($xml, 'child')->createElement('y:item')->get(0);
$this->assertEquals('urn:y', $element->namespaceURI);
$this->assertEquals('y:item', $element->nodeName);

$element = qp($xml, 'child')->createElement('x:item')->get(0);
$this->assertEquals('urn:x', $element->namespaceURI);
}

public function testCreateElementWithPrefixOutOfScope()
{
$xml = '<?xml version="1.0"?><root><a xmlns:y="urn:y"/><b/></root>';

$this->expectException(\QueryPath\Exception::class);
$this->expectExceptionMessage('Undefined namespace for: y:item');
qp($xml, 'b')->createElement('y:item');
}

public function testCreateElementUsesFirstMatch()
{
$xml = '<?xml version="1.0"?><root><a xmlns:y="urn:a"/><b xmlns:y="urn:b"/></root>';

$element = qp($xml, 'a, b')->createElement('y:item');
$this->assertCount(1, $element);
$this->assertEquals('urn:a', $element->get(0)->namespaceURI);
}

public function testCreateElementWithNoMatches()
{
$xml = '<?xml version="1.0"?><root xmlns:x="urn:x"/>';

$this->assertCount(0, qp($xml, 'missing')->createElement('x:item'));
}

public function testComment()
{
$this->assertEquals('This is a comment.', trim(qp($this->file, 'root')->comment()));
Expand Down
Loading