Skip to content

Compare part names independent of percent-encoding - #97

Merged
aaronzi merged 2 commits into
eclipse-aascw:mainfrom
aaronzi:fix/part-name-percent-encoding
Oct 13, 2026
Merged

aaronzi merged 2 commits into
eclipse-aascw:mainfrom
aaronzi:fix/part-name-percent-encoding

Conversation

@aaronzi

@aaronzi aaronzi commented Oct 13, 2026

Copy link
Copy Markdown
Member

Fixes #83

Problem

The parts map was keyed by the raw ZIP entry name, but FindPart/MustPart/DeletePart/IsSpec/… key on the percent-encoded URL.pathname. For an entry stored with a literal space (aasx/suppl/a b.pdf):

  • FindPart returned null, even for the part's own Part.URI.
  • DeletePart did nothing and raised no error.
  • A plain OpenReadWrite… → Flush renamed the entry to a%20b.pdf, while the .rels target kept the literal space. Reading the result back threw supplementary part … not found.

On the issue's framing: in ISO/IEC 29500-2 (which IDTA-01005 makes normative for AASX), part names are URIs, and the ZIP entry name is the part name without its leading /. Conforming producers therefore write a%20b.pdf, and this library already handled that case correctly. Literal-space entries come from non-conforming producers. They're still worth reading robustly, especially because the old behavior could corrupt such a package on round trip.

Change

  • Map keys are encoding-independent. Each path segment is percent-decoded, lowercased and re-encoded. /a b.pdf, /a%20b.pdf and /A%20B.pdf now map to the same key, in line with the OPC rules for comparing part names. This applies equally to parts, relationship sources and targets, and content-type overrides.
  • Part.URI for entries with stray %, ? or # escapes those characters, so they aren't treated as URL syntax.
  • ZIP entry names are kept. Parts read from an archive remember their entry name, and Flush writes them back under it, so a round trip no longer renames entries.
  • Relationship part names follow the source part's actual name. Previously they were derived from the lowercased map key. A spec aasx/Data.xml now gets aasx/_rels/Data.xml.rels instead of aasx/_rels/data.xml.rels.

Tests

New tests cover:

  • literal and percent-encoded entry names: lookup by own URI, by a URL containing a space, and case-insensitively; round trip; DeletePart
  • entry names containing % and #
  • self-written packages with spaces
  • case of relationship part names

Three of these fail on main. The other two guard behavior that already worked.

I also compared main against this branch on 66 real-world AASX files (vendor packages, IDTA templates, the IDTA-01005 example). Specs, supplementaries and lookups are identical on all of them. The only difference: 3 fewer packages get entries renamed on an open-and-flush round trip.

The Go library has the same defect, mirrored (it fails on the conforming %20 form). A matching PR is opened there.

The parts map was keyed by the raw ZIP entry name while lookups used the
percent-encoded URL pathname. A part whose entry name contains a literal
space (or another character URL encodes) could therefore not be found
via FindPart, not even with its own Part.URI, and DeletePart silently
did nothing. Flush also renamed such entries to their encoded form while
their relationship targets kept the literal name, which broke the
package on a plain open-and-flush round trip.

ISO/IEC 29500-2 part names are URIs compared case-insensitively, so map
keys are now built from the decoded, lowercased and re-encoded path
segments. Parts read from an archive remember their entry name, which
is reused on Flush, and relationship parts are named after their source
part instead of the lowercased map key.

Fixes eclipse-aascw#83
@aaronzi
aaronzi force-pushed the fix/part-name-percent-encoding branch from a6c7731 to 81e8ccd Compare October 13, 2026 06:44
@aaronzi
aaronzi requested a balanced review from Copilot October 13, 2026 06:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Encoding-equivalent ZIP entries can collide and silently lose package content.

1 open finding
What changed in this PR

Canonically compares percent-encoded part names while preserving original ZIP entry names.

Changes:

  • Normalizes part, relationship, and content-type lookup keys.
  • Preserves archive names during round trips.
  • Adds coverage for encoded names and relationship casing.
File Description
src/​package.ts Implements canonical lookup and entry-name preservation.
test/​package.test.ts Tests lookup, deletion, round trips, and naming.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/package.ts
Two ZIP entries whose names differ only in percent-encoding or ASCII
case, e.g. "a b.pdf" and "a%20b.pdf", now map to the same part. Opening
such an archive used to keep only one of them and lose the other on
Flush, so it is now rejected as an invalid format, as the Go library
already does.

ISO/IEC 29500-2 compares part names as case-insensitive ASCII strings,
so non-ASCII characters are no longer case-folded: "%C3%84" (Ä) and
"%C3%A4" (ä) are distinct part names.
@aaronzi
aaronzi merged commit e9d50ad into eclipse-aascw:main Oct 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FindPart cannot address a part whose name has a space in it

2 participants