use crate::formula::omml::elements::{ElementContext, ElementType};
use crate::formula::ast::MathNode;
use std::borrow::Cow as StdCow;
pub fn intern_string<'arena>(arena: &'arena bumpalo::Bump, s: &str) -> &'arena str {
arena.alloc_str(s)
}
#[allow(dead_code)] pub fn extract_text_content(nodes: &[MathNode]) -> String {
let mut result = String::new();
for node in nodes {
match node {
MathNode::Text(text) => result.push_str(text.as_ref()),
MathNode::Fenced { content, .. } => {
result.push_str(&extract_text_content(content));
}
MathNode::Function { name, argument } => {
result.push_str(name.as_ref());
result.push('(');
result.push_str(&extract_text_content(argument));
result.push(')');
}
_ => {} }
}
result
}
#[allow(dead_code)] pub fn get_element_type_fast(name: &[u8]) -> ElementType {
match name {
b"m:oMath" | b"oMath" => ElementType::Math,
b"m:r" | b"r" => ElementType::Run,
b"m:t" | b"t" => ElementType::Text,
b"m:f" | b"f" => ElementType::Fraction,
b"m:num" | b"num" => ElementType::Numerator,
b"m:den" | b"den" => ElementType::Denominator,
b"m:rad" | b"rad" => ElementType::Radical,
b"m:deg" | b"deg" => ElementType::Degree,
b"m:e" | b"e" => ElementType::Base,
b"m:sSup" | b"sSup" => ElementType::Superscript,
b"m:sSub" | b"sSub" => ElementType::Subscript,
b"m:sSubSup" | b"sSubSup" => ElementType::SubSup,
b"m:sup" | b"sup" => ElementType::SuperscriptElement,
b"m:sub" | b"sub" => ElementType::SubscriptElement,
b"m:d" | b"d" => ElementType::Delimiter,
b"m:nary" | b"nary" => ElementType::Nary,
b"m:func" | b"func" => ElementType::Function,
b"m:fName" | b"fName" => ElementType::FunctionName,
b"m:m" | b"m" => ElementType::Matrix,
b"m:mr" | b"mr" => ElementType::MatrixRow,
b"m:mPr" | b"mPr" => ElementType::Properties,
b"m:acc" | b"acc" => ElementType::Accent,
b"m:accPr" | b"accPr" => ElementType::AccentProperties,
b"m:bar" | b"bar" => ElementType::Bar,
b"m:box" | b"box" => ElementType::Box,
b"m:phant" | b"phant" => ElementType::Phantom,
b"m:groupChr" | b"groupChr" => ElementType::GroupChar,
b"m:borderBox" | b"borderBox" => ElementType::BorderBox,
b"m:eqArr" | b"eqArr" => ElementType::EqArr,
b"m:eqArrPr" | b"eqArrPr" => ElementType::EqArrPr,
b"m:rPr" | b"rPr" => ElementType::Properties,
b"m:fPr" | b"fPr" => ElementType::Properties,
b"m:radPr" | b"radPr" => ElementType::Properties,
b"m:sSupPr" | b"sSupPr" => ElementType::Properties,
b"m:sSubPr" | b"sSubPr" => ElementType::Properties,
b"m:dPr" | b"dPr" => ElementType::Properties,
b"m:naryPr" | b"naryPr" => ElementType::Properties,
b"m:funcPr" | b"funcPr" => ElementType::Properties,
b"m:groupChrPr" | b"groupChrPr" => ElementType::Properties,
b"m:chr" | b"chr" => ElementType::Text, b"m:sPre" | b"sPre" => ElementType::Run, b"m:sPost" | b"sPost" => ElementType::Run, b"m:lim" | b"lim" => ElementType::Nary, b"m:limLow" | b"limLow" => ElementType::SubscriptElement, b"m:limUpp" | b"limUpp" => ElementType::SuperscriptElement, _ => ElementType::Unknown,
}
}
#[allow(dead_code)] pub fn find_attribute_fast<'a>(
attrs: &'a [quick_xml::events::attributes::Attribute<'a>],
key: &str,
) -> Option<&'a quick_xml::events::attributes::Attribute<'a>> {
for attr in attrs {
if let Ok(attr_key) = std::str::from_utf8(attr.key.as_ref())
&& (attr_key == key || attr_key == format!("m:{}", key)) {
return Some(attr);
}
}
None
}
pub struct ContextPool<'arena> {
pool: Vec<ElementContext<'arena>>,
available: Vec<usize>,
}
impl<'arena> ContextPool<'arena> {
pub fn new(capacity: usize) -> Self {
Self {
pool: Vec::with_capacity(capacity),
available: Vec::new(),
}
}
pub fn get(&mut self, element_type: ElementType) -> ElementContext<'arena> {
if let Some(index) = self.available.pop() {
let mut context = self.pool.swap_remove(index);
context.element_type = element_type;
context.clear();
context
} else {
ElementContext::new(element_type)
}
}
pub fn put(&mut self, mut context: ElementContext<'arena>) {
if self.pool.len() < self.pool.capacity() {
context.clear();
self.pool.push(context);
}
}
}
pub fn process_text_zero_copy<'a>(text: &'a str) -> StdCow<'a, str> {
let trimmed = text.trim();
if trimmed.len() == text.len() {
StdCow::Borrowed(text)
} else {
StdCow::Owned(trimmed.to_string())
}
}
#[allow(dead_code)] pub fn parse_numeric_attr(attr: Option<&str>) -> Option<f32> {
attr.and_then(|s| fast_float2::parse(s).ok())
}
pub fn extend_vec_efficient<T>(vec: &mut Vec<T>, items: impl IntoIterator<Item = T>) {
vec.extend(items);
}
pub struct ElementStack<'arena> {
stack: Vec<ElementContext<'arena>>,
}
impl<'arena> ElementStack<'arena> {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
stack: Vec::with_capacity(64), }
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
stack: Vec::with_capacity(capacity),
}
}
#[inline(always)]
pub fn push(&mut self, context: ElementContext<'arena>) {
self.stack.push(context);
}
#[inline(always)]
pub fn pop(&mut self) -> Option<ElementContext<'arena>> {
self.stack.pop()
}
#[inline(always)]
pub fn last(&self) -> Option<&ElementContext<'arena>> {
self.stack.last()
}
#[inline(always)]
pub fn last_mut(&mut self) -> Option<&mut ElementContext<'arena>> {
self.stack.last_mut()
}
#[inline(always)]
#[allow(dead_code)] pub fn peek(&self, depth: usize) -> Option<&ElementContext<'arena>> {
let len = self.stack.len();
if depth < len {
Some(&self.stack[len - 1 - depth])
} else {
None
}
}
#[inline(always)]
#[allow(dead_code)] pub fn peek_mut(&mut self, depth: usize) -> Option<&mut ElementContext<'arena>> {
let len = self.stack.len();
if depth < len {
let idx = len - 1 - depth;
Some(&mut self.stack[idx])
} else {
None
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
#[inline(always)]
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.stack.len()
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.stack.clear();
}
#[inline(always)]
#[allow(dead_code)]
pub fn capacity(&self) -> usize {
self.stack.capacity()
}
#[allow(dead_code)] pub fn reserve(&mut self, additional: usize) {
self.stack.reserve(additional);
}
#[allow(dead_code)] pub fn shrink_to_fit(&mut self) {
self.stack.shrink_to_fit();
}
}
#[allow(dead_code)] pub fn strip_namespace(name: &[u8]) -> &[u8] {
if let Some(colon_pos) = memchr::memchr(b':', name) {
&name[colon_pos + 1..]
} else {
name
}
}
#[allow(dead_code)] pub fn handle_parse_error<T>(result: Result<T, impl std::fmt::Display>) -> Result<T, String> {
result.map_err(|e| e.to_string())
}
#[allow(dead_code)] pub fn is_valid_omml_element_name(name: &str) -> bool {
!name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == ':' || c == '_' || c == '-')
}
#[allow(dead_code)] pub fn validate_omml_attribute_value(value: &str) -> bool {
!value.is_empty() && value.len() < 10000 && !value.contains('\0')
}
#[allow(dead_code)] pub struct StringInterner {
strings: std::collections::HashSet<String>,
}
#[allow(dead_code)] impl StringInterner {
pub fn new() -> Self {
Self {
strings: std::collections::HashSet::new(),
}
}
pub fn intern(&mut self, s: &str) -> &str {
if self.strings.contains(s) {
self.strings.get(s).unwrap().as_str()
} else {
self.strings.insert(s.to_string());
self.strings.get(s).unwrap().as_str()
}
}
}
#[allow(dead_code)] pub fn extract_attribute_value_simd<'a>(
attrs: &'a [quick_xml::events::attributes::Attribute<'a>],
key: &str,
) -> Option<&'a [u8]> {
for attr in attrs {
if let Ok(attr_key) = std::str::from_utf8(attr.key.as_ref())
&& (attr_key == key || attr_key == format!("m:{}", key)) {
return Some(&attr.value);
}
}
None
}
#[allow(dead_code)] pub fn normalize_xml_text(text: &str) -> String {
text.replace("<", "<")
.replace(">", ">")
.replace("&", "&")
.replace(""", "\"")
.replace("'", "'")
.replace(" ", " ")
.replace(" ", "\u{00A0}") }
pub fn validate_omml_structure(nodes: &[super::MathNode]) -> Result<(), super::OmmlError> {
if nodes.is_empty() {
return Err(super::OmmlError::InvalidStructure(
"Empty OMML document".to_string()
));
}
let has_math_root = nodes.iter().any(|node| matches!(node, super::MathNode::Row(_)));
if !has_math_root && !nodes.is_empty() {
validate_math_nodes(nodes)?;
}
Ok(())
}
pub fn validate_math_nodes(nodes: &[super::MathNode]) -> Result<(), super::OmmlError> {
for node in nodes {
match node {
super::MathNode::Frac { numerator, denominator, .. } => {
if numerator.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Fraction numerator is empty".to_string()
));
}
if denominator.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Fraction denominator is empty".to_string()
));
}
}
super::MathNode::Root { base, .. } => {
if base.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Root base is empty".to_string()
));
}
}
super::MathNode::Power { base, exponent } => {
if base.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Power base is empty".to_string()
));
}
if exponent.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Power exponent is empty".to_string()
));
}
}
super::MathNode::Sub { base, subscript } => {
if base.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Subscript base is empty".to_string()
));
}
if subscript.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Subscript is empty".to_string()
));
}
}
super::MathNode::Function { name, argument } => {
if name.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Function name is empty".to_string()
));
}
if argument.is_empty() {
return Err(super::OmmlError::MissingRequiredElement(
"Function argument is empty".to_string()
));
}
}
super::MathNode::Fenced { content, .. } => {
if content.is_empty() {
return Err(super::OmmlError::ValidationError(
"Fenced content is empty".to_string()
));
}
}
super::MathNode::Matrix { rows, .. } => {
if rows.is_empty() {
return Err(super::OmmlError::ValidationError(
"Matrix has no rows".to_string()
));
}
for (i, row) in rows.iter().enumerate() {
if row.is_empty() {
return Err(super::OmmlError::ValidationError(
format!("Matrix row {} is empty", i)
));
}
}
}
_ => {} }
}
Ok(())
}
pub fn validate_element_nesting(
element_type: &ElementType,
parent_type: Option<&ElementType>,
) -> Result<(), super::OmmlError> {
match element_type {
ElementType::Math => {
if parent_type.is_some() {
return Err(super::OmmlError::InvalidStructure(
"Math element should be root".to_string()
));
}
}
ElementType::Numerator | ElementType::Denominator => {
if !matches!(parent_type, Some(ElementType::Fraction)) {
return Err(super::OmmlError::InvalidStructure(
"Numerator/denominator must be inside fraction".to_string()
));
}
}
ElementType::Degree => {
if !matches!(parent_type, Some(ElementType::Radical)) {
return Err(super::OmmlError::InvalidStructure(
"Degree must be inside radical".to_string()
));
}
}
ElementType::Base => {
match parent_type {
Some(
ElementType::Superscript
| ElementType::Subscript
| ElementType::SubSup
| ElementType::Radical
| ElementType::Accent
| ElementType::Bar
| ElementType::GroupChar
) => {}
_ => {
}
}
}
ElementType::SuperscriptElement => {
match parent_type {
Some(ElementType::Superscript | ElementType::SubSup | ElementType::Nary | ElementType::Integrand) => {}
_ => {
return Err(super::OmmlError::InvalidStructure(
"Superscript element in invalid context".to_string()
));
}
}
}
ElementType::SubscriptElement => {
match parent_type {
Some(ElementType::Subscript | ElementType::SubSup | ElementType::Nary | ElementType::Integrand) => {}
_ => {
return Err(super::OmmlError::InvalidStructure(
"Subscript element in invalid context".to_string()
));
}
}
}
_ => {} }
Ok(())
}