diff --git a/src/formats/docx/content.rs b/src/formats/docx/content.rs
index c53c1ecf..44906f25 100644
--- a/src/formats/docx/content.rs
+++ b/src/formats/docx/content.rs
@@ -14,7 +14,7 @@ use crate::shared::delta::rebase_emphasis;
use crate::shared::fields::{FieldFrame, field_result};
use crate::shared::header::resolve_header_rows;
use crate::shared::list::{ListEntry, ListKey, flush_list};
-use crate::shared::text::{clean_text, is_xml_space};
+use crate::shared::text::clean_text;
use std::cell::RefCell;
use std::collections::HashMap;
@@ -475,20 +475,10 @@ impl<'a, 'b, 'e> InlineWalker<'a, 'b, 'e> {
}
match child.local.as_str() {
"t" => {
- // Open XML text-space contract: edge whitespace in w:t
- // is significant only under xml:space="preserve";
- // unmarked edges are discarded (Word never renders them).
- // Only XML whitespace counts: a no-break space is
- // character data, not whitespace the contract may drop.
- let preserved = child.attr_qualified(ns::XML, "space") == Some("preserve");
- let raw = child.text();
- // The contract applies to the XML text, before
- // normalization turns a no-break space into a space.
- let text = clean_text(if preserved {
- raw.as_ref()
- } else {
- raw.trim_matches(is_xml_space)
- });
+ // Run edges carry the spacing between words in documents
+ // that never mark xml:space, and XML leaves unmarked
+ // whitespace to the application, so it is kept.
+ let text = clean_text(child.text().as_ref());
if !text.is_empty() {
self.push(Inline::Text { text, style });
}
diff --git a/src/formats/docx/mod.rs b/src/formats/docx/mod.rs
index 21dfef57..eee5317e 100644
--- a/src/formats/docx/mod.rs
+++ b/src/formats/docx/mod.rs
@@ -248,36 +248,19 @@ mod tests {
}
#[test]
- fn unpreserved_edge_whitespace_is_discarded() {
- // M3: w:t edge whitespace is significant only under
- // xml:space="preserve".
+ fn unmarked_run_edge_whitespace_is_kept() {
+ // Converters that never write xml:space carry inter-word spacing on
+ // run edges; dropping it glues the words together.
let document = format!(
r#"
- lead
- discarded
- kept
- tail
+ This
+ by-law
+ grants
"#
);
let doc = parse(&docx_parts(&[("word/document.xml", &document)])).unwrap();
let Some(Block::Paragraph(inlines)) = doc.blocks.first() else { panic!() };
- assert_eq!(crate::model::inlines_to_plain_text(inlines), "leaddiscarded kept tail");
- }
-
- #[test]
- fn unpreserved_no_break_space_is_kept() {
- // The xml:space contract governs XML whitespace; a no-break space is
- // character data, so it survives an unmarked edge.
- let nbsp = '\u{a0}';
- let document = format!(
- r#"
- before{nbsp}
- after
- "#
- );
- let doc = parse(&docx_parts(&[("word/document.xml", &document)])).unwrap();
- let Some(Block::Paragraph(inlines)) = doc.blocks.first() else { panic!() };
- assert_eq!(crate::model::inlines_to_plain_text(inlines), "before after");
+ assert_eq!(crate::model::inlines_to_plain_text(inlines), "This by-law grants");
}
#[test]
diff --git a/src/shared/text.rs b/src/shared/text.rs
index f5bd0daa..aa151948 100644
--- a/src/shared/text.rs
+++ b/src/shared/text.rs
@@ -26,13 +26,6 @@ pub fn clean_text(text: &str) -> String {
out
}
-/// XML whitespace, the S production of XML 1.0: the only characters an
-/// `xml:space` contract governs. Everything else, a no-break space included,
-/// is character data.
-pub fn is_xml_space(c: char) -> bool {
- matches!(c, ' ' | '\t' | '\r' | '\n')
-}
-
/// Collapse whitespace runs to single spaces.
pub fn collapse_ws(text: &str) -> String {
let mut out = String::with_capacity(text.len());