port more to rust

This commit is contained in:
Jacob Lifshay 2026-01-02 02:47:21 -08:00
parent 8643d47338
commit 104ee37933
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
2 changed files with 776 additions and 0 deletions

View file

@ -1,8 +1,16 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use non_nan_float::NonNaNF32;
use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
fmt,
sync::OnceLock,
};
mod quad_tree;
mod xml_tree;
mod non_nan_float {
#[derive(Default, PartialEq, PartialOrd, Clone, Copy)]
@ -44,6 +52,14 @@ mod non_nan_float {
self.partial_cmp(other).expect("known to be non-NaN")
}
}
impl std::ops::Neg for NonNaNF32 {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
}
macro_rules! make_enum_font {
@ -514,4 +530,566 @@ impl Font {
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Char {
font: Font,
text: String,
adv: NonNaNF32,
min_x: NonNaNF32,
min_y: NonNaNF32,
max_x: NonNaNF32,
max_y: NonNaNF32,
}
impl Char {
fn width(&self) -> f32 {
self.max_x.get() - self.min_x.get()
}
fn height(&self) -> f32 {
self.max_y.get() - self.min_y.get()
}
fn top_down_left_to_right_sort_key(&self) -> impl Ord {
(-self.min_y, self.min_x)
}
}
const COLUMN_SPLIT_X: f32 = 300.0;
const PAGE_BODY_MAX_X: f32 = 600.0;
const PAGE_BODY_MIN_X: f32 = 50.0;
const PAGE_BODY_MAX_Y: f32 = 780.0;
const PAGE_BODY_MIN_Y: f32 = 45.0;
const ONE_TITLE_LINE_SPLIT_Y: f32 = 734.0;
const TWO_TITLE_LINES_SPLIT_Y: f32 = 715.0;
const INSN_BIT_FIELDS_PREFIX_TEXT_TOP_PAD_HEIGHT: f32 = 29.938;
const INSN_BIT_FIELDS_AFFIX_TEXT_TO_BOX_TOP_HEIGHT: f32 = 9.278;
const INSN_BIT_FIELDS_PREFIX_BOX_BOTTOM_TO_SUFFIX_TEXT_HEIGHT: f32 = 20.971;
const INSN_BIT_FIELDS_TOP_PAD_HEIGHT: f32 = 20.175;
const INSN_BIT_FIELDS_TOP_PAD_HEIGHT2: f32 = 14.694;
const INSN_BIT_FIELDS_BOX_HEIGHT: f32 = 22.317;
const INSN_SP_REGS_ALTERED_REGISTER_COLUMN_X: f32 = 34.405;
const INSN_SP_REGS_ALTERED_FIELDS_COLUMN_X: f32 = 86.692;
const INSN_SP_REGS_ALTERED_FIELDS_CONDS_SPLIT_X: f32 = 188.74;
#[derive(Clone)]
struct ParsedTextLine {
element: xml_tree::Element,
regular_min_y: f32,
regular_max_y: f32,
fonts: TextLineFonts,
chars: Vec<Char>,
preceding_blank_lines: u32,
}
impl ParsedTextLine {
fn regular_height(&self) -> f32 {
self.regular_max_y - self.regular_min_y
}
fn get_header_text(&self) -> Option<String> {
assert_eq!(self.fonts, TextLineFonts::InsnDescFonts);
if !self.element.text.trim().is_empty() {
return None;
}
if !self.element.tail.trim().is_empty() {
return None;
}
let [b] = &*self.element.children else {
return None;
};
if b.tag.normal() != Some("b") {
return None;
}
if b.children.len() != 0 {
return None;
}
let text = self.element.inner_text();
// should also check titlecase, but rust doesn't include that in std
if text.ends_with(":") && text.chars().next().is_some_and(|ch| ch.is_uppercase()) {
Some(text)
} else {
None
}
}
fn write_xml(&self, parent: &mut xml_tree::Element, trailing_nl: bool) {
for _ in 0..self.preceding_blank_lines {
parent.sub_element("br".into(), []).tail = "\n".into();
}
if let Some(last_child) = parent.children.last_mut() {
last_child.tail += &self.element.text;
} else {
parent.text += &self.element.text;
}
parent.children.extend_from_slice(&self.element.children);
if trailing_nl {
parent.sub_element("br".into(), []).tail = "\n".into();
}
}
fn write_xml_lines(
lines: impl IntoIterator<Item: Borrow<ParsedTextLine>>,
parent: &mut xml_tree::Element,
trailing_nl: bool,
preceding_nl: bool,
) {
if preceding_nl {
parent.sub_element("br".into(), []).tail = "\n".into();
}
let mut first = true;
for line in lines {
let line = line.borrow();
if first {
first = false;
} else {
parent.sub_element("br".into(), []).tail = "\n".into();
}
line.write_xml(parent, false);
}
if trailing_nl {
parent.sub_element("br".into(), []).tail = "\n".into();
}
}
}
impl fmt::Debug for ParsedTextLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
element,
regular_min_y,
regular_max_y,
fonts,
chars,
preceding_blank_lines,
} = self;
f.debug_struct("ParsedTextLine")
.field("element", &format_args!("{element}"))
.field("regular_min_y", regular_min_y)
.field("regular_max_y", regular_max_y)
.field("fonts", fonts)
.field("chars", chars)
.field("preceding_blank_lines", preceding_blank_lines)
.finish()
}
}
impl fmt::Display for ParsedTextLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..self.preceding_blank_lines {
f.write_str("\n")?;
}
self.element.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum BaselinePos {
Above,
Below,
}
macro_rules! make_enum_with_values {
(
$(#[$enum_meta:meta])*
enum $Enum:ident {
$($Variant:ident,)*
}
) => {
$(#[$enum_meta])*
enum $Enum {
$($Variant,)*
}
impl $Enum {
const VALUES: &[Self] = &[$(Self::$Variant,)*];
}
};
}
make_enum_with_values! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum TextLineFonts {
InsnMnemonicFonts,
InsnHeaderFonts,
InsnBitFieldBitNumberFonts,
InsnBitFieldNameFonts,
InsnBitFieldsAffixTitleFonts,
InsnCodeFonts,
InsnDescFonts,
}
}
impl TextLineFonts {
fn regular(self) -> &'static [Font] {
match self {
TextLineFonts::InsnMnemonicFonts => KnownFontGroup::InsnDesc.fonts(),
TextLineFonts::InsnHeaderFonts => &[Font::InsnHeader],
TextLineFonts::InsnBitFieldBitNumberFonts => &[Font::InsnDescSmall, Font::TitlePageRev],
TextLineFonts::InsnBitFieldNameFonts => KnownFontGroup::InsnDesc.fonts(),
TextLineFonts::InsnBitFieldsAffixTitleFonts => &[Font::InsnDescSmall],
TextLineFonts::InsnCodeFonts => KnownFontGroup::InsnCode.fonts(),
TextLineFonts::InsnDescFonts => {
static FONTS: OnceLock<Box<[Font]>> = OnceLock::new();
FONTS.get_or_init(|| {
Box::from_iter(
KnownFontGroup::InsnDesc
.fonts()
.iter()
.cloned()
.chain([Font::InsnDescSmall]),
)
})
}
}
}
fn italic(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => {
Some(&[Font::InsnDescItalic, Font::InsnDescSmallItalic])
}
}
}
fn bold(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => Some(&[Font::InsnDescSmallBold]),
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescBold, Font::InsnDescSmallBold]),
}
}
fn bold_italic(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => {
Some(&[Font::InsnDescBoldItalic, Font::InsnDescSmallBoldItalic])
}
}
}
fn subscript(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => Some(&[Font::InsnDescSubscript]),
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => Some(KnownFontGroup::InsnCodeSubscript.fonts()),
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescSubscript]),
}
}
fn bold_subscript(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescBoldSubscript]),
}
}
fn italic_subscript(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescItalicSubscript]),
}
}
fn bold_italic_subscript(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescBoldItalicSubscript]),
}
}
fn code(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescCode, Font::InsnExtMnemonic]),
}
}
fn code_subscript(self) -> Option<&'static [Font]> {
match self {
TextLineFonts::InsnMnemonicFonts => None,
TextLineFonts::InsnHeaderFonts => None,
TextLineFonts::InsnBitFieldBitNumberFonts => None,
TextLineFonts::InsnBitFieldNameFonts => None,
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
TextLineFonts::InsnCodeFonts => None,
TextLineFonts::InsnDescFonts => Some(KnownFontGroup::InsnCodeSubscript.fonts()),
}
}
fn get_fonts(
self,
part_kind: TextLineFontKind,
) -> Option<(&'static [Font], Option<BaselinePos>)> {
let fonts = match part_kind {
TextLineFontKind::Regular => self.regular(),
TextLineFontKind::Italic => self.italic()?,
TextLineFontKind::Bold => self.bold()?,
TextLineFontKind::BoldItalic => self.bold_italic()?,
TextLineFontKind::Subscript => self.subscript()?,
TextLineFontKind::Superscript => self.subscript()?,
TextLineFontKind::BoldSubscript => self.bold_subscript()?,
TextLineFontKind::BoldSuperscript => self.bold_subscript()?,
TextLineFontKind::ItalicSubscript => self.italic_subscript()?,
TextLineFontKind::ItalicSuperscript => self.italic_subscript()?,
TextLineFontKind::BoldItalicSubscript => self.bold_italic_subscript()?,
TextLineFontKind::BoldItalicSuperscript => self.bold_italic_subscript()?,
TextLineFontKind::Code => self.code()?,
TextLineFontKind::CodeSubscript => self.code_subscript()?,
TextLineFontKind::CodeSuperscript => self.code_subscript()?,
};
Some((fonts, part_kind.sub_super().baseline_pos()))
}
fn font_to_kind_map(self) -> &'static HashMap<(Font, Option<BaselinePos>), TextLineFontKind> {
static MAPS: OnceLock<
HashMap<TextLineFonts, HashMap<(Font, Option<BaselinePos>), TextLineFontKind>>,
> = OnceLock::new();
&MAPS.get_or_init(|| {
Self::VALUES
.iter()
.map(|&this: &TextLineFonts| {
let mut map = HashMap::new();
for &kind in TextLineFontKind::VALUES {
let Some((fonts, baseline_pos)) = this.get_fonts(kind) else {
continue;
};
for font in fonts {
let old_kind = map.insert((font.clone(), baseline_pos), kind);
assert!(
old_kind.is_none(),
"duplicate font: kind={kind:?} old_kind={old_kind:?} font={font:?}"
);
}
}
(this, map)
})
.collect()
})[&self]
}
fn fonts(self) -> &'static HashSet<Font> {
static SETS: OnceLock<HashMap<TextLineFonts, HashSet<Font>>> = OnceLock::new();
&SETS.get_or_init(|| {
Self::VALUES
.iter()
.map(|&this: &TextLineFonts| {
let mut set = HashSet::new();
for &kind in TextLineFontKind::VALUES {
let Some((fonts, _baseline_pos)) = this.get_fonts(kind) else {
continue;
};
set.extend(fonts.iter().cloned());
}
(this, set)
})
.collect()
})[&self]
}
fn get_kind(self, font: Font, baseline_pos: BaselinePos) -> Option<TextLineFontKind> {
let font_to_kind_map = self.font_to_kind_map();
font_to_kind_map
.get(&(font.clone(), Some(baseline_pos)))
.or_else(|| font_to_kind_map.get(&(font, None)))
.copied()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum FontVariantCode {
Code,
NotCode,
}
impl FontVariantCode {
const fn value(self) -> &'static [&'static str] {
match self {
Self::Code => &["code"],
Self::NotCode => &[],
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum FontVariantBold {
Bold,
NotBold,
}
impl FontVariantBold {
const fn value(self) -> &'static [&'static str] {
match self {
Self::Bold => &["b"],
Self::NotBold => &[],
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum FontVariantItalic {
Italic,
NotItalic,
}
impl FontVariantItalic {
const fn value(self) -> &'static [&'static str] {
match self {
Self::Italic => &["i"],
Self::NotItalic => &[],
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum FontVariantSubSuper {
NotSubSuper,
Subscript,
Superscript,
}
impl FontVariantSubSuper {
const fn value(self) -> &'static [&'static str] {
match self {
Self::NotSubSuper => &[],
Self::Subscript => &["sub"],
Self::Superscript => &["sup"],
}
}
}
impl FontVariantSubSuper {
fn baseline_pos(self) -> Option<BaselinePos> {
match self {
FontVariantSubSuper::NotSubSuper => None,
FontVariantSubSuper::Subscript => Some(BaselinePos::Below),
FontVariantSubSuper::Superscript => Some(BaselinePos::Above),
}
}
}
make_enum_with_values! {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum TextLineFontKind {
Regular,
Subscript,
Superscript,
Italic,
ItalicSubscript,
ItalicSuperscript,
Bold,
BoldSubscript,
BoldSuperscript,
BoldItalic,
BoldItalicSubscript,
BoldItalicSuperscript,
Code,
CodeSubscript,
CodeSuperscript,
}
}
impl TextLineFontKind {
fn code(self) -> FontVariantCode {
match self {
Self::Regular
| Self::Subscript
| Self::Superscript
| Self::Italic
| Self::ItalicSubscript
| Self::ItalicSuperscript
| Self::Bold
| Self::BoldSubscript
| Self::BoldSuperscript
| Self::BoldItalic
| Self::BoldItalicSubscript
| Self::BoldItalicSuperscript => FontVariantCode::NotCode,
Self::Code | Self::CodeSubscript | Self::CodeSuperscript => FontVariantCode::Code,
}
}
fn bold(self) -> FontVariantBold {
match self {
Self::Regular
| Self::Subscript
| Self::Superscript
| Self::Italic
| Self::ItalicSubscript
| Self::ItalicSuperscript => FontVariantBold::NotBold,
Self::Bold
| Self::BoldSubscript
| Self::BoldSuperscript
| Self::BoldItalic
| Self::BoldItalicSubscript
| Self::BoldItalicSuperscript => FontVariantBold::Bold,
Self::Code | Self::CodeSubscript | Self::CodeSuperscript => FontVariantBold::NotBold,
}
}
fn italic(self) -> FontVariantItalic {
match self {
Self::Regular | Self::Subscript | Self::Superscript => FontVariantItalic::NotItalic,
Self::Italic | Self::ItalicSubscript | Self::ItalicSuperscript => {
FontVariantItalic::Italic
}
Self::Bold | Self::BoldSubscript | Self::BoldSuperscript => {
FontVariantItalic::NotItalic
}
Self::BoldItalic | Self::BoldItalicSubscript | Self::BoldItalicSuperscript => {
FontVariantItalic::Italic
}
Self::Code | Self::CodeSubscript | Self::CodeSuperscript => {
FontVariantItalic::NotItalic
}
}
}
fn sub_super(self) -> FontVariantSubSuper {
match self {
Self::Regular => FontVariantSubSuper::NotSubSuper,
Self::Subscript => FontVariantSubSuper::Subscript,
Self::Superscript => FontVariantSubSuper::Superscript,
Self::Italic => FontVariantSubSuper::NotSubSuper,
Self::ItalicSubscript => FontVariantSubSuper::Subscript,
Self::ItalicSuperscript => FontVariantSubSuper::Superscript,
Self::Bold => FontVariantSubSuper::NotSubSuper,
Self::BoldSubscript => FontVariantSubSuper::Subscript,
Self::BoldSuperscript => FontVariantSubSuper::Superscript,
Self::BoldItalic => FontVariantSubSuper::NotSubSuper,
Self::BoldItalicSubscript => FontVariantSubSuper::Subscript,
Self::BoldItalicSuperscript => FontVariantSubSuper::Superscript,
Self::Code => FontVariantSubSuper::NotSubSuper,
Self::CodeSubscript => FontVariantSubSuper::Subscript,
Self::CodeSuperscript => FontVariantSubSuper::Superscript,
}
}
fn text_line_tags(self) -> impl Clone + Iterator<Item = &'static str> {
self.code()
.value()
.iter()
.copied()
.chain(self.bold().value().iter().copied())
.chain(self.italic().value().iter().copied())
.chain(self.sub_super().value().iter().copied())
}
}
fn main() {}

198
src/xml_tree.rs Normal file
View file

@ -0,0 +1,198 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use quick_xml::{
Writer,
events::{BytesText, Event},
};
use std::fmt;
struct FmtToIoAdaptor<W> {
writer: W,
partial_char: [u8; 4],
partial_char_len: u8,
}
impl<W: fmt::Write> FmtToIoAdaptor<W> {
fn new(writer: W) -> Self {
Self {
writer,
partial_char: [0; 4],
partial_char_len: 0,
}
}
fn finish(self) -> Result<W, fmt::Error> {
let Self {
writer,
partial_char: _,
partial_char_len,
} = self;
if partial_char_len != 0 {
Err(fmt::Error)
} else {
Ok(writer)
}
}
fn write_byte(&mut self, b: u8) -> std::io::Result<()> {
let Self {
writer,
partial_char,
partial_char_len,
} = self;
partial_char[usize::from(*partial_char_len)] = b;
*partial_char_len += 1;
match str::from_utf8(&partial_char[..usize::from(*partial_char_len)]) {
Ok(s) => {
*partial_char_len = 0;
writer.write_str(s).map_err(std::io::Error::other)
}
Err(e) => {
if e.error_len().is_some() {
*partial_char_len = 0;
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
} else {
Ok(())
}
}
}
}
}
impl<W: fmt::Write> std::io::Write for FmtToIoAdaptor<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
for &b in buf {
self.write_byte(b)?;
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) enum ElementTag {
Comment,
Normal(String),
}
impl ElementTag {
pub(crate) fn normal(&self) -> Option<&str> {
match self {
ElementTag::Comment => None,
ElementTag::Normal(v) => Some(v),
}
}
}
impl fmt::Debug for ElementTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Comment => f.write_str("<Comment>"),
Self::Normal(v) => v.fmt(f),
}
}
}
/// like python's xml.etree.ElementTree.Element
#[derive(Clone, Debug)]
pub(crate) struct Element {
pub(crate) tag: ElementTag,
pub(crate) attrib: Vec<(String, String)>,
/// text contained in this element but before any children
pub(crate) text: String,
pub(crate) children: Vec<Element>,
/// text after the end of this element
pub(crate) tail: String,
}
/// equivalent to python `xml.etree.ElementTree.tostring(self, encoding="unicode")`
impl fmt::Display for Element {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut writer = Writer::new(FmtToIoAdaptor::new(f));
fn helper(
element: &Element,
writer: &mut Writer<impl std::io::Write>,
) -> std::io::Result<()> {
let Element {
tag,
attrib,
text,
children,
tail,
} = element;
match tag {
ElementTag::Comment => {
writer.write_event(Event::Comment(BytesText::new(text)))?;
}
ElementTag::Normal(tag) if tag.is_empty() => {
writer.write_event(Event::Text(BytesText::new(text)))?;
}
ElementTag::Normal(tag) => {
let mut element_writer = writer.create_element(tag);
for (name, value) in attrib {
element_writer =
element_writer.with_attribute((name.as_str(), value.as_str()));
}
if text.is_empty() && children.is_empty() {
element_writer.write_empty()?;
} else {
element_writer.write_inner_content(|writer| {
writer.write_event(Event::Text(BytesText::new(text)))?;
for child in children {
helper(child, writer)?;
}
Ok(())
})?;
}
}
}
writer.write_event(Event::Text(BytesText::new(tail)))?;
Ok(())
}
helper(self, &mut writer).map_err(|_| fmt::Error)?;
writer.into_inner().finish()?;
Ok(())
}
}
impl Element {
/// equivalent to python `"".join(self.itertext())`
pub(crate) fn inner_text(&self) -> String {
let mut retval = String::new();
fn helper(element: &Element, retval: &mut String) {
let Element {
tag,
attrib: _,
text,
children,
tail: _,
} = element;
let ElementTag::Normal(_) = tag else {
return;
};
retval.push_str(text);
for child in children {
helper(child, retval);
retval.push_str(&child.tail);
}
}
helper(self, &mut retval);
retval
}
/// equivalent of python's `xml.etree.ElementTree.SubElement()`
pub(crate) fn sub_element(
&mut self,
tag: String,
attrib: impl IntoIterator<Item = (String, String)>,
) -> &mut Self {
self.children.push(Self {
tag: ElementTag::Normal(tag),
attrib: Vec::from_iter(attrib),
text: String::new(),
children: Vec::new(),
tail: String::new(),
});
self.children.last_mut().expect("just pushed")
}
}