cezheng / Fuzi

A fast & lightweight XML & HTML parser in Swift with XPath & CSS support
MIT License
1.07k stars 153 forks source link

Only namespace prefixes bound on the root node are considered by document.xpath(_ xpath: String) #60

Open martin-cowie opened 7 years ago

martin-cowie commented 7 years ago

Description:

XML namespaces can be bound to a prefix at any element in a document. They can be bound within an element that uses that namespace prefix. XPath expressions should be able to use the same prefixes. E.g given the document:

<root>
    <u:BrowseResponse xmlns:u="urn:foo.bar" />
</root>

the XPath expression /root/u:BrowseResponse should resolve to a nodelist of 1 element.

The XPath expression /root/u:BrowseResponse resolve to a nodelist of 0 elements and the following is printed

XPath error : Undefined namespace prefix
XPath error : Invalid expression

If the document is amended such that the prefix u: is bound at the root element the xpath expression evaluates correctly.

Environment

How to reproduce:

The following test fails, demonstrating the issue.

    func testNestedNamespacePrefixWithXPath() {
        let docStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><u:BrowseResponse xmlns:u=\"urn:foo.bar\"/></root>"

        do {
            let document = try Fuzi.XMLDocument(string: docStr)
            let nodes = document.xpath("/root/u:BrowseResponse")
            XCTAssertFalse(nodes.isEmpty)
        } catch let error {
            print(error)
            XCTFail(String(describing:error))
        }
    }
cezheng commented 7 years ago

Seems like a duplicate of #25 This is how libxml2 deals with namespaces, you have to define a prefix yourself before using it with XPath queries

martin-cowie commented 7 years ago

Thanks for coming back to me Cezheng.

I use Document.definePrefix(_ prefix: String, defaultNamespace ns: String) elsewhere in my code to handle a default namespace. Alas, using the same technique here doesn't work, possibly because urn:foo.bar is not bound as a default namespace in the XML:

    func testNestedNamespacePrefixWithXPath() {
        let docStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><u:BrowseResponse xmlns:u=\"urn:foo.bar\"/></root>"
        do {
            let document = try Fuzi.XMLDocument(string: docStr)
            document.definePrefix("u", defaultNamespace: "urn:foo.bar") // New code
            let nodes = document.xpath("/root/u:BrowseResponse")
            XCTAssertFalse(nodes.isEmpty)
        } catch let error {
            print(error)
            XCTFail(String(describing:error))
        }
    }
cezheng commented 7 years ago

the relevant code is here

Seems if the namespace definition is under root element, the query should work. And it doesn't make sense to traverse the whole doc just looking for namespaces.