From 0316247f36320c7b386bac14d2dc1ebab0e5e4dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 01:52:32 +0000 Subject: [PATCH 1/2] build(deps): bump quick-xml from 0.41.0 to 0.42.0 Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.41.0 to 0.42.0. - [Release notes](https://github.com/tafia/quick-xml/releases) - [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md) - [Commits](https://github.com/tafia/quick-xml/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: quick-xml dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- apps/big-search/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ade195d..a55f679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1492,9 +1492,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", ] diff --git a/apps/big-search/Cargo.toml b/apps/big-search/Cargo.toml index 0df3465..4224037 100644 --- a/apps/big-search/Cargo.toml +++ b/apps/big-search/Cargo.toml @@ -23,7 +23,7 @@ imagesize = { version = "0.14" } libc = { version = "0.2.186" } log = { version = "0.4.33" } notify = { version = "8.2" } -quick-xml = { version = "0.41.0" } +quick-xml = { version = "0.42.0" } rusqlite = { version = "0.40.1", default-features = false, features = ["cache"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = { version = "1.0.150" } From 6bddd4a16d1371974a35706b940860857fdc4504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Gon=C3=A7alves?= Date: Sun, 11 Oct 2026 23:04:13 -0300 Subject: [PATCH 2/2] big-search: read XML names and text as str for quick-xml 0.42 quick-xml 0.42 decodes names, attribute values and text itself and hands them out as str. A part with bytes that are not UTF-8 now ends where the damage starts instead of being read lossily. --- apps/big-search/src/extract.rs | 44 ++++----- apps/big-search/src/extract/epub.rs | 24 ++--- apps/big-search/src/prominent.rs | 141 +++++++++++++--------------- 3 files changed, 96 insertions(+), 113 deletions(-) diff --git a/apps/big-search/src/extract.rs b/apps/big-search/src/extract.rs index 0c1f6fe..b7dc673 100644 --- a/apps/big-search/src/extract.rs +++ b/apps/big-search/src/extract.rs @@ -607,10 +607,10 @@ pub fn pages_with(path: &Path, terms: &[String]) -> Vec { "pdf" => crate::extract_pdf::for_each_pdf_page(path, &mut |text| pages.test(text)), "pptx" => for_each_pptx_slide(path, &mut |text| pages.test(text)), "docx" | "odt" | "ods" | "odp" => { - let (part, break_tag): (&str, &[u8]) = if ext == "docx" { - ("word/document.xml", b"w:lastRenderedPageBreak") + let (part, break_tag): (&str, &str) = if ext == "docx" { + ("word/document.xml", "w:lastRenderedPageBreak") } else { - ("content.xml", b"text:soft-page-break") + ("content.xml", "text:soft-page-break") }; // No break markers → page layout unknown (the app didn't cache it). // Report no page rather than guessing "page 1". PDF and PPTX page @@ -692,7 +692,7 @@ fn for_each_pptx_slide(path: &Path, on_page: &mut dyn FnMut(&str)) { fn for_each_office_page( path: &Path, part: &str, - break_tag: &[u8], + break_tag: &str, on_page: &mut dyn FnMut(&str), ) -> bool { use quick_xml::events::Event; @@ -765,16 +765,16 @@ pub(crate) fn zip_entry_bytes(path: &Path, name: &str) -> Option> { } /// True if `e` is a cached page-break marker or an explicit `w:br w:type="page"`. -fn is_page_break(e: &quick_xml::events::BytesStart, break_tag: &[u8]) -> bool { +fn is_page_break(e: &quick_xml::events::BytesStart, break_tag: &str) -> bool { let name = e.name(); if name.as_ref() == break_tag { return true; } - if name.as_ref() == b"w:br" { + if name.as_ref() == "w:br" { return e .attributes() .flatten() - .any(|a| a.key.as_ref() == b"w:type" && a.value.as_ref() == b"page"); + .any(|a| a.key.as_ref() == "w:type" && a.value.as_ref() == "page"); } false } @@ -806,7 +806,7 @@ pub fn xml_text(xml: &[u8], out: &mut String) { /// text deleted under change tracking and comments are not the document's text. pub fn xml_text_lines(xml: &[u8], out: &mut String) { use quick_xml::events::Event; - let hidden = |name: &[u8]| matches!(name, b"w:instrText" | b"w:delText" | b"office:annotation"); + let hidden = |name: &str| matches!(name, "w:instrText" | "w:delText" | "office:annotation"); let mut reader = quick_xml::Reader::from_reader(xml); let mut buf = Vec::new(); let mut hidden_depth = 0usize; @@ -820,15 +820,15 @@ pub fn xml_text_lines(xml: &[u8], out: &mut String) { if hidden_depth > 0 => {} Ok(e @ (Event::Text(_) | Event::GeneralRef(_))) => push_xml_text(&e, out), Ok(Event::End(e)) => match e.name().as_ref() { - b"w:p" | b"text:p" | b"text:h" | b"a:p" => out.push('\n'), - b"w:tc" | b"table:table-cell" => out.push('\t'), + "w:p" | "text:p" | "text:h" | "a:p" => out.push('\n'), + "w:tc" | "table:table-cell" => out.push('\t'), _ => {} }, Ok(Event::Empty(e)) => match e.name().as_ref() { - b"w:br" | b"w:cr" | b"text:line-break" => out.push('\n'), - b"w:tab" | b"text:tab" => out.push('\t'), - b"text:s" => out.push(' '), - b"w:p" | b"text:p" | b"text:h" => out.push('\n'), + "w:br" | "w:cr" | "text:line-break" => out.push('\n'), + "w:tab" | "text:tab" => out.push('\t'), + "text:s" => out.push(' '), + "w:p" | "text:p" | "text:h" => out.push('\n'), _ => {} }, Ok(Event::Eof) | Err(_) => break, @@ -841,22 +841,18 @@ pub fn xml_text_lines(xml: &[u8], out: &mut String) { /// The text of a text event or an entity reference, appended as written. /// /// quick-xml reports `&` and `ó` as events of their own, between the -/// text around them. Bytes that are not valid UTF-8 are read lossily rather -/// than dropped: an office part is UTF-8 by specification, so this only -/// happens to a damaged file, and a damaged file should still give up whatever -/// text it has. +/// text around them. The reader stops at bytes that are not valid UTF-8; an +/// office part is UTF-8 by specification, so only a damaged file loses the text +/// after the damage. fn push_xml_text(event: &quick_xml::events::Event<'_>, out: &mut String) { use quick_xml::events::Event; match event { - Event::Text(t) => match t.decode() { - Ok(text) => out.push_str(&text), - Err(_) => out.push_str(&String::from_utf8_lossy(t)), - }, + Event::Text(t) => out.push_str(t), Event::GeneralRef(r) => { if let Ok(Some(c)) = r.resolve_char_ref() { out.push(c); - } else if let Ok(name) = r.decode() { - out.push_str(match name.as_ref() { + } else { + out.push_str(match &**r { "amp" => "&", "lt" => "<", "gt" => ">", diff --git a/apps/big-search/src/extract/epub.rs b/apps/big-search/src/extract/epub.rs index aa33e72..7d7e5e2 100644 --- a/apps/big-search/src/extract/epub.rs +++ b/apps/big-search/src/extract/epub.rs @@ -67,15 +67,15 @@ pub(super) fn read_epub(path: &Path) -> Option<(String, Option)> { } /// The local name of an XML element, without its namespace prefix. -fn local_name(e: &quick_xml::events::BytesStart<'_>) -> Vec { - e.local_name().as_ref().to_vec() +fn local_name(e: &quick_xml::events::BytesStart<'_>) -> String { + e.local_name().as_ref().to_owned() } -fn xml_attribute(e: &quick_xml::events::BytesStart<'_>, name: &[u8]) -> Option { +fn xml_attribute(e: &quick_xml::events::BytesStart<'_>, name: &str) -> Option { e.attributes() .flatten() .find(|a| a.key.local_name().as_ref() == name) - .map(|a| decode_entities(&String::from_utf8_lossy(&a.value))) + .map(|a| decode_entities(&a.value)) } /// The package document `META-INF/container.xml` points to. @@ -85,8 +85,8 @@ fn epub_package(container: &[u8]) -> Option { let mut buf = Vec::new(); loop { match reader.read_event_into(&mut buf) { - Ok(Event::Start(e) | Event::Empty(e)) if local_name(&e) == b"rootfile" => { - return xml_attribute(&e, b"full-path"); + Ok(Event::Start(e) | Event::Empty(e)) if local_name(&e) == "rootfile" => { + return xml_attribute(&e, "full-path"); } Ok(Event::Eof) | Err(_) => return None, _ => {} @@ -105,22 +105,22 @@ fn epub_spine(opf: &[u8]) -> (Option, Vec) { let mut title_text = String::new(); loop { match reader.read_event_into(&mut buf) { - Ok(Event::Start(e) | Event::Empty(e)) => match local_name(&e).as_slice() { - b"item" => { + Ok(Event::Start(e) | Event::Empty(e)) => match local_name(&e).as_str() { + "item" => { if let (Some(id), Some(href)) = - (xml_attribute(&e, b"id"), xml_attribute(&e, b"href")) + (xml_attribute(&e, "id"), xml_attribute(&e, "href")) { hrefs.insert(id, href); } } - b"itemref" => order.extend(xml_attribute(&e, b"idref")), - b"title" if title.is_none() => in_title = true, + "itemref" => order.extend(xml_attribute(&e, "idref")), + "title" if title.is_none() => in_title = true, _ => {} }, Ok(e @ (Event::Text(_) | Event::GeneralRef(_))) if in_title => { push_xml_text(&e, &mut title_text); } - Ok(Event::End(e)) if in_title && e.local_name().as_ref() == b"title" => { + Ok(Event::End(e)) if in_title && e.local_name().as_ref() == "title" => { in_title = false; let text = title_text.trim(); if !text.is_empty() { diff --git a/apps/big-search/src/prominent.rs b/apps/big-search/src/prominent.rs index 36ebad1..405c4a9 100644 --- a/apps/big-search/src/prominent.rs +++ b/apps/big-search/src/prominent.rs @@ -103,25 +103,22 @@ fn ranked(lines: Vec<(String, f32)>, join: bool) -> Vec { .collect() } -fn attr(e: &BytesStart<'_>, name: &[u8]) -> Option { +fn attr(e: &BytesStart<'_>, name: &str) -> Option { e.attributes() .flatten() .find(|a| a.key.as_ref() == name) - .map(|a| String::from_utf8_lossy(&a.value).into_owned()) + .map(|a| a.value.into_owned()) } /// Text of a text event or an entity reference, decoded. fn push_event_text(event: &Event<'_>, out: &mut String) { match event { - Event::Text(t) => match t.decode() { - Ok(text) => out.push_str(&text), - Err(_) => out.push_str(&String::from_utf8_lossy(t)), - }, + Event::Text(t) => out.push_str(t), Event::GeneralRef(r) => { if let Ok(Some(c)) = r.resolve_char_ref() { out.push(c); - } else if let Ok(name) = r.decode() { - out.push_str(match name.as_ref() { + } else { + out.push_str(match &**r { "amp" => "&", "lt" => "<", "gt" => ">", @@ -166,28 +163,23 @@ fn pdf_lines(xhtml: &[u8]) -> Vec<(String, f32)> { loop { let event = reader.read_event_into(&mut buf); match &event { - Ok(Event::Start(e)) if e.name().as_ref() == b"line" => { - let coord = |n: &[u8]| attr(e, n).and_then(|v| v.parse::().ok()); - line = match ( - coord(b"xMin"), - coord(b"yMin"), - coord(b"xMax"), - coord(b"yMax"), - ) { + Ok(Event::Start(e)) if e.name().as_ref() == "line" => { + let coord = |n: &str| attr(e, n).and_then(|v| v.parse::().ok()); + line = match (coord("xMin"), coord("yMin"), coord("xMax"), coord("yMax")) { (Some(x0), Some(y0), Some(x1), Some(y1)) => Some((y1 - y0, x1 - x0)), _ => None, }; text.clear(); } - Ok(Event::Start(e)) if e.name().as_ref() == b"word" => in_word = true, - Ok(Event::End(e)) if e.name().as_ref() == b"word" => { + Ok(Event::Start(e)) if e.name().as_ref() == "word" => in_word = true, + Ok(Event::End(e)) if e.name().as_ref() == "word" => { in_word = false; text.push(' '); } Ok(ev @ (Event::Text(_) | Event::GeneralRef(_))) if in_word => { push_event_text(ev, &mut text); } - Ok(Event::End(e)) if e.name().as_ref() == b"line" => { + Ok(Event::End(e)) if e.name().as_ref() == "line" => { if let Some((height, width)) = line.take() && height <= width { @@ -256,20 +248,20 @@ fn read_odf_styles(xml: &[u8], styles: &mut Styles) { let event = reader.read_event_into(&mut buf); match &event { Ok(Event::Start(e) | Event::Empty(e)) => match e.name().as_ref() { - b"style:style" => { - if let Some(name) = attr(e, b"style:name") { + "style:style" => { + if let Some(name) = attr(e, "style:name") { styles .by_name - .insert(name.clone(), (attr(e, b"style:parent-style-name"), None)); + .insert(name.clone(), (attr(e, "style:parent-style-name"), None)); current = matches!(event, Ok(Event::Start(_))).then_some(name); } } - b"style:default-style" => { + "style:default-style" => { in_default = matches!(event, Ok(Event::Start(_))) - && attr(e, b"style:family").as_deref() == Some("paragraph"); + && attr(e, "style:family").as_deref() == Some("paragraph"); } - b"style:text-properties" => { - if let Some(size) = attr(e, b"fo:font-size") { + "style:text-properties" => { + if let Some(size) = attr(e, "fo:font-size") { if in_default { styles.default = odf_size(&size, 12.0); } else if let Some(entry) = @@ -282,8 +274,8 @@ fn read_odf_styles(xml: &[u8], styles: &mut Styles) { _ => {} }, Ok(Event::End(e)) => match e.name().as_ref() { - b"style:style" => current = None, - b"style:default-style" => in_default = false, + "style:style" => current = None, + "style:default-style" => in_default = false, _ => {} }, Ok(Event::Eof) | Err(_) => break, @@ -318,20 +310,20 @@ fn odf_opening_paragraphs(content: &[u8], styles: &Styles) -> Vec<(String, f32)> loop { let event = reader.read_event_into(&mut buf); match &event { - Ok(Event::Start(e)) if e.name().as_ref() == b"office:annotation" => hidden += 1, - Ok(Event::End(e)) if e.name().as_ref() == b"office:annotation" => { + Ok(Event::Start(e)) if e.name().as_ref() == "office:annotation" => hidden += 1, + Ok(Event::End(e)) if e.name().as_ref() == "office:annotation" => { hidden = hidden.saturating_sub(1); } _ if hidden > 0 => {} - Ok(Event::Start(e)) if matches!(e.name().as_ref(), b"text:p" | b"text:h") => { - let size = styles.size(attr(e, b"text:style-name").as_deref(), 12.0); + Ok(Event::Start(e)) if matches!(e.name().as_ref(), "text:p" | "text:h") => { + let size = styles.size(attr(e, "text:style-name").as_deref(), 12.0); paragraph = Some((String::new(), size, 0.0)); spans.clear(); } - Ok(Event::Start(e)) if e.name().as_ref() == b"text:span" => { + Ok(Event::Start(e)) if e.name().as_ref() == "text:span" => { spans.push(odf_span_size(e, styles, &spans, paragraph.as_ref())); } - Ok(Event::End(e)) if e.name().as_ref() == b"text:span" => { + Ok(Event::End(e)) if e.name().as_ref() == "text:span" => { spans.pop(); } Ok(ev @ (Event::Text(_) | Event::GeneralRef(_))) => { @@ -340,16 +332,13 @@ fn odf_opening_paragraphs(content: &[u8], styles: &Styles) -> Vec<(String, f32)> } } Ok(Event::Empty(e)) - if matches!( - e.name().as_ref(), - b"text:s" | b"text:tab" | b"text:line-break" - ) => + if matches!(e.name().as_ref(), "text:s" | "text:tab" | "text:line-break") => { if let Some((text, _, _)) = paragraph.as_mut() { text.push(' '); } } - Ok(Event::End(e)) if matches!(e.name().as_ref(), b"text:p" | b"text:h") => { + Ok(Event::End(e)) if matches!(e.name().as_ref(), "text:p" | "text:h") => { if let Some((text, _, largest)) = paragraph.take() && keep_opening(&mut lines, text, largest) { @@ -372,7 +361,7 @@ fn odf_span_size( paragraph: Option<&(String, f32, f32)>, ) -> f32 { let outer = spans.last().copied().or(paragraph.map(|p| p.1)); - attr(e, b"text:style-name") + attr(e, "text:style-name") .and_then(|n| styles.by_name.get(&n).and_then(|s| s.1.clone())) .and_then(|v| odf_size(&v, outer.unwrap_or(12.0))) .or(outer) @@ -394,22 +383,22 @@ fn read_docx_styles(xml: &[u8]) -> (DocxStyles, f32) { let event = reader.read_event_into(&mut buf); match &event { Ok(Event::Start(e) | Event::Empty(e)) => match e.name().as_ref() { - b"w:style" => { - current = attr(e, b"w:styleId"); + "w:style" => { + current = attr(e, "w:styleId"); if let Some(id) = ¤t { styles.insert(id.clone(), (None, None)); } } - b"w:rPrDefault" => in_defaults = true, - b"w:basedOn" => { - if let (Some(id), Some(base)) = (¤t, attr(e, b"w:val")) + "w:rPrDefault" => in_defaults = true, + "w:basedOn" => { + if let (Some(id), Some(base)) = (¤t, attr(e, "w:val")) && let Some(style) = styles.get_mut(id) { style.0 = Some(base); } } - b"w:sz" => { - let size = attr(e, b"w:val").and_then(|v| v.parse::().ok()); + "w:sz" => { + let size = attr(e, "w:val").and_then(|v| v.parse::().ok()); if in_defaults { default = size.unwrap_or(default); } else if let Some(style) = current.as_ref().and_then(|id| styles.get_mut(id)) { @@ -419,8 +408,8 @@ fn read_docx_styles(xml: &[u8]) -> (DocxStyles, f32) { _ => {} }, Ok(Event::End(e)) => match e.name().as_ref() { - b"w:style" => current = None, - b"w:rPrDefault" => in_defaults = false, + "w:style" => current = None, + "w:rPrDefault" => in_defaults = false, _ => {} }, Ok(Event::Eof) | Err(_) => break, @@ -470,39 +459,37 @@ fn docx_opening_paragraphs(xml: &[u8], styles: &DocxStyles, default: f32) -> Vec loop { let event = reader.read_event_into(&mut buf); match &event { - Ok(Event::Start(e)) if e.name().as_ref() == b"w:p" => { + Ok(Event::Start(e)) if e.name().as_ref() == "w:p" => { paragraph = Some((String::new(), None, 0.0)); } - Ok(Event::Start(e)) if e.name().as_ref() == b"w:pPr" => in_ppr = true, - Ok(Event::End(e)) if e.name().as_ref() == b"w:pPr" => in_ppr = false, - Ok(Event::Start(e) | Event::Empty(e)) if e.name().as_ref() == b"w:pStyle" => { + Ok(Event::Start(e)) if e.name().as_ref() == "w:pPr" => in_ppr = true, + Ok(Event::End(e)) if e.name().as_ref() == "w:pPr" => in_ppr = false, + Ok(Event::Start(e) | Event::Empty(e)) if e.name().as_ref() == "w:pStyle" => { if let Some((_, style, _)) = paragraph.as_mut() { - *style = docx_style_size(styles, attr(e, b"w:val").as_deref()); + *style = docx_style_size(styles, attr(e, "w:val").as_deref()); } } - Ok(Event::Start(e)) if e.name().as_ref() == b"w:r" => run = None, + Ok(Event::Start(e)) if e.name().as_ref() == "w:r" => run = None, // The paragraph mark's own size (inside w:pPr) is not its text's. - Ok(Event::Start(e) | Event::Empty(e)) - if !in_ppr && e.name().as_ref() == b"w:rStyle" => - { - run = run.or(docx_style_size(styles, attr(e, b"w:val").as_deref())); + Ok(Event::Start(e) | Event::Empty(e)) if !in_ppr && e.name().as_ref() == "w:rStyle" => { + run = run.or(docx_style_size(styles, attr(e, "w:val").as_deref())); } - Ok(Event::Start(e) | Event::Empty(e)) if !in_ppr && e.name().as_ref() == b"w:sz" => { - run = attr(e, b"w:val").and_then(|v| v.parse().ok()).or(run); + Ok(Event::Start(e) | Event::Empty(e)) if !in_ppr && e.name().as_ref() == "w:sz" => { + run = attr(e, "w:val").and_then(|v| v.parse().ok()).or(run); } - Ok(Event::Start(e)) if e.name().as_ref() == b"w:t" => in_text = true, - Ok(Event::End(e)) if e.name().as_ref() == b"w:t" => in_text = false, + Ok(Event::Start(e)) if e.name().as_ref() == "w:t" => in_text = true, + Ok(Event::End(e)) if e.name().as_ref() == "w:t" => in_text = false, Ok(ev @ (Event::Text(_) | Event::GeneralRef(_))) if in_text => { if let Some((text, style, largest)) = paragraph.as_mut() { push_sized_text(ev, text, largest, run.or(*style).unwrap_or(default)); } } - Ok(Event::Empty(e)) if matches!(e.name().as_ref(), b"w:tab" | b"w:br" | b"w:cr") => { + Ok(Event::Empty(e)) if matches!(e.name().as_ref(), "w:tab" | "w:br" | "w:cr") => { if let Some((text, _, _)) = paragraph.as_mut() { text.push(' '); } } - Ok(Event::End(e)) if e.name().as_ref() == b"w:p" => { + Ok(Event::End(e)) if e.name().as_ref() == "w:p" => { if let Some((text, _, largest)) = paragraph.take() && keep_opening(&mut lines, text, largest / 2.0) { @@ -536,26 +523,26 @@ fn pptx_title(path: &Path) -> Option { Ok(Event::Start(e)) => { depth += 1; match e.name().as_ref() { - b"p:sp" => { + "p:sp" => { shape_start = Some(depth); is_title = false; text.clear(); } - b"a:t" => in_text = true, + "a:t" => in_text = true, _ => {} } } - Ok(Event::Empty(e)) if e.name().as_ref() == b"p:ph" => { - is_title |= matches!(attr(e, b"type").as_deref(), Some("title" | "ctrTitle")); + Ok(Event::Empty(e)) if e.name().as_ref() == "p:ph" => { + is_title |= matches!(attr(e, "type").as_deref(), Some("title" | "ctrTitle")); } Ok(ev @ (Event::Text(_) | Event::GeneralRef(_))) if in_text => { push_event_text(ev, &mut text) } Ok(Event::End(e)) => { match e.name().as_ref() { - b"a:t" => in_text = false, - b"a:p" => text.push(' '), - b"p:sp" if shape_start == Some(depth) => { + "a:t" => in_text = false, + "a:p" => text.push(' '), + "p:sp" if shape_start == Some(depth) => { if is_title && !text.trim().is_empty() { return Some(text.split_whitespace().collect::>().join(" ")); } @@ -582,20 +569,20 @@ fn odp_title(path: &Path) -> Option { loop { let event = reader.read_event_into(&mut buf); match &event { - Ok(Event::Start(e)) if e.name().as_ref() == b"draw:page" => { + Ok(Event::Start(e)) if e.name().as_ref() == "draw:page" => { pages += 1; if pages > 1 { return None; } } - Ok(Event::Start(e)) if e.name().as_ref() == b"draw:frame" => { - in_title = attr(e, b"presentation:class").as_deref() == Some("title"); + Ok(Event::Start(e)) if e.name().as_ref() == "draw:frame" => { + in_title = attr(e, "presentation:class").as_deref() == Some("title"); } Ok(ev @ (Event::Text(_) | Event::GeneralRef(_))) if in_title => { push_event_text(ev, &mut text) } - Ok(Event::End(e)) if in_title && e.name().as_ref() == b"text:p" => text.push(' '), - Ok(Event::End(e)) if e.name().as_ref() == b"draw:frame" && in_title => { + Ok(Event::End(e)) if in_title && e.name().as_ref() == "text:p" => text.push(' '), + Ok(Event::End(e)) if e.name().as_ref() == "draw:frame" && in_title => { let title = text.split_whitespace().collect::>().join(" "); return (!title.is_empty()).then_some(title); }