use alloc::string::String;
use alloc::vec::Vec;
use crate::line::tachys;
use mf2_catalog::{Catalog, markup_key};
use mf2_runtime::{ErrorSink, MarkupKind, MarkupPart, NoErrors, Part, PartSink};
use tachys::view::any_view::{AnyView, IntoAny};
use crate::leptos::markup::{FlatHandler, NestingHandler};
use crate::leptos::state::{self, TextUse};
use crate::tr::{MarkupHandler, TrRich};
pub(crate) fn fragment_with(
rich: &TrRich,
catalog: &Catalog,
registry: Option<&'static mf2_runtime::Registry>,
bidi: Option<mf2_runtime::BidiStrategy>,
) -> Vec<AnyView> {
let (Some(cx), Some(installed)) = (
state::context_for(TextUse::Displayed, bidi),
state::registry(),
) else {
return Vec::new();
};
let formatter = mf2_runtime::Formatter::new(catalog, registry.unwrap_or(installed), &cx);
let mut builder = Builder {
rich,
text: String::new(),
stack: Vec::new(),
root: Vec::new(),
};
let mut errors = NoErrors;
rich.parts(&formatter, &mut builder, &mut errors as &mut dyn ErrorSink);
let root = builder.finish();
#[cfg(feature = "mark-fallback-lang")]
if let Some(lender) = crate::leptos::lang::Lender::of(catalog, rich.id()) {
return alloc::vec![lender.wrap(root)];
}
root
}
#[cfg(any(
not(feature = "static-locale"),
all(feature = "hydrate", feature = "fn-datetime")
))]
pub(crate) fn fragment(rich: &TrRich, catalog: &Catalog) -> Vec<AnyView> {
fragment_with(rich, catalog, None, None)
}
pub(crate) fn active_fragment(rich: &TrRich) -> Vec<AnyView> {
match crate::leptos::catalog::current() {
Some(cx) => cx.in_zone(|| fragment_with(rich, cx.catalog(), cx.registry(), cx.bidi())),
None => Vec::new(),
}
}
struct Builder<'a> {
rich: &'a TrRich,
text: String,
stack: Vec<(u64, Vec<AnyView>)>,
root: Vec<AnyView>,
}
impl PartSink for Builder<'_> {
fn part(&mut self, part: Part<'_>) {
match part {
Part::Text(t) => self.text.push_str(t),
Part::BidiIsolation(i) => self.text.push_str(i.as_str()),
Part::Expression(e) => e.write(&mut self.text),
Part::Fallback(source) => {
self.text.push('{');
source.write(&mut self.text);
self.text.push('}');
}
Part::Markup(m) => self.markup(&m),
_ => {}
}
}
}
impl Builder<'_> {
fn handler(&self, key: u64) -> Option<&dyn MarkupHandler> {
self.rich
.handlers()
.iter()
.find(|(k, _)| *k == key)
.map(|(_, h)| &**h)
}
fn push(&mut self, view: AnyView) {
match self.stack.last_mut() {
Some((_, children)) => children.push(view),
None => self.root.push(view),
}
}
fn flush(&mut self) {
if self.text.is_empty() {
return;
}
let text = core::mem::take(&mut self.text);
self.push(text.into_any());
}
fn markup(&mut self, m: &MarkupPart<'_>) {
let key = markup_key(m.name());
let Some(handler) = self.handler(key) else {
return;
};
if let Some(flat) = handler.as_any().downcast_ref::<FlatHandler>() {
let view = flat.call(m);
self.flush();
self.push(view);
return;
}
if handler.as_any().downcast_ref::<NestingHandler>().is_none() {
return;
}
match m.kind() {
MarkupKind::Open => {
self.flush();
self.stack.push((key, Vec::new()));
}
MarkupKind::Standalone => {
self.flush();
self.close(key, Vec::new());
}
MarkupKind::Close => {
self.flush();
let Some(at) = self.stack.iter().rposition(|(k, _)| *k == key) else {
return;
};
while self.stack.len() > at + 1 {
if let Some((inner, children)) = self.stack.pop() {
self.close(inner, children);
}
}
if let Some((_, children)) = self.stack.pop() {
self.close(key, children);
}
}
}
}
fn close(&mut self, key: u64, children: Vec<AnyView>) {
let view = self
.handler(key)
.and_then(|h| h.as_any().downcast_ref::<NestingHandler>())
.map(|nesting| nesting.call(children.into_any()));
if let Some(view) = view {
self.push(view);
}
}
fn finish(mut self) -> Vec<AnyView> {
self.flush();
while let Some((key, children)) = self.stack.pop() {
self.close(key, children);
}
self.root
}
}