1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//! Shared quick-xml decoding helpers.
//!
//! ## quick-xml 0.40+ entity model
//!
//! quick-xml 0.40 redesigned entity handling. A text node such as `A & B`
//! is no longer delivered as a single [`Event::Text`] with the entity resolved
//! inline. Instead the reader splits it into `Text("A ")`, `GeneralRef("amp")`,
//! `Text(" B")`. Every loop that accumulates text therefore MUST handle
//! [`Event::GeneralRef`] via [`resolve_general_ref`], or every
//! `& < > &#NN;` in a document silently disappears.
//!
//! A raw (dangling) `&` that does not form a reference is *ill-formed* XML and,
//! by default, aborts the whole part with `Error::IllFormed(UnclosedReference)`.
//! undoc's robustness goal is graceful degradation on malformed input, so every
//! reader is built via [`reader_for`], which enables `allow_dangling_amp` — the
//! stray `&` then arrives as raw [`Event::Text`] bytes and is preserved verbatim
//! (matching quick-xml 0.37's lenient behavior).
//!
//! ## End-of-line normalization
//!
//! quick-xml does not perform XML §2.11 EOL normalization, and CRs also re-enter
//! via ` `/`
` character references (how Excel stores in-cell line breaks,
//! issue #7). Because ` ` now arrives as *two* separate `GeneralRef`
//! events, a lone CR cannot be collapsed at the event level. [`decode_text_lossy`]
//! still normalizes literal CR/CRLF inside a single text node, but callers that
//! reconstruct in-cell text from character references (XLSX) must additionally
//! run [`normalize_line_endings`] once on the fully accumulated string at the
//! flush point. The function is idempotent (`\r`-free input returns borrowed), so
//! the double pass is safe.
//!
//! Scope note (deliberate): only the XLSX paths add flush-point normalization,
//! because character-reference CRs are an Excel-specific convention. DOCX/PPTX
//! encode line breaks as elements (`<w:br/>`, `<w:cr/>`, `<a:br/>`), not ` `,
//! so their direct-run text (a `GeneralRef` that becomes its own run) is not
//! re-normalized. A literal CR inside a single text node is still handled by
//! [`decode_text_lossy`]; a hypothetical ` ` inside a `<w:t>`/`<a:t>` run
//! would survive as a raw CR — accepted, as no conforming producer emits it.
//! DOCX body/table/textbox paragraphs are unaffected regardless, since they
//! round-trip through re-serialized XML that is normalized on the second parse.
//!
//! CDATA (`Event::CData`) is not handled anywhere: no loop handled it before
//! this migration and OOXML does not use CDATA sections, so there is no
//! regression. Add a `CData` arm alongside the `GeneralRef` arms if that ever
//! changes.
//!
//! [`Event::Text`]: quick_xml::events::Event::Text
//! [`Event::GeneralRef`]: quick_xml::events::Event::GeneralRef
use Cow;
use resolve_predefined_entity;
use ;
use Reader;
use crate;
/// Build a [`Reader`] with undoc's shared leniency policy.
///
/// Enables `allow_dangling_amp` so a stray `&` (ill-formed XML from a
/// non-conforming producer) is delivered as raw [`Event::Text`] rather than
/// aborting the entire part with `Error::IllFormed(UnclosedReference)`. This
/// preserves undoc's graceful-degradation contract under quick-xml 0.40+, where
/// the check is on by default.
///
/// Callers set `trim_text` themselves — it differs per call site (text content
/// must preserve `xml:space="preserve"` whitespace; attribute-only loops may
/// trim).
///
/// [`Event::Text`]: quick_xml::events::Event::Text
pub
/// Resolve an [`Event::GeneralRef`] entity reference to its string value.
///
/// Handles numeric character references (`0`, `0`) and the five
/// predefined XML entities (`& < > " '`). An unrecognized
/// *named* entity (custom DTD or an empty `&;` token) is preserved literally as
/// `&name;` to avoid silent data loss.
///
/// A *malformed numeric* reference (`&#xZZ;`, a codepoint above U+10FFFF, a lone
/// surrogate, or an overflowing value) yields the empty string — it is dropped,
/// not preserved. This is a deliberate, minor divergence from quick-xml 0.37's
/// lenient path, which surfaced such tokens as raw `&#xZZ;` text. No conforming
/// OOXML producer emits an out-of-range character reference, so the input is
/// unreachable in practice; dropping keeps the numeric branch panic-free and
/// mirrors the sibling unhwp migration.
///
/// CR (` `) is intentionally NOT normalized here — normalization must happen
/// once at the flush point on the fully accumulated string, because a CRLF pair
/// arrives as two separate references (see module docs and
/// [`normalize_line_endings`]).
///
/// [`Event::GeneralRef`]: quick_xml::events::Event::GeneralRef
pub
/// Normalize line endings to LF.
///
/// Covers two layers quick-xml leaves untouched: XML §2.11 end-of-line
/// normalization for literal CR/CRLF from non-conforming producers, and CRs
/// that re-enter via ` `/`
` character references — how Excel stores
/// in-cell line breaks. undoc extracts text, where a CR carries no meaning
/// distinct from LF but breaks Markdown pipe tables, so both collapse to LF.
///
/// Idempotent: input without `\r` is returned borrowed and unchanged.
pub
/// Decode a [`Event::Text`] event into an owned `String` using lossy UTF-8
/// substitution.
///
/// Under quick-xml 0.40+ text events no longer carry entity references (those
/// arrive as [`Event::GeneralRef`] — see [`resolve_general_ref`]), so this only
/// decodes raw bytes and normalizes literal CR/CRLF within the node. Invalid
/// UTF-8 is replaced with U+FFFD rather than surfacing as an error, matching the
/// graceful-degradation goal for content text (paragraphs, cells, runs, labels).
///
/// [`Event::Text`]: quick_xml::events::Event::Text
/// [`Event::GeneralRef`]: quick_xml::events::Event::GeneralRef
pub
/// Decode a [`Event::Text`] event into an owned `String`, requiring valid UTF-8.
///
/// Intended for metadata paths where invalid UTF-8 must surface as
/// `Error::XmlParse` with a location context rather than be silently replaced.
///
/// [`Event::Text`]: quick_xml::events::Event::Text
pub