Commit Graph
272 Commits
Author SHA1 Message Date
FredApps e2bc9e017e Merge remote-tracking branch 'origin/main' into pr1104
# Conflicts:
#	docs/file-format-parity.md
2026-11-02 18:25:52 +01:00
41d46625e0 DOCX: VML horizontal rules take their line (#3956)
* DOCX: VML horizontal rules take their line

A web page's <hr> pasted into Word is a VML rule: `v:rect` with
`o:hr="t"`, often `width:0`. WordCraft took the width as written and
drew a 1 pt dot where Word draws a line across the page.

Measured on Word with a synthetic document: a rule with no width and
no `o:hrpct` spans the line inside the paragraph's indents; `o:hrpct`
is a share of it in tenths of a percent (500 = half); `o:hrpct="0"`
keeps the shape's own width; `o:hralign` places a shorter one (left,
centre, right); `fillcolor` and `stroked` apply as to any VML shape.
WordCraft now matches each case within 0.1 pt.

- wordcraft-doc: ShapeExtra::hr (HorizontalRule: share, alignment,
  Word's standard look).
- wordcraft-docx: `o:hr`, `o:hrpct`, `o:hralign`, `o:hrstd` read; a rule
  is written back as the same VML rule (DrawingML has none), and Word
  opens the re-saved file with the same lines.
- wordcraft-layout: a rule takes its whole line and is drawn at its
  share; measured for a table column it asks for no width, so it
  follows its column. (Word draws a rule in a cell at the page's text
  width, centred on the cell and past its edges; it stays in the cell
  here.)

docs/file-format-parity.md is updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Horizontal rules: only an inline, shown rule is one

Review: the rule arm of the writer ignored the shape's placement, so a
rule imported with position:absolute or visibility:hidden came back
inline and visible. Only a rule in the text and shown is read as a rule
now (a pasted <hr> is one); a positioned or hidden one stays a plain
shape, which keeps both when saved. The writer checks the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Horizontal rules: a see-through fill is kept; column measuring asks for the right width

Review: `write_hr` wrote only the fill's colour, so a rule with `v:fill
opacity` came back opaque after a save; the opacity now goes back as `v:fill`.

Review: measured for a table column, every rule asked for no width, so one of
its own width (`o:hrpct="0"`) could end up clipped by a narrow column; it now
asks for its width. AutoFit to contents measures on a line of its own width
that the rule took for a real line, so a rule sharing its line made the
column as wide as it could go; any measuring width now counts.

`Placed::Shape` boxes its extras: with the rule, `Placed` went past clippy's
large_enum_variant limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Horizontal rules: placed in what the line has left; rotation and flips kept on save

Review: a whole-line rule ignores a first-line indent or a line narrowed by a
float. Measured in Word, it does too: the width stays the share of the
paragraph's width and the rule runs past the margin, or under a float on its
right. What did differ was the placement: Word centres and right-aligns a rule
in what the line has left, with a first-line indent moving the end of that room
along, and a right-aligned rule beside a float was drawn off the page here. The
display now places it that way (`ParaLayout::rule_room`).

Review: a save dropped a rule's rotation and flips; they now go back in its
style, as Word writes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: VML horizontal-rule revision row at the top of the history

---------

Co-authored-by: Daniele Mori <Dinasty@hotmail.it>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-02 10:55:20 -05:00
242e850aaa #3660: refuse a package that declares too many zip entries before zip parses them (#5260)
* #3660: refuse a package that declares too many zip entries before zip parses them

zip::ZipArchive::new parses and keeps every central directory header
the end record declares before its caller can look at len(). A ZIP64
end record can declare one per 46 bytes of the file: a 47 MB package of
one stored file listed 1,000,000 times takes zip 3.0.0 about half a
second and a large transient allocation, and len() then says 1, since
the copies share a name, so the docx reader's MAX_ENTRIES cap never
fires. ODT and spreadsheet packages had no cap at all.

wordcraft_doc::zip_guard::check finds the end records the way zip finds
them (backwards; the ZIP64 record from the locator's offset on; a plain
record when it has no entries or a central header before it) and refuses
a package declaring more than the reader's limit. The docx package
reader, the ODT reader, the spreadsheet reader (mail-merge data) and the
template-title probe call it first, with 20,000 entries as the limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* formats: keep the extensions doc line on EXTENSIONS

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-02 04:58:18 -05:00
NaxvogandClaude Opus 5.5 868fffa6fe Files: a Word chunk that asks to keep its source formatting keeps it (w:matchSrc)
An alternative format chunk holding a Word document with w:matchSrc keeps its
own styles: a style the document already has comes along under a new id, the
chunk's document defaults (and Word's, where they say nothing) go into its
base styles, and its unstyled paragraphs use its own Normal. Without
w:matchSrc the document's styles win, as before. Adopting another document
now also renumbers table styles and the revisions of table, row and cell
property changes.

A document whose chunk shares style names with it (Normal with an exact line
height, an italic Subtitle) ran a page longer than in Word: the chunk's tables
took the document's styles. It now keeps its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 07:04:31 +01:00
NaxvogandClaude Opus 5.5 89c0d516ae Files: documents keep their embedded alternative format chunks, and MHT opens
Word brings a document's alternative format chunks (w:altChunk) in where they
stand when it opens the file; WordCraft left them out, so a document made of
one (as generators and mail-merge tools write them) opened empty.

- docx: read_with/ReadOptions with a ChunkImporter for formats other than
  WordprocessingML; nested Word documents are read here (at most 4 deep,
  256 chunks and 512 MB of chunk bytes a file, XML depth counted on).
- doc: Document::adopt (moved from paste) brings another document's media,
  lists, styles, notes, comments and revisions along under new ids; pictures
  in groups and charts are relinked. Insert > Text from File uses it and now
  keeps the file's pictures and lists.
- engine: chunks in MHT, HTML (in their charset), RTF and plain text go
  through the format importers.
- formats: MHT web archives open (MIME, quoted-printable/base64, charsets,
  pictures by Content-Location or cid). HTML reads style element rules
  (simple selectors, cascade, budgets), paragraph margins, indents, line
  height, cell widths and borders; an opened page takes Word's web-page
  defaults (Times New Roman 12 pt, single lines, black headings, automatic
  spacing). html::decode knows the single-byte Windows code pages.

docxide-pdf suite: croatian_regulations_altchunk goes from one empty page to
Word's 8 pages, SSIM 2.7% -> 78.7%; no other document changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 07:04:31 +01:00
ReesandBrandon Thomas bf9e9fb8ae DOCX: keep alternate-content table rows and cells (#5221)
* Fix #5219: retain alternate-content table rows and cells

* Re-wrap the collect() doc comment

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-01 23:17:19 -05:00
8a191fbf11 Shapes: arrowheads keep their kind and size and are drawn at Word's sizes (#5192)
Word draws an arrowhead 2, 3 or 5 times the line's width long and wide
(small, medium, large; `a:headEnd`/`a:tailEnd` `len`/`w`), a line under
2 points as a 2-point one. Every arrowhead was drawn one size and saved
as a medium triangle: the kind, width and length are now read (DrawingML
and VML), kept and written back, and the size is drawn as Word does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-01 14:46:16 -05:00
9632646c5e Bibliography: keep a source's corporate author (#4848) (#4898)
* #4848: corporate authors in bibliographies

A Word source whose author is an organisation (Create Source › Corporate Author)
stores it as `b:Corporate`, which the reader ignored: the source opened with no
author, citations fell back to the title and bibliography entries started with
the year. A bare name typed into Author was split into "Organization, W. H.".

- Source gains `corporate`, read from and written as `b:Corporate` (in place of a
  name list: a role holds one or the other, ECMA-376 CT_NameOrCorporateType).
- Citations and bibliography entries write it as it is in every style; it gives
  the source's tag and its place in an author-sorted list, and untagged sources
  are matched by it.
- APA puts its full stop after a group author ("World Health Organization.
  (2021).") instead of relying on the initials for it.
- Create Source has a Corporate Author box under Author; Edit Source opens with
  it checked when the source has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6DxS4tKmWSLCiDPvb4G

* Match untagged sources by corporate author before personal author

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-30 00:12:17 -04:00
ReesandBrandon Thomas 18ae8b2150 DOCX: preserve custom-property bookmark links (#4767) (#4771)
* M12: preserve custom-property bookmark links (#4767)

* M12: preserve bookmark links across property renames (#4767)

* M12: align custom-property preservation status dates (#4767)

* docs: keep the gaps.md note in the status line and revision table

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-30 00:11:27 -04:00
Harrison BoothandBrandon Thomas 48f5a905b1 DOCX: keep run-level snapToGrid and webHidden when saving (#4863)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 23:51:39 -04:00
ReesandBrandon Thomas 4abb77aa70 DOCX: avoid repeated searches when writing comment IDs (#4841)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 23:50:49 -04:00
e855daf279 Built-in heading styles have their outline level without w:outlineLvl, so the Navigation pane lists them (#4696) (#4706)
* #4696: built-in heading styles have their outline level without w:outlineLvl

Some producers write "heading 1"…"heading 9" in styles.xml without w:outlineLvl. Word
gives those built-in styles their outline level anyway (checked: Word for Windows reports
OutlineLevel 1 and 2, whatever the style id), so their headings show in the Navigation
pane. WordCraft read no level, and the Headings tab stayed empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4696: review — w:customStyle="on" marks a custom style too

A custom style named "heading 4" with customStyle="on" (ST_OnOff) was taken for Word's
built-in heading and got an outline level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 23:50:27 -04:00
f774f6f5c0 #4548: comment ranges inside footnotes and endnotes are kept on read (#4550)
* #4548: comment ranges inside footnotes and endnotes are kept on read

read_opened read footnotes.xml and endnotes.xml before comments.xml, so
a comment range inside a note looked its id up in an empty comment map
and was dropped. Comments are now read first. read_comments uses nothing
the notes set up.

Regression test: tests/it/note_comments.rs (synthetic markup).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: file-format-parity row for comment ranges in notes (#4548)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 23:49:56 -04:00
983f1e0267 docx: pin Standard-encryption key derivation to a real-world package (#4472)
Adds the key that Apache POI's Excel-written test file
protected_passtika.xlsx (password "tika") derives, computed from
[MS-OFFCRYPTO] §2.3.4.7 independently of the implementation, so real
Word 2007 files stay pinned.

Also adds the contributor's people.toml entry.

Co-authored-by: Bra1nZ <bra1nz@astraoffice.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 22:24:23 -04:00
Fin WarmanandBrandon Thomas a066fe5f13 Keep zero-sized paragraph frames in normal flow (#2810)
* 411: Keep zero-sized paragraph frames in normal flow

* M2.1: keep frame controls consistent with document defaults (#2810)

* M2.1: check the final frame round trip (#2810)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 21:38:01 -04:00
Dylan M. Taylor 75d30d69b4 Refactor docx for strict Clippy checks 2026-10-28 01:06:47 +00:00
Martin BallandClaude Opus 5.5 cd683a9fd1 #3872: review — privacy rewrite keeps attribute values as written, escaping only quotes
Unescaping and escaping again turned &#10; and &#9; into raw characters that read
back as spaces. The raw value of well-formed markup holds no `<` and every `&` starts
a reference, so only a `"` from a single-quoted value is replaced with &quot;.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
2026-10-27 09:51:52 +00:00
Martin BallandClaude Opus 5.5 932a2e8955 #3872: review — font table found by its stored spelling; privacy rewrite escapes attribute values
- A package storing WORD/FONTTABLE.XML (part names are case-insensitive) loaded no
  embedded fonts and a save dropped the table and every font: lookups used the exact
  constant spelling. They now find the key case-insensitively.
- scrub_private re-pushed attribute values raw (quick-xml push_attribute does not
  escape), so a value read from single quotes became malformed XML. The value is now
  unescaped and escaped again.
- rel_targets matches _rels/ case-insensitively.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
2026-10-27 09:51:52 +00:00
aiongg b8ddca30a1 refactor(docx): split read/chart.rs into read/chart/ 2026-10-26 21:16:49 +00:00
aiongg 80d8a9952a refactor(docx): split write/story.rs into write/story/ 2026-10-26 21:16:49 +00:00
aiongg 125fdc65ef refactor(docx): split read/story.rs into read/story/ 2026-10-26 21:16:49 +00:00
aiongg b1a94d4d4b refactor(docx): split write.rs into write/ 2026-10-26 21:16:49 +00:00
aiongg 0a4f5b8e84 refactor(docx): split read.rs into read/ 2026-10-26 21:16:49 +00:00
aiongg 5b6c461448 refactor(docx): move crypt tests to crypt/tests.rs 2026-10-26 20:54:02 +00:00
aiongg c4fac489c8 refactor(docx): move read/chart tests to read/chart/tests.rs 2026-10-26 20:54:02 +00:00
aiongg 7466008646 refactor(docx): move crates/docx/src/write/mod.rs to crates/docx/src/write.rs 2026-10-26 20:54:01 +00:00
aiongg 07a6cac5bd refactor(docx): move crates/docx/src/read/mod.rs to crates/docx/src/read.rs 2026-10-26 20:54:01 +00:00
Dylan M. Taylor d859c673c1 Sonar: reduce cognitive complexity in highlight, review and outline commands 2026-10-26 16:01:36 -04:00
Martin BallandClaude Opus 5.5 364688a8c3 #3704: review — case-variant zip names keep the spelling a relationship names; chart, font and glossary limits log once per save
Folding zip entries whose names differ only in case now keeps the
spelling a relationship part targets, and the first in byte order only
when no relationship names any of them. The fold logs one warning per
package, not one per entry.

Limits that leave data out now say so, once per document or save:
- a chart past the chart limits on save (series or points dropped);
- embedded fonts past the byte budget (one warning with the count);
- a kept glossary that can't be cleaned of personal information and is
  written from the model instead.

Tests capture the warnings (crate::test_log) and check each is logged
once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:52 -04:00
Martin BallandClaude Opus 5.5 c14e18cfa8 #3704: review — zip entries whose names differ only in case keep one stored spelling
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:52 -04:00
Martin BallandClaude Opus 5.5 eb2f0dab46 #3742: review — a kept XML part with a malformed comment goes out in base64
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:52 -04:00
Martin BallandClaude Opus 5.5 6dc062d132 #3692: a private save keeps the glossary's parts, without names and dates
Ultrareview finding 1. With "remove personal information" or "remove date
and time" set, the glossary was always written from the model (01c78899),
which holds only the entries the reader took: one without a name, past the
entry cap or with a bad content type was lost, and with none left the whole
glossary part went. The kept parts are now written with the same names and
dates removed that the writer leaves out of the main document (annotation
authors become "Author", comment initials "A", annotation and UTC dates
go; a people part's authors and presence too), rewritten with quick-xml and
matched by namespace. A part that can't be rewritten (malformed, UTF-16)
falls back to the model, so the privacy guarantee holds either way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:52 -04:00
Martin BallandClaude Opus 5.5 60a64acec3 #3704: a part named in several casings is kept, cached and written once
Finding 73: Package lookup matched part names case-insensitively, but relationship targets kept the spelling they were written in, so every casing of one part became its own media entry, graphic parse and passthrough key, and a save wrote a full copy per spelling (64 spellings of a 64 KB picture: 64 media parts). Package now keeps a lowercase index, Package::canonical returns the stored spelling, and rels() resolves internal targets to it, so every downstream cache is keyed by the one part; get_shared uses the index instead of a linear scan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:46 -04:00
Martin BallandClaude Opus 5.5 722afa4201 #3742: kept XML is inlined in Flat OPC only when it is one well-formed element
Finding 55: flat.rs xml_fragment inlined kept part bytes into pkg:xmlData after a prefix check only, so a part whose text closed the wrapper (</pkg:xmlData></pkg:part><pkg:part ...>) wrote parts of its own into the saved .xml - e.g. a second core properties part. Now the bytes are inlined only when quick-xml reads them as exactly one balanced element with nothing but whitespace, comments and PIs around it; anything else is written as pkg:binaryData and still round-trips.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:46 -04:00
Martin BallandClaude Opus 5.5 1955793aa6 #3648: a chart part is written within the chart limits whatever the model
Finding 76. chart_xml trusted its ChartSpec to be sanitized, but a .json
document is deserialised without ChartSpec::sanitize, and every series
repeats every category, so a chart past the limits multiplied into a
gigabyte-sized part on save. chart_xml now writes a bounded, sanitized copy:
only the series and points the limits keep are copied before sanitizing.
(Sanitizing on .json open, and SmartArt, are in the engine and not covered
here.)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:46 -04:00
Martin BallandClaude Opus 5.5 639964b0ed #3692: a glossary is written from the model when the privacy settings ask for changes
Finding 72. An unchanged glossary was carried byte for byte, judged by a
fingerprint of its entries that leaves out the document's privacy settings,
so a save with "remove personal information" or "remove date and time" kept
the authors and dates of the glossary's tracked changes. With either setting
on, the glossary is now written from the model, which applies them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:46 -04:00
Martin BallandClaude Opus 5.5 4c5930dac1 #3692: embedded fonts are copied once per file and within a byte budget
Finding 51. embedded_fonts copied a part's bytes for every font-table
reference, so the 64 references allowed to one 256 MB part could make 16 GB of
copies on open. A reference repeating one already taken (same part, key and
name) is skipped, and copies stop at MAX_FONT_BYTES (one part's limit),
checked before each copy is made.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:46 -04:00
Martin BallandClaude Opus 5.5 12afc75411 Finding 42. read_comments checked each candidate id for a comment whose
w:id isn't a number against every id mapped so far, so a comments part of
many non-numeric ids took time quadratic in their count (49,000 comments:
2.4 s, now 0.35 s in a debug build). The ids mapped before the part are
collected into a set once; ids given in the part are already in `entries`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:45 -04:00
Martin BallandClaude Opus 5.5 2337ff75fa #3723: zip entries that fail their CRC check are charged to the package total
Finding 43. Package::open_limited dropped an entry whose read failed before
adding the bytes it had already inflated to the running total, so many
entries with a wrong CRC (checked only after the whole stream inflates) could
each inflate up to the part limit for free. The bytes are now charged first
and the size check runs before the read result is looked at.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:28 -04:00
Martin BallandClaude Opus 5.5 bbeb7e2253 #3642: compound-file precheck shared in wordcraft-doc; .docx encryption sniff and .xls reader call it too
Finding 63 follow-up. #3642 names three cfb call sites: the .doc reader, the encrypted-package sniff and decrypt in docx/crypt.rs (reached for any .docx name), and the .xls reader. The DIFAT precheck moves from docbin to wordcraft_doc::cfb_guard::check (bytes only, no cfb dependency) and runs before every CompoundFile::open in the three crates. The crypt.rs change is only the two precheck calls (open PR #2125 also edits that file).

Regression tests use the shared hostile sample (cfb_guard::repeating_difat_sample): docbin a_difat_repeating_one_fat_sector_is_refused_before_cfb_builds_the_fat, docx tests/malformed.rs and formats sheet::xls a_repeating_difat_is_refused_before_cfb_opens_it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 23:47:30 +00:00
Dylan M. Taylor e5cffe7641 Merge pull request #4233 from VoiceLessQ/fix/small-cleanups
Small fixes: MERGEFIELD in any case, 18 SonarCloud closures, one unused constructor
2026-10-25 16:45:39 -04:00
ea5780d2ab Gap 9: open AES-encrypted Word files (Standard encryption) (#2628)
* Gap 9: open AES-encrypted Word files (Standard encryption)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 9: restore progress-doc rows lost in the main merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 9: Standard package layout test built from the spec

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:48:13 -04:00
divyanshu2074 0696c55698 fix(docx): support sym hex prefixes and normalize break types and note references (#2413) 2026-10-25 10:41:23 -04:00
MarekSpodBiedry 14f0eec255 Stretch justified lines that end in a manual line break (#3924) (#4023) 2026-10-25 10:27:35 -04:00
Michael HigginsandClaude Opus 5.5 9003e2b0af Limit DOCX XML attributes (#3857)
Caps attributes per XML element at 1024 with a Limit error.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:59:48 -04:00
VoiceLessQ 65a0dc8dc6 Method paths for 18 closures that only call a method (SonarCloud S1612)
SonarCloud's analysis of main at b4da59f0c lists 23 closures such as `|v| v.as_slice()` that hand their argument straight to one method. This passes the method itself: `Vec::as_slice`, `str::to_ascii_lowercase`, `f64::total_cmp` and the like, all forms the repo already uses elsewhere. Behaviour is unchanged.

Five stay. The four in crates/proof/src/es/mod.rs sit in code that #4191 replaces, and its replacement already uses `PoisonError::into_inner`. In crates/engine/src/io/safe_save.rs the path would be `<dyn std::error::Error + Send + Sync>::is::<Guided>`, which reads worse than `|inner| inner.is::<Guided>()`.
2026-10-25 01:19:28 -02:00
Dylan M. TaylorandClaude Sonnet 5.5 b4da59f0c3 security: require the data integrity check on encrypted DOCX packages (#4223)
An agile EncryptionInfo without dataIntegrity made decrypt skip the HMAC, so
altered ciphertext or an altered StreamSize came back as plaintext. The file
no longer decides whether it is authenticated: a package without dataIntegrity
is refused.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 21:10:15 -04:00
aiongg 49d3551ca6 DOCX: a comment's dateUtc is written only from a known UTC time, not from a local w:date with a Z (#4079) 2026-10-24 21:31:35 +00:00
aiongg dd8ea27b99 Merge PR #4039: #3889, #3891: Update Table keeps the styles and columns of a localized table of contents 2026-10-24 21:05:19 +00:00
aiongg 39bf1a6c76 Merge PR #4074: cu20we: fix box float paragraph origins and preserve shape line caps 2026-10-24 20:42:02 +00:00