mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-11 23:32:03 +00:00
Text nodes were decoded without line-end normalisation, so a file saved with Windows line ends kept the CR of each CR LF: a line break inside w:t read as two spaces. Text and CDATA now go through quick-xml's xml_content, which reads CR LF and a lone CR as one line feed; a CR written as stays a CR. Attribute values were already normalised. Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
This commit is contained in:
co-authored by
Martin Ball
parent
4cd6ffa564
commit
f0fb31c0c9
@@ -149,6 +149,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
## Progress log
|
||||
|
||||
- 2026-11-08: Layout: a turned inline picture whose file keeps no room for the turn (no or a zero `wp:effectExtent`, as other tools write) keeps its line to its frame and overhangs it, cut at the line's top and bottom, as Word does: the frame as it is in Word 2007 layout, with width and height exchanged from 45° to 135° and 225° to 315° in Word 2013+ layout. Such a file saves the same way. Pictures with the room recorded (Word's own files) still take their rotated bounds. Readiness unchanged.
|
||||
- 2026-11-08: XML text and CDATA read with their line ends normalised (XML 1.0 §2.11): CR LF and a lone CR are one line feed, ` ` stays a CR, so a line break inside `w:t` in a file saved with Windows line ends is one space, not two (#5897); readiness estimates unchanged.
|
||||
- 2026-11-06: Arabic interface: the Home tab's Styles gallery reads from the right, as the rest of the mirrored ribbon does (#5566). Normal is the rightmost tile, the next styles follow leftwards and the scroll arrows and More sit at the gallery's left end; left to right is unchanged. Readiness unchanged.
|
||||
- 2026-11-05: ODT heading text of another size keeps it (gap 19): a smaller word in a heading came back at the heading's size in LibreOffice after a save.
|
||||
- 2026-11-05: ODT table rows kept from breaking across pages stay so (gap 19): they came back from a save free to break.
|
||||
@@ -865,6 +866,7 @@ MATH glyphs that reference the same construction now share its immutable variant
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-08 | trivial | Progress log: XML line ends in text normalised (#5897) |
|
||||
| 2026-11-06 | trivial | Progress log: the Arabic interface's Styles gallery reads from the right (#5566) |
|
||||
| 2026-11-07 | trivial | Page Setup also bounds the unfocused margin display after a long numeric paste; the accepted value is retained (#5650). |
|
||||
| 2026-11-05 | trivial | Page Setup's margin, paper-size and document-grid number boxes scroll long text inside the field instead of enlarging the dialog (#5650). Readiness estimates unchanged. |
|
||||
|
||||
+31
-2
@@ -462,15 +462,22 @@ fn parse_with(bytes: &[u8], extra: &[(&'static str, &str)], strict: bool) -> Res
|
||||
attach(&mut stack, &mut root, el);
|
||||
}
|
||||
}
|
||||
// Line ends are normalised as XML 1.0 says (§2.11): CR LF and a lone CR are read as
|
||||
// LF, so a file saved with Windows line ends has no CR in its text. A CR written as
|
||||
// ` ` is a character reference and stays.
|
||||
Event::Text(t) => {
|
||||
if let Some(top) = stack.last_mut() {
|
||||
let s = t.decode().map_err(|e| DocxError::Xml(e.to_string()))?;
|
||||
let s = t.xml_content(XmlVersion::Explicit1_0).map_err(|e| DocxError::Xml(e.to_string()))?;
|
||||
push_text(top, &s);
|
||||
}
|
||||
}
|
||||
Event::CData(t) => {
|
||||
if let Some(top) = stack.last_mut() {
|
||||
let s = String::from_utf8_lossy(&t).into_owned();
|
||||
// CDATA that isn't UTF-8 is decoded lossily, with its line ends normalised the same way.
|
||||
let s = t
|
||||
.xml_content(XmlVersion::Explicit1_0)
|
||||
.map(|s| s.into_owned())
|
||||
.unwrap_or_else(|_| String::from_utf8_lossy(&t).replace("\r\n", "\n").replace('\r', "\n"));
|
||||
push_text(top, &s);
|
||||
}
|
||||
}
|
||||
@@ -724,6 +731,28 @@ mod tests {
|
||||
assert_eq!(t.attr("xml:space"), Some("preserve"));
|
||||
}
|
||||
|
||||
/// CDATA that isn't UTF-8 is decoded lossily, and its line ends are still normalised.
|
||||
#[test]
|
||||
fn line_ends_in_cdata_that_isnt_utf8_are_normalised() {
|
||||
let x =
|
||||
b"<x:document xmlns:x=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><x:t><![CDATA[a\xff\r\nb\rc]]></x:t></x:document>";
|
||||
assert_eq!(parse(x).unwrap().child("w:t").unwrap().text(), "a\u{FFFD}\nb\nc");
|
||||
}
|
||||
|
||||
/// XML 1.0 §2.11: CR LF and a lone CR in text and CDATA are read as LF; ` ` stays a CR.
|
||||
#[test]
|
||||
fn line_ends_in_text_are_normalised_but_a_cr_reference_stays() {
|
||||
let x = b"<x:document xmlns:x=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><x:t>a\r\nb\rc d<![CDATA[e\r\nf]]></x:t></x:document>";
|
||||
let e = parse(x).unwrap();
|
||||
assert_eq!(e.child("w:t").unwrap().text(), "a\nb\nc\rde\nf");
|
||||
// The writer escapes a CR, so a CR in the text survives a save.
|
||||
let x = format!(
|
||||
"<x:document xmlns:x=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><x:t>{}</x:t></x:document>",
|
||||
esc("a\r\nb\rc")
|
||||
);
|
||||
assert_eq!(parse(x.as_bytes()).unwrap().child("w:t").unwrap().text(), "a\r\nb\rc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choice_requirements_resolve_namespace_aliases_and_rebindings() {
|
||||
let x = br#"<mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:pen="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"><mc:Choice Requires="pen"/><mc:Choice xmlns:pen="urn:future" Requires="pen"/><mc:Choice Requires="undeclared"/></mc:AlternateContent>"#;
|
||||
|
||||
@@ -270,3 +270,12 @@ fn flat_opc_limits_count_what_is_not_kept() {
|
||||
// A part nothing reads, one element over what the XML tree reader takes in a part (8 000 000).
|
||||
assert!(limited(&format!(r#"<pkg:part pkg:name="/word/unused.xml"><pkg:xmlData><r>{}</r></pkg:xmlData></pkg:part>"#, "<y/>".repeat(8_000_000))));
|
||||
}
|
||||
|
||||
/// A Word 2003 XML Document saved with Windows line ends: XML reads CR LF as one line feed (XML
|
||||
/// 1.0 §2.11), so a line break inside `w:t` is one space, not two.
|
||||
#[test]
|
||||
fn word_2003_xml_with_windows_line_ends_reads_one_space_per_line_break() {
|
||||
let xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<w:wordDocument xmlns:w=\"http://schemas.microsoft.com/office/word/2003/wordml\">\r\n<w:body><w:p><w:r><w:t>a\r\nb</w:t></w:r></w:p></w:body></w:wordDocument>\r\n";
|
||||
let d = read_xml(xml.as_bytes()).unwrap();
|
||||
assert_eq!(paras(&d)[0].plain_text(), "a b");
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user