Skip to content

ElementTree incorrectly refuses to write attributes without namespaces when default_namespace is used #61290

Closed

Description

@SilverbackNet
BPO 17088
Nosy @rhettinger, @scoder, @silverbacknet, @vadmium, @SpecLad, @mthuurne
PRs
  • bpo-17088: Fix handling of XML attributes when serializing with default namespace #11050
  • Files
  • bug17088_1.patch: Patch and test case
  • bug17088_2.patch: Improved patch and test case
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2013-01-31.02:35:07.204>
    labels = ['expert-XML', 'type-bug', '3.10', '3.11']
    title = 'ElementTree incorrectly refuses to write attributes without namespaces when default_namespace is used'
    updated_at = <Date 2021-07-19.12:58:02.922>
    user = 'https://github.com/SilverbackNet'

    bugs.python.org fields:

    activity = <Date 2021-07-19.12:58:02.922>
    actor = 'scoder'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['XML']
    creation = <Date 2013-01-31.02:35:07.204>
    creator = 'silverbacknet'
    dependencies = []
    files = ['32981', '33125']
    hgrepos = []
    issue_num = 17088
    keywords = ['patch']
    message_count = 17.0
    messages = ['181005', '205281', '205951', '206156', '206186', '206217', '209424', '216061', '216067', '261785', '304120', '331390', '331432', '354101', '365760', '397793', '397794']
    nosy_count = 14.0
    nosy_names = ['rhettinger', 'scoder', 'eli.bendersky', 'wiml', 'silverbacknet', 'martin.panter', 'SpecLad', 'gene_wood', 'marczellm', 'Garrett Birkel', 'Rafael Ascensao', 'mthuurne', 'carlschaefer', 'dtluttik']
    pr_nums = ['11050']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue17088'
    versions = ['Python 3.10', 'Python 3.11']

    Linked PRs

    Activity

    1. SilverbackNet commented on Jan 31, 2013

      SilverbackNetmannequin
      MannequinAuthor

      ET reads a default-namespaced (xmnls="whatever") file correctly but won't write it back out.

      The error given is:
      ValueError: cannot use non-qualified names with default_namespace option

      The XML reference is reasonably clear on this:
      http://www.w3.org/TR/REC-xml-names/#defaulting
      "Default namespace declarations do not apply directly to attribute names;"
      "The namespace name for an unprefixed attribute name always has no value."

      Therefore, it is not an error to write non-qualified _attribute_ names with a default namespace; they're just considered un-namespaced anyway. The trivial case where a file is read in with a default namespace and written out with the same one should make it obvious:

      from xml.etree.ElementTree import *
      register_namespace('svg', 'http://www.w3.org/2000/svg')
      svg = ElementTree(XML("""
      <svg width="12cm" height="4cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" version="1.1">
      <rect x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2" />
      </svg>
      """))
      svg.write('simple_new.svg',encoding='UTF-8',default_namespace='svg')

      Yet this will fail with the error above. By leaving off default_namespace, every element is pointlessly prefixed by 'svg:' in the resulting file, but it does work.

    2. wiml commented on Dec 5, 2013

      wimlmannequin
      Mannequin

      I have run into this a few times although it is only recently that I've convinced myself I understood the XML namespace spec well enough to know what the right behavior was. (I came to the same interpretation as silverbacknet.)

      I have attached a patch which I believe fixes (and tests) the problem.

    3. wiml commented on Dec 12, 2013

      wimlmannequin
      Mannequin

      FWIW: I noticed that my patch has a bug due to sharing the cache dict between element names and attribute names, although I think this is unlikely to crop up very often in practice. I'll submit a better patch if/when I get the time to put one together.

    4. wiml commented on Dec 13, 2013

      wimlmannequin
      Mannequin

      Here's an improved patch (and improved testcase).

      It's a little more intrusive than the last patch because when a default namespace is being used, two distinct qname caches must be made.

    5. scoder commented on Dec 14, 2013

      @scoder
      Contributor

      Note that the option is called "default_namespace", not "default_namespace_prefix". Could you try passing the namespace URI instead?

    6. wiml commented on Dec 15, 2013

      wimlmannequin
      Mannequin

      Yes, the problem occurs regardless of whether the default_namespace parameter is the correct SVG namespace URI --- it's the fact of requesting a default namespace at all that exposes the bug.

    7. wiml commented on Jan 27, 2014

      wimlmannequin
      Mannequin

      Ping

    8. genewood commented on Apr 14, 2014

      genewoodmannequin
      Mannequin

      One workaround to this is described here : http://stackoverflow.com/a/4999510/168874

      It involves prefixing all of the elements with the namespace like this :

          from xml.etree import ElementTree as ET
          
          # build a tree structure
          root = ET.Element("{http://www.company.com}STUFF")
          body = ET.SubElement(root, "{http://www.company.com}MORE_STUFF")
          body.text = "STUFF EVERYWHERE!"
          
          # wrap it in an ElementTree instance, and save as XML
          tree = ET.ElementTree(root)
          
          tree.write("page.xml",
                     xml_declaration=True,encoding='utf-8',
                     method="xml",default_namespace='http://www.company.com')
    9. scoder commented on Apr 14, 2014

      @scoder
      Contributor

      @gene_wood: that's unrelated. This ticket is about attributes being rejected incorrectly.

      Fixing the example of the OP:

      >>> from xml.etree.ElementTree import *
      >>> svg = ElementTree(XML("""
      ... <svg width="12cm" height="4cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" version="1.1">
      ... <rect x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2" />
      ... </svg>
      ... """))
      >>> tostring(svg.getroot())   # formatting is mine
      b'<svg:svg xmlns:svg="http://www.w3.org/2000/svg" height="4cm" version="1.1" viewBox="0 0 1200 400" width="12cm">\n
            <svg:rect fill="none" height="398" stroke="blue" stroke-width="2" width="1198" x="1" y="1" />\n
        </svg:svg>'
      >>> svg.write('simple_new.svg',encoding='UTF-8',default_namespace='http://www.w3.org/2000/svg')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "/usr/lib/python3.3/xml/etree/ElementTree.py", line 826, in write
          qnames, namespaces = _namespaces(self._root, default_namespace)
        File "/usr/lib/python3.3/xml/etree/ElementTree.py", line 942, in _namespaces
          add_qname(key)
        File "/usr/lib/python3.3/xml/etree/ElementTree.py", line 920, in add_qname
          "cannot use non-qualified names with "
      ValueError: cannot use non-qualified names with default_namespace option
      >>> svg.write('simple_new.svg',encoding='UTF-8')
      >>> 

      So, it works without namespace defaulting and fails with an incorrect error when a default namespace is provided. Clearly a bug.

      Regarding the proposed patch: it looks like the right thing to do in general, but it has a relatively high code impact. I would prefer a patch with lower churn. One thing that could be tried is to use only one tag cache dict and extend the key from the plain tag to (tag, is_attribute). Might have a performance impact on the already slow serialiser, though. In any case, both approaches are quite wasteful, because they duplicate the entire namespace-prefix mapping just because there might be a single namespace that behaves differently for atributes. An alternative could be to split the *value* of the mapping in two: (element_prefix, attribute_prefix). This would keep the overhead at serialisation low, with only slightly more work when building the mapping. At first sight, I like that idea better.

      This code returns a list in one case and a set-like view in another (Py3):

      + if default_namespace:
      + prefixes_list = [ (default_namespace, "") ]
      + prefixes_list.extend(namespaces.items())
      + else:
      + prefixes_list = namespaces.items()

      I can't see the need for this change. Why can't the default namespace be stored in the namespaces dict right from the start, as it was before?

      As a minor nitpick, this lambda based sort key:

          key=lambda x: x[1]):  # sort on prefix

      is better expressed using operator.itemgetter(1).

      I'd also rename the "defaultable" flag to "is_attribute" and pass it as keyword argument (bare boolean parameters are unreadable in function calls).

      Given the impact of this change, I'd also suggest not applying it to Py2.x anymore.

    10. GarrettBirkel commented on Mar 14, 2016

      GarrettBirkelmannequin
      Mannequin

      Just hit up against this namespace bug. Has this patch been abandoned??

    11. RafaelAscensao commented on Oct 11, 2017

      RafaelAscensaomannequin
      Mannequin

      what's the status on this?

    12. mthuurne commented on Dec 8, 2018

      mthuurnemannequin
      Mannequin

      I was working on what I thought would be an elegant solution to this problem: for non-qualified attributes, add the element's namespace before accessing the cache and strip the namespace prefix after accessing the cache if it's equal to the element's prefix.

      However, this approach doesn't work: even though non-qualified attributes will be processed like they are the element's namespace, they are considered to have no namespace. This means <ns:x a="1" ns:a="2"/> is considered valid XML, even though it effectively defines the same attribute twice.

      https://www.w3.org/TR/REC-xml-names/#uniqAttrs

      In my opinion the spec made a silly choice here, but that's probably not something that can fixed anymore.

      I haven't decided yet whether I'll make another attempt at fixing this issue. In any case, I hope this tale of caution benefits someone.

    13. mthuurne commented on Dec 9, 2018

      mthuurnemannequin
      Mannequin

      I think I have a good solution now, see the pull request for details. It does touch a lot of code, but I split all the changes into small consistent units, so it should be easier to verify whether they are correct.

    14. 4 remaining items

    15. scoder commented on Jul 19, 2021

      @scoder
      Contributor

      The obvious work-around is to not use a default namespace. The result is just a visual difference, not a semantic one.

      If someone wants to continue with the existing PR, I'll try to free some time to review any improvements.

    16. added
      3.11only security fixes
      and removed on Jul 19, 2021
    17. transferred this issue fromon Apr 10, 2022
    18. couling commented on Jan 5, 2023

      @couling

      @scoder The result is not just visual.

      Strictly speaking prefixes have no semantic effect in XML. But unfortunately this has not stopped developers erroneously relying on the prefix. Many libraries including xml.dom expose the prefix. ElementTree is a little unusual in the software world for not exposing it. Unfortunetly people have used it.

      This issue can cause problems when interfacing with other systems despite the XML standard. Yes, you can argue the other system is broken, but that's of little use to a developer with no ability to change the other side of an interface.

      The register_namespace workaround dodges this problem, but is painful for having to set something globally.


      Using the default namespace attributes is so common that it would be fair to say that default_namespace= has never worked for XML documents with attributes.

      To my mind a better solution would be to depreciate default_namespace entirely and replace it with a new prefixes argument supplying a dictionary of {"Namespace URI": "prefix"} (or visa versa) that take priority over the globally registered one.

      One implementation decision would be: when handling namespaces not in prefixes the solution could either:

      • defaulted from those registered globally.
      • generate new prefixes as is already done for unregistered namespaces. If prefixes not set then, and only then, the globally registered namespaces would take effect.

      Since this is a new param, it would be backward compatible.

    19. added a commit that references this issue on Jun 20, 2024
    20. serhiy-storchaka commented on Aug 31, 2026

      @serhiy-storchaka
      Member

      PR #156747 fixes this.

      A default namespace declaration does not apply to attribute names (Namespaces in XML, 6.2), so an unqualified attribute name can be written as is. A qualified attribute name always needs a prefix, even if it is in the default namespace, so its namespace is declared twice:

      >>> e = ET.Element('{uri}svg', {'{uri}id': 'a'})
      >>> ET.tostring(e, encoding='unicode', default_namespace='uri')
      '<svg xmlns="uri" xmlns:ns1="uri" ns1:id="a" />'

      lxml serializes it the same way.

      @couling, the prefixes argument you proposed is a separate feature; this only removes the error.

    21. added 2 commits that reference this issue on Sep 13, 2026
    22. added
      3.16new features, bugs and security fixes
      and removed
      3.11only security fixes
      on Sep 13, 2026
    23. added a commit that references this issue on Sep 14, 2026
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.16new features, bugs and security fixestopic-XMLtype-bugAn unexpected behavior, bug, or error

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions