Skip to content

xml5ever: compare prefix and local name when looking for duplicate attributes - #780

Open
FadeHack wants to merge 1 commit into
servo:mainfrom
FadeHack:xml5ever-qualified-duplicate-attrs
Open

xml5ever: compare prefix and local name when looking for duplicate attributes#780
FadeHack wants to merge 1 commit into
servo:mainfrom
FadeHack:xml5ever-qualified-duplicate-attrs

Conversation

@FadeHack

Copy link
Copy Markdown

Fixes #775

<root xml:lang="en" lang="en"/> was reported as Duplicate attribute, but <root lang="en" xml:lang="en"/> parsed fine.

Why

finish_attribute compared the raw attribute name it had just read against the local names of the attributes already on the tag:

let current_attr_name = self.current_attr_name.borrow();
let name = &current_attr_name[..];
self.current_tag_attrs
    .borrow()
    .iter()
    .any(|a| &*a.name.local == name)

The stored attributes have been through process_qname by then, so xml:lang is sitting there as prefix xml with local name lang. Reading a plain lang right after it matches that local name and gets rejected.

That is also where the order dependence comes from. With lang first, the raw name xml:lang is compared against the local name lang, they do not match, and both attributes survive.

Namespaces in XML section 6.3 says two attributes are the same only when their expanded names are the same, and xml:lang and lang do not have the same expanded name, so both orderings should parse cleanly. It also affects the WPT test html/rendering/non-replaced-elements/tables/table-align-float.xhtml, whose root element carries both.

The fix

Run process_qname first, then compare the prefix along with the local name. Real duplicates still get reported, including two attributes that share a prefix.

One thing worth calling out: the tokenizer has no namespace bindings at this point, they are resolved later in XmlTreeBuilder::bind_qname, so two different prefixes bound to the same namespace URI with the same local name are still not caught. That is a genuine duplicate under section 6.3 but it cannot be detected here without the bindings, so it belongs in the tree builder. I left it alone rather than widening this PR, and I am happy to look at it separately if you want.

Testing

Added unit tests in xml5ever/src/tokenizer/mod.rs for both orderings from the issue, for two distinct prefixes sharing a local name, and for real duplicates with and without a prefix. The first two fail without the change.

cargo test --all, cargo fmt --all -- --check and cargo clippy --all-features --all-targets are clean.

The duplicate check compared the raw attribute name we had just read
against the local names of the attributes already on the tag. The stored
ones have been through process_qname by then, so xml:lang is sitting
there as prefix "xml" with local name "lang", and reading a plain lang
right after it matched and got rejected as a duplicate.

That is why it was order dependent. Writing lang first and xml:lang
second was fine, because the raw name "xml:lang" never matches the local
name "lang".

Namespaces in XML section 6.3 says attributes are the same only when
their expanded names are the same, and xml:lang and lang have different
expanded names, so both orderings should parse cleanly. This also shows
up on the WPT test table-align-float.xhtml, whose root element carries
both.

So process the name first and compare the prefix along with the local
name. Real duplicates, including two attributes with the same prefix,
are still reported.

The tokenizer has no namespace bindings yet, they are resolved later in
XmlTreeBuilder::bind_qname, so two different prefixes bound to the same
namespace still slip through here. That is a separate thing and it needs
to be handled in the tree builder.

Fixes servo#775
@github-actions github-actions Bot added the V-non-breaking A non-breaking change label Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

V-non-breaking A non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XML tokenizer falsely reports xml:lang + lang as duplicate attributes

1 participant