Repository: ncclient/ncclient

Issue:
get() operation not accepting multiple subtree filters
I am trying to get RPC reply of multiple sub trees at once via get() operation as below but its giving me an error.
```python
getfacts='''
    <host-names xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-shellutil-cfg"/>
    <system-time xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-shellutil-oper"/>
    <interfaces xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-pfi-im-cmd-oper">
     <interfaces>
       <interface>
        <interface-name/>
       </interface>
      </interfaces>
     </interfaces>
    <inventory xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-invmgr-oper">
     <racks>
       <rack>
        <attributes>
            <inv-basic-bag>
                <software-revision/>
                <model-name/>
                <serial-number/>
            </inv-basic-bag>
        </attributes>
       </rack>
      </racks>
     </inventory>'''
  
def connect(host, port, user, password, source):
    conn = manager.connect(host=host,
                           port=port,
                           username=user,
                           password = password,
                           device_params={'name': 'iosxr'}
                           )

    rpc_reply = conn.get(filter=('subtree', getfacts)).xml
    print(rpc_reply)
```

Error:
```
return x if etree.iselement(x) else etree.fromstring(x.encode('UTF-8'), parser=_get_parser(huge_tree))
  File "src/lxml/etree.pyx", line 3235, in lxml.etree.fromstring
  File "src/lxml/parser.pxi", line 1876, in lxml.etree._parseMemoryDocument
  File "src/lxml/parser.pxi", line 1764, in lxml.etree._parseDoc
  File "src/lxml/parser.pxi", line 1127, in lxml.etree._BaseParser._parseDoc
  File "src/lxml/parser.pxi", line 601, in lxml.etree._ParserContext._handleParseResultDoc
  File "src/lxml/parser.pxi", line 711, in lxml.etree._handleParseResult
  File "src/lxml/parser.pxi", line 640, in lxml.etree._raiseParseError
  File "<string>", line 4
lxml.etree.XMLSyntaxError: Extra content at the end of the document, line 4, column 5
```

Discussion:

Patch:
diff --git a/ncclient/operations/util.py b/ncclient/operations/util.py
index bb03502..b760fd1 100755
--- a/ncclient/operations/util.py
+++ b/ncclient/operations/util.py
@@ -56,6 +56,10 @@ def build_filter(spec, capcheck=None):
             rep.append(to_ele(criteria))
         else:
             raise OperationError("Invalid filter type")
+    elif isinstance(spec, list):
+        rep = new_ele("filter", type="subtree")
+        for cri in spec:
+            rep.append(to_ele(cri))
     else:
 
         rep = validated_element(spec, ("filter", qualify("filter"),


Test patch:
diff --git a/test/unit/operations/test_retrieve.py b/test/unit/operations/test_retrieve.py
index fdf5b23..d5166fe 100644
--- a/test/unit/operations/test_retrieve.py
+++ b/test/unit/operations/test_retrieve.py
@@ -220,3 +220,43 @@ class TestRetrieve(unittest.TestCase):
         call = mock_request.call_args_list[0][0][0]
         call = ElementTree.tostring(call)
         self.assertEqual(call, xml)
+
+    @patch('ncclient.operations.retrieve.RPC._request')
+    def test_get_with_multi_subtree_filters(self, mock_request):
+        result = '''
+                <?xml version="1.0" encoding="UTF-8"?>
+                <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                      xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+                    <cont1 xmlns="urn:mod1">
+                        <le1>test_mod1_001</le1>
+                        <le2>this is a test-one example</le2>
+                    </cont1>
+                    <cont2 xmlns="urn:mod2">
+                        <le1>test_mod2_002</le1>
+                        <le2>this is a test-two example</le2>
+                        <lst>
+                            <le3>a list of mod2</le3>
+                        </lst>
+                    </cont2>
+                </data>'''
+        mock_request.return_value = result
+        session = ncclient.transport.SSHSession(self.device_handler)
+        obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL)
+
+        multi_subtree_filters = [
+            '<cont1 xmlns="urn:mod1"> \
+                <le1/> \
+                <le2/> \
+             </cont1>',
+             '<cont2 xmlns="urn:mod2"/>'
+        ]
+
+        ret = obj.request(copy.deepcopy(multi_subtree_filters))
+        node = new_ele("get")
+        node.append(util.build_filter(multi_subtree_filters))
+        xml = ElementTree.tostring(node)
+        call = mock_request.call_args_list[0][0][0]
+        call = ElementTree.tostring(call)
+        self.assertEqual(call, xml)
+        self.assertEqual(ret, result)
+