Repository: ncclient/ncclient

Issue:
Question: how to pass namespace defintion to xpath filter?
Hi,

I would like to pass an xpath filter to the `get` method. It should be the equivalent of this XML request:

```xml
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="28">
  <get>
    <filter type="subtree">
      <turing-machine xmlns="http://example.net/turing-machine">
        <transition-function />
      </turing-machine>
    </filter>
  </get>
</rpc>
```

I wanted to use something like:
```python
reply = connection.get(filter=("xpath", "/tm:turing-machine/tm:transition-function"))
```

This produces an invalid request, since `tm` namespace is not defined anywhere:
```xml
<nc:rpc xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:6f210162-f037-47ee-93cd-b3034b0100ac">
  <nc:get>
    <nc:filter type="xpath" select="/tm:turing-machine/tm:transition-function"/>
  </nc:get>
</nc:rpc>
```

(Netopeer2 server returns an error: `XML namespace with prefix "tm" not defined.`)

Correct request would look like (this is how `netopeer2-cli` produces it):
```xml
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="30">
  <get>
    <filter type="xpath" xmlns:tm="http://example.net/turing-machine" select="/tm:turing-machine/transition-function"/>
  </get>
</rpc>
```

How this could be achieved with `ncclient`?

Discussion:

Patch:
diff --git a/ncclient/operations/util.py b/ncclient/operations/util.py
index b1bd733..0d1bb38 100755
--- a/ncclient/operations/util.py
+++ b/ncclient/operations/util.py
@@ -49,10 +49,12 @@ def build_filter(spec, capcheck=None):
     type = None
     if isinstance(spec, tuple):
         type, criteria = spec
-        rep = new_ele("filter", type=type)
         if type == "xpath":
-            rep.attrib["select"] = criteria
+            ns, select = criteria
+            rep = new_ele_nsmap("filter", ns, type=type)
+            rep.attrib["select"] = select
         elif type == "subtree":
+            rep = new_ele("filter", type=type)
             rep.append(to_ele(criteria))
         else:
             raise OperationError("Invalid filter type")
diff --git a/ncclient/xml_.py b/ncclient/xml_.py
index 8e81c81..adcc742 100644
--- a/ncclient/xml_.py
+++ b/ncclient/xml_.py
@@ -228,6 +228,8 @@ def parent_ns(node):
         return node.nsmap[node.prefix]
     return None
 
+new_ele_nsmap = lambda tag, nsmap, attrs={}, **extra: etree.Element(qualify(tag), attrs, nsmap, **extra)
+
 new_ele = lambda tag, attrs={}, **extra: etree.Element(qualify(tag), attrs, **extra)
 
 new_ele_ns = lambda tag, ns, attrs={}, **extra: etree.Element(qualify(tag,ns), attrs, **extra)


Test patch:
diff --git a/test/unit/operations/test_utils.py b/test/unit/operations/test_utils.py
index 7cad8ab..45adbd3 100644
--- a/test/unit/operations/test_utils.py
+++ b/test/unit/operations/test_utils.py
@@ -58,12 +58,14 @@ class TestUtils(unittest.TestCase):
         self.assertEqual(call, ElementTree.tostring(to_ele(xml)))
 
     def test_build_filter_2(self):
-        criteria = "configuration/system"
+        select = "configuration/system"
+        ns = {"ns0": "http://www.xxx.org"}
+        criteria = (ns, select)
         filter = ("xpath", criteria)
         reply = build_filter(filter)
         call = ElementTree.tostring(reply)
-        node = new_ele("filter", type="xpath")
-        node.attrib["select"] = criteria
+        node = new_ele_nsmap("filter", ns, type="xpath")
+        node.attrib["select"] = select
         self.assertEqual(call, ElementTree.tostring(node))
 
     def test_build_filter_3(self):