Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Sources/OOXMLSwift/IO/DocxReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,17 @@ public struct DocxReader {
// children) via complementary mechanisms. Keep local until parseRun
// gains a second pre-consumer that would benefit from `@testable`
// introspection.
//
// Foreign-namespace asymmetry note (#19): the typed `rPr` lookup above
// is prefix-qualified (`elements(forName: "w:rPr")`), while this
// allowlist is prefix-agnostic (`localName == "rPr"`). A malformed
// `<x:rPr xmlns:x="other-ns">` direct child is therefore neither parsed
// as run properties nor preserved as `rawElements`. This mirrors the
// documented paragraph-level `pPr` policy from #14: ECMA-376 defines
// run properties here as `<w:rPr>`, so silent-drop is the conservative
// response to foreign-namespace lookalikes. If a real producer emits
// meaningful foreign-namespace `rPr`, migrate this to a namespace-aware
// `(localName, namespaceURI)` check instead of widening the allowlist.
let recognizedRunChildren: Set<String> = ["rPr", "t", "drawing", "oMath", "oMathPara"]
var collectedRawElements: [RawElement] = []
for child in element.children ?? [] {
Expand Down
39 changes: 39 additions & 0 deletions Tests/OOXMLSwiftTests/Issue19NamespacePrefixAsymmetryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import XCTest
@testable import OOXMLSwift

/// Regression coverage for PsychQuant/ooxml-swift#19.
///
/// `parseRun` intentionally treats typed `<w:rPr>` lookup as prefix-qualified
/// while its raw-child skip list uses `localName`. This pins the documented
/// malformed-input policy: a foreign-namespace `<x:rPr>` lookalike is silently
/// dropped, not parsed as WordprocessingML run properties and not preserved as
/// raw XML.
final class Issue19NamespacePrefixAsymmetryTests: XCTestCase {

func testForeignNamespaceRPrLookalikeIsDocumentedSilentDrop() throws {
let element = try XMLElement(xmlString: """
<w:r xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:x="urn:foreign">
<x:rPr>
<x:b/>
</x:rPr>
<w:t>body</w:t>
<x:custom>keep me</x:custom>
</w:r>
""")

let run = try DocxReader.parseRun(
from: element,
relationships: RelationshipsCollection()
)

XCTAssertEqual(run.text, "body")
XCTAssertFalse(run.properties.bold, "foreign-namespace rPr must not be parsed as OOXML run properties")

let rawElements = run.rawElements ?? []
let rawNames = rawElements.map(\.name)
XCTAssertFalse(rawNames.contains("rPr"), "foreign-namespace rPr is intentionally skipped by localName allowlist")
XCTAssertEqual(rawNames, ["custom"], "unrelated foreign children should still be preserved as rawElements")
XCTAssertEqual(rawElements.first?.xml.contains("<x:custom>keep me</x:custom>"), true)
}
}