use crate::binxml::value_variant::BinXmlValue;
use crate::err::{EvtxError, Result};
use crate::model::ir::{
Element, ElementId, IrArena, Node, Placeholder, TemplateValue, Text, is_optional_empty,
is_optional_empty_template_value,
};
#[derive(Clone, Copy)]
pub(crate) struct TplCtx<'t, 'a> {
pub(crate) values: &'t [TemplateValue<'a>],
pub(crate) frags: &'t IrArena<'a>,
pub(crate) nested: &'t [crate::binxml::ir::TplInstance<'a>],
pub(crate) may_expand: bool,
}
#[derive(Clone, Copy)]
pub(crate) struct Scope<'t, 'a> {
pub(crate) arena: &'t IrArena<'a>,
pub(crate) ctx: Option<TplCtx<'t, 'a>>,
}
#[derive(Clone, Copy)]
pub(crate) struct Ovr<'p> {
slot: usize,
idx: usize,
parent: Option<&'p Ovr<'p>>,
}
fn ovr_lookup(mut ovr: Option<&Ovr<'_>>, node: &Node<'_>) -> Option<usize> {
let addr = node as *const Node<'_> as usize;
while let Some(o) = ovr {
if o.slot == addr {
return Some(o.idx);
}
ovr = o.parent;
}
None
}
pub(crate) enum RNode<'n, 'a> {
Plain(&'n Node<'a>),
Text(Text<'a>),
Value(&'n BinXmlValue<'a>),
OwnValue(BinXmlValue<'a>),
Frag(ElementId),
Nested(u16),
Skip,
}
fn item_rnode<'n, 'a>(value: &BinXmlValue<'a>, idx: usize) -> RNode<'n, 'a> {
match value.array_item_as_value(idx) {
Some(BinXmlValue::StringType(s)) => {
if s.is_empty() {
RNode::Skip
} else {
RNode::Text(Text::utf16(s))
}
}
Some(other) => RNode::OwnValue(other),
None => RNode::Skip,
}
}
impl<'t, 'a> Scope<'t, 'a> {
pub(crate) fn materialized(arena: &'t IrArena<'a>) -> Self {
Scope { arena, ctx: None }
}
pub(crate) fn frag_scope(&self) -> Scope<'t, 'a> {
let ctx = self.ctx.as_ref().expect("frag scope without template ctx");
Scope {
arena: ctx.frags,
ctx: None,
}
}
pub(crate) fn nested_scope_root(&self, idx: u16) -> (Scope<'t, 'a>, ElementId) {
let ctx = self
.ctx
.as_ref()
.expect("nested scope without template ctx");
let inst = &ctx.nested[idx as usize];
let scope = Scope {
arena: inst.template.arena(),
ctx: Some(TplCtx {
values: inst.values.as_slice(),
frags: ctx.frags,
nested: ctx.nested,
may_expand: inst.may_expand,
}),
};
(scope, inst.template.root())
}
#[inline(always)]
pub(crate) fn resolve<'n>(
&self,
node: &'n Node<'a>,
ovr: Option<&Ovr<'_>>,
) -> Result<RNode<'n, 'a>>
where
't: 'n,
{
let Some(ctx) = &self.ctx else {
return Ok(RNode::Plain(node));
};
match node {
Node::Placeholder(ph) => resolve_placeholder(ctx, ph, node, ovr),
Node::Value(v) if ovr.is_some() => match ovr_lookup(ovr, node) {
Some(idx) => Ok(item_rnode(v, idx)),
None => Ok(RNode::Plain(node)),
},
_ => Ok(RNode::Plain(node)),
}
}
}
#[inline]
fn resolve_placeholder<'n, 'a>(
ctx: &TplCtx<'n, 'a>,
ph: &Placeholder,
node: &'n Node<'a>,
ovr: Option<&Ovr<'_>>,
) -> Result<RNode<'n, 'a>> {
let Some(tv) = ctx.values.get(ph.id as usize) else {
return Ok(RNode::Skip);
};
if ovr.is_some()
&& let Some(idx) = ovr_lookup(ovr, node)
{
return match tv {
TemplateValue::Value(v) => Ok(item_rnode(v, idx)),
TemplateValue::BinXmlElement(_) | TemplateValue::NestedTemplate(_) => Ok(RNode::Skip),
};
}
if ph.optional && is_optional_empty_template_value(tv) {
return Ok(RNode::Skip);
}
match tv {
TemplateValue::BinXmlElement(id) => Ok(RNode::Frag(*id)),
TemplateValue::NestedTemplate(idx) => Ok(RNode::Nested(*idx)),
TemplateValue::Value(v) => match v {
BinXmlValue::EvtXml => Err(EvtxError::FailedToCreateRecordModel(
"Unimplemented - EvtXml",
)),
BinXmlValue::BinXmlType(_) => Err(EvtxError::FailedToCreateRecordModel(
"unsupported BinXML value in template substitution",
)),
BinXmlValue::EvtHandle => Err(EvtxError::FailedToCreateRecordModel(
"unsupported BinXML value in tree",
)),
BinXmlValue::StringType(s) => Ok(RNode::Text(Text::utf16(*s))),
BinXmlValue::AnsiStringType(s) => Ok(RNode::Text(Text::utf8(s))),
other => Ok(RNode::Value(other)),
},
}
}
fn expandable_len(node: &Node<'_>, ctx: &TplCtx<'_, '_>) -> Option<usize> {
match node {
Node::Value(v) => v.expandable_array_len(),
Node::Placeholder(ph) => {
let tv = ctx.values.get(ph.id as usize)?;
if ph.optional && is_optional_empty_template_value(tv) {
return None;
}
match tv {
TemplateValue::Value(v) => v.expandable_array_len(),
TemplateValue::BinXmlElement(_) | TemplateValue::NestedTemplate(_) => None,
}
}
_ => None,
}
}
pub(crate) fn find_expansion(
element: &Element<'_>,
ctx: &TplCtx<'_, '_>,
ovr: Option<&Ovr<'_>>,
) -> Option<(usize, usize)> {
if !ctx.may_expand {
return None;
}
let scan = |node: &Node<'_>| -> Option<(usize, usize)> {
let len = expandable_len(node, ctx)?;
if len <= 1 || ovr_lookup(ovr, node).is_some() {
return None;
}
Some((node as *const Node<'_> as usize, len))
};
for node in &element.children {
if let Some(hit) = scan(node) {
return Some(hit);
}
}
for attr in &element.attrs {
for node in &attr.value {
if let Some(hit) = scan(node) {
return Some(hit);
}
}
}
None
}
impl<'p> Ovr<'p> {
pub(crate) fn frame(slot: usize, idx: usize, parent: Option<&'p Ovr<'p>>) -> Ovr<'p> {
Ovr { slot, idx, parent }
}
}
pub(crate) fn for_each_expansion<'a, F>(
scope: &Scope<'_, 'a>,
element: &Element<'a>,
ovr: Option<&Ovr<'_>>,
f: &mut F,
) -> Result<()>
where
F: FnMut(Option<&Ovr<'_>>) -> Result<()>,
{
if let Some(ctx) = &scope.ctx
&& let Some((slot, len)) = find_expansion(element, ctx, ovr)
{
for idx in 0..len {
let frame = Ovr::frame(slot, idx, ovr);
for_each_expansion(scope, element, Some(&frame), f)?;
}
return Ok(());
}
f(ovr)
}
pub(crate) fn expansion_any<'a, F>(
scope: &Scope<'_, 'a>,
element: &Element<'a>,
ovr: Option<&Ovr<'_>>,
f: &mut F,
) -> Result<bool>
where
F: FnMut(Option<&Ovr<'_>>) -> Result<bool>,
{
if let Some(ctx) = &scope.ctx
&& let Some((slot, len)) = find_expansion(element, ctx, ovr)
{
for idx in 0..len {
let frame = Ovr::frame(slot, idx, ovr);
if expansion_any(scope, element, Some(&frame), f)? {
return Ok(true);
}
}
return Ok(false);
}
f(ovr)
}
pub(crate) fn count_expansion_copies(
scope: &Scope<'_, '_>,
element: &Element<'_>,
ovr: Option<&Ovr<'_>>,
) -> usize {
if let Some(ctx) = &scope.ctx
&& let Some((slot, len)) = find_expansion(element, ctx, ovr)
{
let frame = Ovr::frame(slot, 0, ovr);
return len * count_expansion_copies(scope, element, Some(&frame));
}
1
}
pub(crate) struct ChildElement<'t, 'a> {
pub(crate) scope: Scope<'t, 'a>,
pub(crate) element: &'t Element<'a>,
pub(crate) expand: bool,
}
#[inline]
pub(crate) fn resolve_child_element<'t, 'a>(
scope: Scope<'t, 'a>,
node: &'t Node<'a>,
ovr: Option<&Ovr<'_>>,
) -> Result<Option<ChildElement<'t, 'a>>> {
match scope.resolve(node, ovr)? {
RNode::Plain(Node::Element(child_id)) => {
let element = scope.arena.get(*child_id).expect("invalid element id");
Ok(Some(ChildElement {
scope,
element,
expand: true,
}))
}
RNode::Frag(child_id) => {
let frag_scope = scope.frag_scope();
let element = frag_scope.arena.get(child_id).expect("invalid element id");
Ok(Some(ChildElement {
scope: frag_scope,
element,
expand: true,
}))
}
RNode::Nested(idx) => {
let (nested_scope, root) = scope.nested_scope_root(idx);
let element = nested_scope.arena.get(root).expect("invalid element id");
Ok(Some(ChildElement {
scope: nested_scope,
element,
expand: false,
}))
}
_ => Ok(None),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScanClass {
Skip,
Element,
Content,
Empty,
}
fn item_class(value: &BinXmlValue<'_>, idx: usize) -> ScanClass {
match value.array_item_as_value(idx) {
Some(BinXmlValue::StringType(s)) => {
if s.is_empty() {
ScanClass::Skip
} else {
ScanClass::Content
}
}
Some(_) => ScanClass::Content,
None => ScanClass::Skip,
}
}
#[inline(always)]
pub(crate) fn scan_class(
scope: &Scope<'_, '_>,
node: &Node<'_>,
ovr: Option<&Ovr<'_>>,
) -> ScanClass {
match node {
Node::Element(_) => ScanClass::Element,
Node::Text(text) | Node::CData(text) => {
if text.is_empty() {
ScanClass::Empty
} else {
ScanClass::Content
}
}
Node::Value(value) => {
if ovr.is_some()
&& scope.ctx.is_some()
&& let Some(idx) = ovr_lookup(ovr, node)
{
return item_class(value, idx);
}
if is_optional_empty(value) {
ScanClass::Empty
} else {
ScanClass::Content
}
}
Node::CharRef(_) | Node::EntityRef(_) => ScanClass::Content,
Node::PITarget(_) | Node::PIData(_) => ScanClass::Empty,
Node::Placeholder(ph) => {
let Some(ctx) = &scope.ctx else {
return ScanClass::Content;
};
let Some(tv) = ctx.values.get(ph.id as usize) else {
return ScanClass::Skip;
};
if ovr.is_some()
&& let Some(idx) = ovr_lookup(ovr, node)
{
return match tv {
TemplateValue::Value(v) => item_class(v, idx),
_ => ScanClass::Skip,
};
}
if ph.optional && is_optional_empty_template_value(tv) {
return ScanClass::Skip;
}
match tv {
TemplateValue::BinXmlElement(_) | TemplateValue::NestedTemplate(_) => {
ScanClass::Element
}
TemplateValue::Value(v) => {
if is_optional_empty(v) {
ScanClass::Empty
} else {
ScanClass::Content
}
}
}
}
}
}
#[inline]
pub(crate) fn has_non_empty_text_content(
scope: &Scope<'_, '_>,
nodes: &[Node<'_>],
ovr: Option<&Ovr<'_>>,
) -> bool {
nodes
.iter()
.any(|node| scan_class(scope, node, ovr) == ScanClass::Content)
}
pub(crate) fn content_layout(
scope: &Scope<'_, '_>,
element: &Element<'_>,
ovr: Option<&Ovr<'_>>,
) -> (bool, bool) {
let mut has_text = false;
let mut has_element_child = element.has_element_child;
for node in &element.children {
match scan_class(scope, node, ovr) {
ScanClass::Content => has_text = true,
ScanClass::Element => has_element_child = true,
_ => {}
}
if has_text && has_element_child {
break;
}
}
(has_text, has_element_child)
}
pub(crate) fn child_layout(
scope: &Scope<'_, '_>,
element: &Element<'_>,
ovr: Option<&Ovr<'_>>,
) -> (bool, bool) {
if element.children.is_empty() {
return (true, false);
}
if scope.ctx.is_none() {
return (false, element.has_element_child);
}
let mut any = false;
let mut has_element_child = element.has_element_child;
for node in &element.children {
match scan_class(scope, node, ovr) {
ScanClass::Skip => {}
ScanClass::Element => {
any = true;
has_element_child = true;
}
_ => {
any = true;
}
}
if any && has_element_child {
break;
}
}
(!any, has_element_child)
}