Repository: Shoobx/xmldiff

Issue:
-f xml fails with:  "TypeError: empty namespace prefix is not supported in XPath"
I wanted to try adding the argon2 S2K allocation to https://www.iana.org/assignments/pgp-parameters/pgp-parameters.xml, which means adding a row for codepoint 4 in the S2K parameters section, so i fetched it, modified it by hand, and tried to compare the output with `xmldiff` 2.4 (from debian, version 2.4-2).  It worked fine with the `diff` formatter, but the `xml` formatter choked with error message 
"TypeError: empty namespace prefix is not supported in XPath".  Here's a log:

```console
$ xmldiff pgp-parameters.xml.orig pgp-parameters.xml
[insert, /*/*[4], {http://www.iana.org/assignments}record, 7]
[move, /*/*[5]/*[8]/*[1], /*/*[4]/*[8], 0]
[insert, /*/*[4]/*[8], {http://www.iana.org/assignments}description, 1]
[update-text, /*/*[4]/*[8]/*[2], "Argon2"]
[insert, /*/*[4]/*[8], {http://www.iana.org/assignments}xref, 2]
[insert-attribute, /*/*[4]/*[8]/*[3], data, "rfcXXXX"]
[insert-attribute, /*/*[4]/*[8]/*[3], type, "rfc"]
[move, /*/*[18]/*[8]/*[1], /*/*[4]/*[9], 0]
[update-text, /*/*[4]/*[9]/*[1], "5-99"]
[move, /*/*[8]/*[8]/*[1], /*/*[5]/*[8], 0]
[move, /*/*[16]/*[8]/*[1], /*/*[8]/*[8], 0]
[insert, /*/*[16]/*[8], {http://www.iana.org/assignments}value, 0]
[update-text, /*/*[16]/*[8]/*[1], "4"]
[move, /*/*[4]/*[9]/*[2], /*/*[18]/*[8], 0]
$ xmldiff -f xml -p pgp-parameters.xml.orig pgp-parameters.xml
Traceback (most recent call last):
  File "/usr/bin/xmldiff", line 33, in <module>
    sys.exit(load_entry_point('xmldiff==2.4', 'console_scripts', 'xmldiff')())
  File "/usr/lib/python3/dist-packages/xmldiff/main.py", line 116, in diff_command
    result = diff_files(args.file1, args.file2, diff_options=diff_options,
  File "/usr/lib/python3/dist-packages/xmldiff/main.py", line 50, in diff_files
    return _diff(etree.parse, left, right,
  File "/usr/lib/python3/dist-packages/xmldiff/main.py", line 38, in _diff
    return diff_trees(left_tree, right_tree, diff_options=diff_options,
  File "/usr/lib/python3/dist-packages/xmldiff/main.py", line 30, in diff_trees
    return formatter.format(diffs, left)
  File "/usr/lib/python3/dist-packages/xmldiff/formatting.py", line 343, in format
    self.handle_action(action, root)
  File "/usr/lib/python3/dist-packages/xmldiff/formatting.py", line 356, in handle_action
    method(action, result)
  File "/usr/lib/python3/dist-packages/xmldiff/formatting.py", line 467, in _handle_InsertNode
    target = self._xpath(tree, action.target)
  File "/usr/lib/python3/dist-packages/xmldiff/formatting.py", line 398, in _xpath
    for match in node.xpath(path, namespaces=node.nsmap):
  File "src/lxml/etree.pyx", line 1594, in lxml.etree._Element.xpath
  File "src/lxml/xpath.pxi", line 259, in lxml.etree.XPathElementEvaluator.__init__
  File "src/lxml/xpath.pxi", line 131, in lxml.etree._XPathEvaluatorBase.__init__
  File "src/lxml/xpath.pxi", line 55, in lxml.etree._XPathContext.__init__
  File "src/lxml/extensions.pxi", line 81, in lxml.etree._BaseContext.__init__
TypeError: empty namespace prefix is not supported in XPath
```
bug: xpath cannot detect None prefix namespace
When I use xmldiff.patch to execute patching, it use xpath to search the node. 
Because the nsmap of the tree is like {None:'aaaURL','s':"bbbURL"}, the default namespace prefix is None, so the xpath cannot read it.
I cannot use my own namespace as input, nor can i edit the nsmap of the tree or transfer None to ''
The error message goes like this:

>   File "D:\Programs\Code\python\projects\auto_localization\.venv\lib\site-packages\xmldiff\patch.py", line 55, in _handle_UpdateTextAfter
>     tree.xpath(action.node, namespaces=self.nsmap)[0].tail = action.text
>   File "src\lxml\etree.pyx", line 1597, in lxml.etree._Element.xpath
>   File "src\lxml\xpath.pxi", line 259, in lxml.etree.XPathElementEvaluator.__init__
>   File "src\lxml\xpath.pxi", line 131, in lxml.etree._XPathEvaluatorBase.__init__
>   File "src\lxml\xpath.pxi", line 55, in lxml.etree._XPathContext.__init__
>   File "src\lxml\extensions.pxi", line 81, in lxml.etree._BaseContext.__init__
> TypeError: empty namespace prefix is not supported in XPath

and the xml header is like this:

> \<ResourceDictionary
>         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
>         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>         xmlns:s="clr-namespace:System;assembly=mscorlib">

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index c35c7e7..88b864e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,13 +4,13 @@ Changes
 2.6.3 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- And there was a namespace bug in the patch as well. #118
 
 
 2.6.2 (2023-05-21)
 ------------------
 
-- Solved an error in the xmlformatter when using default namespaces. #89, #113
+- Solved an error in the xmlformatter when using default namespaces. #89
 
 
 2.6.1 (2023-04-05)
diff --git a/xmldiff/patch.py b/xmldiff/patch.py
index d052db3..49eb35b 100644
--- a/xmldiff/patch.py
+++ b/xmldiff/patch.py
@@ -16,6 +16,8 @@ class Patcher:
 
         # Save the namespace:
         self._nsmap = tree.nsmap
+        if None in self._nsmap:
+            del self._nsmap[None]
 
         # Copy the tree so we don't modify the original
         result = deepcopy(tree)


Test patch:
diff --git a/tests/test_patch.py b/tests/test_patch.py
index 670167e..62ff76e 100644
--- a/tests/test_patch.py
+++ b/tests/test_patch.py
@@ -121,6 +121,21 @@ class DiffPatch(unittest.TestCase):
         # top level comment differs, but that's OK.
         compare_elements(result, right.getroot())
 
+    def test_diff_default_namespace(self):
+        here = os.path.split(__file__)[0]
+        lfile = os.path.join(here, "test_data", "namespace.left.xml")
+        rfile = os.path.join(here, "test_data", "namespace.right.xml")
+
+        left = etree.parse(lfile)
+        right = etree.parse(rfile)
+        diff = diff_trees(left, right)
+        result = Patcher().patch(diff, left)
+
+        # This example has top level comments, and lxml doesn't deal well
+        # with that, so the trees are not EXACTLY the same, the trailing
+        # top level comment differs, but that's OK.
+        compare_elements(result, right.getroot())
+
 
 TEST_DIFF = """[delete, node]
 [insert, target, tag, 0]