use crate::ParserSettings;
use crate::binxml::ir::{
IrTemplateCache, TEMPLATE_DEFINITION_HEADER_SIZE, build_tree_from_binxml_bytes_direct,
read_template_definition_header_at,
};
use crate::binxml::tokens::{single_instance_offset, token};
use crate::binxml::value_render::{StringEscapeMode, ValueRenderer};
use crate::err::Result;
use crate::evtx_chunk::EvtxChunk;
use crate::model::ir::{Attr, Element, ElementId, IrTree, Node, Placeholder, Text};
use crate::utils::ByteCursor;
use ahash::AHashMap;
use std::sync::Arc;
const INDENT_WIDTH: u16 = 2;
const XML_DECL: &[u8] = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct LitRange(u32, u32);
impl LitRange {
fn of(self, lits: &[u8]) -> &[u8] {
&lits[self.0 as usize..self.1 as usize]
}
}
type SlotRange = (u32, u32);
fn next_name_suffix<K: PartialEq>(name: K, counts: &mut Vec<(K, u16)>) -> u16 {
if let Some((_, c)) = counts.iter_mut().find(|(n, _)| *n == name) {
let suffix = *c;
*c += 1;
suffix
} else {
counts.push((name, 1));
0
}
}
#[derive(Debug, Clone)]
enum XOp {
Lit(LitRange),
Val { slot: u16, in_attr: bool },
AttrVal { slot: u16, pre: LitRange },
Body {
slot: u16,
optional: bool,
indent: u16,
tail_text: LitRange,
tail_empty: LitRange,
tail_elem: LitRange,
},
ChildSlot {
slot: u16,
optional: bool,
indent: u16,
ind: LitRange,
},
Expand {
slot: u16,
optional: bool,
indent: u16,
open: LitRange,
tail_text: LitRange,
tail_empty: LitRange,
tail_elem: LitRange,
},
}
pub(crate) struct XmlProgram {
lits: Vec<u8>,
ops: Vec<XOp>,
indent_on: bool,
elem_slots: Vec<(u16, u16)>,
expand_slots: Vec<u16>,
}
pub(crate) type ProgramCache<P> = AHashMap<(u32, u16, bool), Option<Arc<P>>>;
#[derive(Default)]
pub(crate) struct ProgramStore {
xml: std::sync::RwLock<AHashMap<StoreKey, Option<Arc<XmlProgram>>>>,
json: std::sync::RwLock<AHashMap<StoreKey, Option<Arc<JsonProgram>>>>,
hasher: ahash::RandomState,
}
type StoreKey = ([u8; 16], u32, u64, u16, bool);
impl std::fmt::Debug for ProgramStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProgramStore").finish_non_exhaustive()
}
}
pub(crate) trait StoredProgram: TemplateProgram {
fn shard(store: &ProgramStore) -> &std::sync::RwLock<AHashMap<StoreKey, Option<Arc<Self>>>>;
}
impl StoredProgram for XmlProgram {
fn shard(store: &ProgramStore) -> &std::sync::RwLock<AHashMap<StoreKey, Option<Arc<Self>>>> {
&store.xml
}
}
impl StoredProgram for JsonProgram {
fn shard(store: &ProgramStore) -> &std::sync::RwLock<AHashMap<StoreKey, Option<Arc<Self>>>> {
&store.json
}
}
#[derive(Default)]
pub(crate) struct RenderCaches {
pub(crate) xml: XmlProgramCache,
pub(crate) json: JsonProgramCache,
pub(crate) pf_xml: Preflight<XmlProgram>,
pub(crate) pf_json: Preflight<JsonProgram>,
}
impl std::fmt::Debug for RenderCaches {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RenderCaches")
.field("xml_programs", &self.xml.len())
.field("json_programs", &self.json.len())
.finish()
}
}
pub(crate) type XmlProgramCache = ProgramCache<XmlProgram>;
pub(crate) type JsonProgramCache = ProgramCache<JsonProgram>;
#[derive(Debug, Clone, Copy)]
pub(crate) enum SlotConstraint {
ForbidElem(u16),
ElemOrEmpty(u16),
}
pub(crate) trait TemplateProgram: Sized {
const ALLOW_GENERIC_FRAGS: bool;
fn elem_slots(&self) -> &[(u16, u16)];
fn constraints(&self) -> &[SlotConstraint] {
&[]
}
fn expand_slots(&self) -> &[u16] {
&[]
}
fn compile(
tree: &IrTree<'_>,
has_literal_array: bool,
base_indent: u16,
is_root: bool,
settings: &ParserSettings,
) -> Option<Self>;
}
impl TemplateProgram for XmlProgram {
const ALLOW_GENERIC_FRAGS: bool = true;
fn elem_slots(&self) -> &[(u16, u16)] {
&self.elem_slots
}
fn expand_slots(&self) -> &[u16] {
&self.expand_slots
}
fn compile(
tree: &IrTree<'_>,
has_literal_array: bool,
base_indent: u16,
is_root: bool,
settings: &ParserSettings,
) -> Option<Self> {
compile_xml_template(tree, has_literal_array, base_indent, is_root, settings)
}
}
struct Bail;
struct XmlCompiler<'t, 'a> {
tree: &'t IrTree<'a>,
lits: Vec<u8>,
ops: Vec<XOp>,
run_start: usize,
elem_slots: Vec<(u16, u16)>,
expand_slots: Vec<u16>,
indent_on: bool,
vr: ValueRenderer,
materialized: bool,
}
fn bail_err() -> crate::err::EvtxError {
crate::err::EvtxError::FailedToCreateRecordModel("compiled-template bail")
}
fn unresolved_placeholder() -> crate::err::EvtxError {
crate::err::EvtxError::FailedToCreateRecordModel("unresolved placeholder in tree")
}
fn compile_xml_template(
tree: &IrTree<'_>,
has_literal_array: bool,
base_indent: u16,
is_root: bool,
settings: &ParserSettings,
) -> Option<XmlProgram> {
if has_literal_array {
return None;
}
let mut c = XmlCompiler {
tree,
lits: Vec::with_capacity(512),
ops: Vec::with_capacity(32),
run_start: 0,
elem_slots: Vec::new(),
expand_slots: Vec::new(),
indent_on: settings.should_indent(),
vr: ValueRenderer::new(),
materialized: false,
};
if is_root {
c.lits.extend_from_slice(XML_DECL);
}
match c.compile_element(tree.root(), base_indent) {
Ok(()) => {
c.flush_lit_run();
Some(XmlProgram {
lits: c.lits,
ops: c.ops,
indent_on: c.indent_on,
elem_slots: c.elem_slots,
expand_slots: c.expand_slots,
})
}
Err(_) => None,
}
}
pub(crate) fn render_tree_xml(
tree: &IrTree<'_>,
settings: &ParserSettings,
out: &mut Vec<u8>,
) -> Result<()> {
let mut c = XmlCompiler {
tree,
lits: std::mem::take(out),
ops: Vec::new(),
run_start: 0,
elem_slots: Vec::new(),
expand_slots: Vec::new(),
indent_on: settings.should_indent(),
vr: ValueRenderer::new(),
materialized: true,
};
c.lits.extend_from_slice(XML_DECL);
let res = c.compile_element(tree.root(), 0);
debug_assert!(c.ops.is_empty(), "materialized walk produced ops");
*out = c.lits;
res
}
fn render_subtree_xml(
tree: &IrTree<'_>,
indent: u16,
indent_on: bool,
out: &mut Vec<u8>,
) -> Result<()> {
let mut c = XmlCompiler {
tree,
lits: std::mem::take(out),
ops: Vec::new(),
run_start: 0,
elem_slots: Vec::new(),
expand_slots: Vec::new(),
indent_on,
vr: ValueRenderer::new(),
materialized: true,
};
let res = c.compile_element(tree.root(), indent);
debug_assert!(c.ops.is_empty(), "materialized walk produced ops");
*out = c.lits;
res
}
impl<'t, 'a> XmlCompiler<'t, 'a> {
fn element_ref(&self, id: ElementId) -> &'t Element<'a> {
self.tree.arena().get(id).expect("invalid element id")
}
fn flush_lit_run(&mut self) {
let end = self.lits.len();
if end > self.run_start {
self.ops
.push(XOp::Lit(LitRange(self.run_start as u32, end as u32)));
}
self.run_start = end;
}
fn side_range(&mut self, f: impl FnOnce(&mut Self)) -> LitRange {
debug_assert_eq!(self.run_start, self.lits.len(), "unflushed lit run");
let start = self.lits.len() as u32;
f(self);
self.run_start = self.lits.len();
LitRange(start, self.lits.len() as u32)
}
fn indent_str(&mut self, level: u16) {
if self.indent_on {
self.lits.extend(std::iter::repeat_n(b' ', level as usize));
}
}
fn newline(&mut self) {
if self.indent_on {
self.lits.push(b'\n');
}
}
fn compile_element(&mut self, id: ElementId, indent: u16) -> Result<()> {
let element = self.element_ref(id);
if !self.materialized
&& element.attrs.is_empty()
&& is_data_element_name(element.name.as_str())
&& let ChildrenKind::SinglePlaceholder(ph) = classify_children(element)
{
return self.compile_expand(element, ph, indent);
}
self.indent_str(indent);
self.lits.push(b'<');
self.lits
.extend_from_slice(element.name.as_str().as_bytes());
for attr in &element.attrs {
self.compile_attr(attr)?;
}
match classify_children(element) {
ChildrenKind::SinglePlaceholder(ph) => {
if self.materialized {
return Err(unresolved_placeholder());
}
let name = element.name.as_str().as_bytes();
let is_binary = element.name.as_str() == "Binary";
self.lits.push(b'>');
self.flush_lit_run();
let tail_text = self.side_range(|c| {
c.lits.extend_from_slice(b"</");
c.lits.extend_from_slice(name);
c.lits.push(b'>');
c.newline();
});
let tail_empty = self.side_range(|c| {
if !is_binary {
c.newline();
c.indent_str(indent);
}
c.lits.extend_from_slice(b"</");
c.lits.extend_from_slice(name);
c.lits.push(b'>');
c.newline();
});
let tail_elem = self.side_range(|c| {
c.indent_str(indent);
c.lits.extend_from_slice(b"</");
c.lits.extend_from_slice(name);
c.lits.push(b'>');
c.newline();
});
self.elem_slots.push((ph.id, indent + INDENT_WIDTH));
self.ops.push(XOp::Body {
slot: ph.id,
optional: ph.optional,
indent,
tail_text,
tail_empty,
tail_elem,
});
}
ChildrenKind::Empty => {
self.lits.push(b'>');
if element.name.as_str() != "Binary" {
self.newline();
self.indent_str(indent);
}
self.close_tag_inline(element);
}
ChildrenKind::StaticInline => {
self.lits.push(b'>');
let nodes = &element.children;
let mut idx = 0;
while idx < nodes.len() {
match &nodes[idx] {
Node::PITarget(name) => {
self.lits.extend_from_slice(b"<?");
self.lits.extend_from_slice(name.as_str().as_bytes());
if let Some(Node::PIData(data)) = nodes.get(idx + 1) {
self.lits.push(b' ');
self.compile_raw_text(data);
self.lits.extend_from_slice(b"?>");
idx += 2;
continue;
}
self.lits.extend_from_slice(b"?>");
}
Node::PIData(_) => {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"PIData without PITarget",
));
}
node => self.compile_literal_content_node(node, false)?,
}
idx += 1;
}
self.close_tag_inline(element);
}
ChildrenKind::StaticLines => {
self.lits.push(b'>');
self.newline();
for node in &element.children {
match node {
Node::Element(child) => {
self.compile_element(*child, indent + INDENT_WIDTH)?;
}
Node::Placeholder(ph) => {
if self.materialized {
return Err(unresolved_placeholder());
}
self.flush_lit_run();
let ind = self.side_range(|c| c.indent_str(indent + INDENT_WIDTH));
self.elem_slots.push((ph.id, indent + INDENT_WIDTH));
self.ops.push(XOp::ChildSlot {
slot: ph.id,
optional: ph.optional,
indent: indent + INDENT_WIDTH,
ind,
});
}
other => {
self.indent_str(indent + INDENT_WIDTH);
self.compile_literal_content_node(other, false)?;
self.newline();
}
}
}
self.indent_str(indent);
self.close_tag_inline(element);
}
ChildrenKind::Bail => {
return Err(if self.materialized {
unresolved_placeholder()
} else {
bail_err()
});
}
}
Ok(())
}
fn close_tag_inline(&mut self, element: &Element<'_>) {
self.lits.extend_from_slice(b"</");
self.lits
.extend_from_slice(element.name.as_str().as_bytes());
self.lits.push(b'>');
self.newline();
}
fn compile_attr(&mut self, attr: &Attr<'a>) -> Result<()> {
let mut has_nonempty_const = false;
let mut n_placeholders = 0usize;
for node in attr.value.iter() {
match node {
Node::Placeholder(_) => {
if self.materialized {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"unresolved placeholder in attribute value",
));
}
n_placeholders += 1;
}
Node::Element(_) => {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"element node inside attribute value",
));
}
Node::PITarget(_) | Node::PIData(_) => {
return Err(crate::err::EvtxError::Unimplemented {
name: "processing instruction in attribute value".to_string(),
});
}
Node::Text(t) => {
if !t.is_empty() {
has_nonempty_const = true;
}
}
Node::CData(_) | Node::EntityRef(_) | Node::CharRef(_) => has_nonempty_const = true,
Node::Value(v) => {
if !crate::model::ir::is_optional_empty(v) {
has_nonempty_const = true;
}
}
}
}
if n_placeholders == 0 {
if !has_nonempty_const {
return Ok(()); }
self.lits.push(b' ');
self.lits.extend_from_slice(attr.name.as_str().as_bytes());
self.lits.extend_from_slice(b"=\"");
for node in attr.value.iter() {
self.compile_literal_content_node(node, true)?;
}
self.lits.push(b'"');
return Ok(());
}
if has_nonempty_const {
self.lits.push(b' ');
self.lits.extend_from_slice(attr.name.as_str().as_bytes());
self.lits.extend_from_slice(b"=\"");
for node in attr.value.iter() {
match node {
Node::Placeholder(ph) => {
self.flush_lit_run();
self.ops.push(XOp::Val {
slot: ph.id,
in_attr: true,
});
}
other => self.compile_literal_content_node(other, true)?,
}
}
self.lits.push(b'"');
return Ok(());
}
if n_placeholders > 1 {
return Err(bail_err());
}
let ph = attr
.value
.iter()
.find_map(|n| match n {
Node::Placeholder(ph) => Some(ph),
_ => None,
})
.expect("counted placeholder");
let name = attr.name.as_str().as_bytes();
self.flush_lit_run();
let pre = self.side_range(|c| {
c.lits.push(b' ');
c.lits.extend_from_slice(name);
c.lits.extend_from_slice(b"=\"");
});
self.ops.push(XOp::AttrVal { slot: ph.id, pre });
Ok(())
}
fn compile_expand(
&mut self,
element: &'t Element<'a>,
ph: &Placeholder,
indent: u16,
) -> Result<()> {
let name = element.name.as_str().as_bytes();
self.flush_lit_run();
let open = self.side_range(|c| {
c.indent_str(indent);
c.lits.push(b'<');
c.lits.extend_from_slice(name);
c.lits.push(b'>');
});
let tail_text = self.side_range(|c| {
c.lits.extend_from_slice(b"</");
c.lits.extend_from_slice(name);
c.lits.push(b'>');
c.newline();
});
let tail_empty = self.side_range(|c| {
c.newline();
c.indent_str(indent);
c.lits.extend_from_slice(b"</");
c.lits.extend_from_slice(name);
c.lits.push(b'>');
c.newline();
});
let tail_elem = self.side_range(|c| {
c.indent_str(indent);
c.lits.extend_from_slice(b"</");
c.lits.extend_from_slice(name);
c.lits.push(b'>');
c.newline();
});
self.elem_slots.push((ph.id, indent + INDENT_WIDTH));
self.expand_slots.push(ph.id);
self.ops.push(XOp::Expand {
slot: ph.id,
optional: ph.optional,
indent,
open,
tail_text,
tail_empty,
tail_elem,
});
Ok(())
}
fn compile_literal_content_node(&mut self, node: &Node<'a>, in_attribute: bool) -> Result<()> {
match node {
Node::Text(text) => self.compile_literal_text(text, in_attribute),
Node::Value(value) => {
let mut sink = std::mem::take(&mut self.lits);
let res = self.vr.write_xml_value_text(&mut sink, value, in_attribute);
self.lits = sink;
res
}
Node::EntityRef(name) => {
self.lits.push(b'&');
self.lits.extend_from_slice(name.as_str().as_bytes());
self.lits.push(b';');
Ok(())
}
Node::CharRef(ch) => {
self.lits.extend_from_slice(format!("&#{};", ch).as_bytes());
Ok(())
}
Node::CData(text) => {
if in_attribute {
self.compile_literal_text(text, true)
} else {
self.lits.extend_from_slice(b"<![CDATA[");
self.compile_raw_text(text);
self.lits.extend_from_slice(b"]]>");
Ok(())
}
}
Node::PITarget(_) | Node::PIData(_) => Ok(()),
Node::Element(_) => Err(crate::err::EvtxError::FailedToCreateRecordModel(
"unexpected element node in text context",
)),
Node::Placeholder(_) => Err(unresolved_placeholder()),
}
}
fn compile_literal_text(&mut self, text: &Text<'a>, in_attribute: bool) -> Result<()> {
match text {
Text::Utf16(value) => {
let bytes = value.as_bytes();
let units = bytes.len() / 2;
if units == 0 {
return Ok(());
}
let mut sink = std::mem::take(&mut self.lits);
let res = utf16_simd::write_xml_utf16le(&mut sink, bytes, units, in_attribute);
self.lits = sink;
res.map_err(crate::err::EvtxError::from)?;
Ok(())
}
Text::Utf8(value) => {
xml_escape_str_into(&mut self.lits, value, in_attribute);
Ok(())
}
}
}
fn compile_raw_text(&mut self, text: &Text<'a>) {
match text {
Text::Utf16(value) => {
let bytes = value.as_bytes();
let units = bytes.len() / 2;
if units > 0 {
let mut sink = std::mem::take(&mut self.lits);
let _ = utf16_simd::write_utf16le_raw(&mut sink, bytes, units);
self.lits = sink;
}
}
Text::Utf8(value) => self.lits.extend_from_slice(value.as_bytes()),
}
}
}
fn xml_escape_str_into(out: &mut Vec<u8>, text: &str, in_attribute: bool) {
for ch in text.chars() {
match ch {
'&' => out.extend_from_slice(b"&"),
'<' => out.extend_from_slice(b"<"),
'>' => out.extend_from_slice(b">"),
'"' if in_attribute => out.extend_from_slice(b"""),
'\'' if in_attribute => out.extend_from_slice(b"'"),
_ => {
let mut buf = [0_u8; 4];
out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
}
}
}
}
enum ChildrenKind<'n> {
Empty,
SinglePlaceholder(&'n Placeholder),
StaticInline,
StaticLines,
Bail,
}
fn classify_children<'n>(element: &'n Element<'_>) -> ChildrenKind<'n> {
if element.children.is_empty() {
return ChildrenKind::Empty;
}
if element.children.len() == 1
&& let Node::Placeholder(ph) = &element.children[0]
{
return ChildrenKind::SinglePlaceholder(ph);
}
let mut has_placeholder = false;
let mut has_literal_element = false;
let mut has_literal_content = false;
for node in &element.children {
match node {
Node::Placeholder(_) => has_placeholder = true,
Node::Element(_) => has_literal_element = true,
Node::PITarget(_) | Node::PIData(_) => {}
Node::Text(t) | Node::CData(t) => {
if !t.is_empty() {
has_literal_content = true;
}
}
Node::EntityRef(_) | Node::CharRef(_) => has_literal_content = true,
Node::Value(v) => {
if !crate::model::ir::is_optional_empty(v) {
has_literal_content = true;
}
}
}
}
if !has_placeholder {
if has_literal_element {
return ChildrenKind::StaticLines;
}
return ChildrenKind::StaticInline;
}
if has_literal_element && !has_literal_content {
return ChildrenKind::StaticLines;
}
ChildrenKind::Bail
}
fn subtree_has_placeholder(tree: &IrTree<'_>, element: &Element<'_>) -> bool {
for attr in &element.attrs {
if attr.value.iter().any(|n| matches!(n, Node::Placeholder(_))) {
return true;
}
}
for node in &element.children {
match node {
Node::Placeholder(_) => return true,
Node::Element(id)
if subtree_has_placeholder(tree, tree.arena().get(*id).expect("element id")) =>
{
return true;
}
_ => {}
}
}
false
}
#[derive(Clone, Copy)]
struct RawSlot {
off: u32,
len: u16,
ty: u8,
nested: u16,
aux: u32,
}
const NO_NESTED: u16 = u16::MAX;
const NO_AUX: u32 = u32::MAX;
impl RawSlot {
fn bytes<'d>(&self, data: &'d [u8]) -> &'d [u8] {
&data[self.off as usize..self.off as usize + usize::from(self.len)]
}
fn nested_idx(&self) -> Option<usize> {
(self.nested != NO_NESTED).then_some(usize::from(self.nested))
}
fn ansi_idx(&self) -> Option<usize> {
(self.ty == value_ty::ANSI_STRING && self.aux != NO_AUX).then_some(self.aux as usize)
}
fn is_binxml_payload(&self) -> bool {
self.ty == value_ty::BIN_XML && self.len > 0
}
}
struct NestedInst<P> {
prog: Arc<P>,
slots: SlotRange,
}
pub(crate) struct Preflight<P> {
slots: Vec<RawSlot>,
nested: Vec<NestedInst<P>>,
ansi: Vec<String>,
arrays: Vec<(u32, u32)>,
array_items: Vec<(u32, u16)>,
}
impl<P> Default for Preflight<P> {
fn default() -> Self {
Preflight {
slots: Vec::new(),
nested: Vec::new(),
ansi: Vec::new(),
arrays: Vec::new(),
array_items: Vec::new(),
}
}
}
pub(crate) mod value_ty {
pub(crate) const NULL: u8 = 0x00;
pub(crate) const UTF16_STRING: u8 = 0x01;
pub(crate) const ANSI_STRING: u8 = 0x02;
pub(crate) const INT8: u8 = 0x03;
pub(crate) const UINT8: u8 = 0x04;
pub(crate) const INT16: u8 = 0x05;
pub(crate) const UINT16: u8 = 0x06;
pub(crate) const INT32: u8 = 0x07;
pub(crate) const UINT32: u8 = 0x08;
pub(crate) const INT64: u8 = 0x09;
pub(crate) const UINT64: u8 = 0x0a;
pub(crate) const REAL32: u8 = 0x0b;
pub(crate) const REAL64: u8 = 0x0c;
pub(crate) const BOOL: u8 = 0x0d;
pub(crate) const BINARY: u8 = 0x0e;
pub(crate) const GUID: u8 = 0x0f;
pub(crate) const SIZE_T: u8 = 0x10;
pub(crate) const FILETIME: u8 = 0x11;
pub(crate) const SYSTIME: u8 = 0x12;
pub(crate) const SID: u8 = 0x13;
pub(crate) const HEX_INT32: u8 = 0x14;
pub(crate) const HEX_INT64: u8 = 0x15;
pub(crate) const BIN_XML: u8 = 0x21;
pub(crate) const STR_ARRAY: u8 = 0x81;
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum TyClass {
Reject,
Fixed(u8),
Utf16,
Sized,
Ansi,
SizeT,
Sid,
StrArray,
}
const TY_CLASS: [TyClass; 256] = {
use TyClass::*;
use value_ty::*;
let mut t = [Reject; 256];
t[NULL as usize] = Sized;
t[UTF16_STRING as usize] = Utf16;
t[ANSI_STRING as usize] = Ansi;
t[INT8 as usize] = Fixed(1);
t[UINT8 as usize] = Fixed(1);
t[INT16 as usize] = Fixed(2);
t[UINT16 as usize] = Fixed(2);
t[INT32 as usize] = Fixed(4);
t[UINT32 as usize] = Fixed(4);
t[INT64 as usize] = Fixed(8);
t[UINT64 as usize] = Fixed(8);
t[REAL32 as usize] = Fixed(4);
t[REAL64 as usize] = Fixed(8);
t[BOOL as usize] = Fixed(4); t[BINARY as usize] = Sized;
t[GUID as usize] = Fixed(16);
t[SIZE_T as usize] = SizeT;
t[FILETIME as usize] = Fixed(8);
t[SYSTIME as usize] = Fixed(16);
t[SID as usize] = Sid;
t[HEX_INT32 as usize] = Fixed(4);
t[HEX_INT64 as usize] = Fixed(8);
t[BIN_XML as usize] = Sized;
t[STR_ARRAY as usize] = StrArray;
t
};
const MAX_FAST_LANE_NESTING: usize = 8;
const MAX_SUBSTITUTIONS: usize = 4096;
fn ends_stream(stream: &[u8], consumed: usize) -> bool {
matches!(stream.get(consumed..), Some([]) | Some([token::EOF, ..]))
}
struct PreflightBail;
impl From<crate::err::DeserializationError> for PreflightBail {
fn from(_: crate::err::DeserializationError) -> Self {
PreflightBail
}
}
impl From<crate::err::EvtxError> for PreflightBail {
fn from(_: crate::err::EvtxError) -> Self {
PreflightBail
}
}
impl<P: StoredProgram> Preflight<P> {
fn clear(&mut self) {
self.slots.clear();
self.nested.clear();
self.ansi.clear();
self.arrays.clear();
self.array_items.clear();
}
fn str_array_items(&self, s: &RawSlot) -> &[(u32, u16)] {
debug_assert_eq!(s.ty, value_ty::STR_ARRAY);
let (start, count) = self.arrays[s.aux as usize];
&self.array_items[start as usize..(start + count) as usize]
}
fn slot_empty(&self, s: &RawSlot, data: &[u8]) -> bool {
match s.ty {
value_ty::NULL => true,
value_ty::UTF16_STRING => s.len == 0 || s.bytes(data).starts_with(&[0, 0]),
value_ty::ANSI_STRING => s.ansi_idx().is_none_or(|i| self.ansi[i].is_empty()),
value_ty::BINARY | value_ty::BIN_XML => s.len == 0,
_ => false,
}
}
#[allow(clippy::too_many_arguments)]
fn scan_instance<'a>(
&mut self,
chunk: &'a EvtxChunk<'a>,
pos: usize,
depth: usize,
cache: &mut IrTemplateCache<'a>,
progs: &mut ProgramCache<P>,
settings: &ParserSettings,
base_indent: u16,
is_root: bool,
) -> std::result::Result<(Arc<P>, SlotRange, usize), PreflightBail> {
if depth > MAX_FAST_LANE_NESTING {
return Err(PreflightBail);
}
let data = chunk.data;
let mut cur = ByteCursor::with_pos(data, pos)?;
cur.u8()?; let _template_id = cur.u32()?;
let def_offset = cur.u32()?;
if cur.position() as u32 == def_offset {
let header = read_template_definition_header_at(data, def_offset)?;
cur.set_pos_u64(
cur.position()
+ (TEMPLATE_DEFINITION_HEADER_SIZE as u64)
+ u64::from(header.data_size),
"skip inline template definition",
)?;
}
let n = cur.u32()? as usize;
if n > MAX_SUBSTITUTIONS {
return Err(PreflightBail);
}
let prog = get_or_compile(
chunk,
def_offset,
base_indent,
is_root,
cache,
progs,
settings,
)
.ok_or(PreflightBail)?;
let descriptors = cur.take_bytes(n * 4, "descriptor table")?;
let mut off = cur.pos();
let slot_start = self.slots.len() as u32;
for (i, desc) in descriptors.chunks_exact(4).enumerate() {
let &[len_lo, len_hi, ty, _pad] = desc else {
unreachable!("chunks_exact(4)");
};
let len = u16::from_le_bytes([len_lo, len_hi]);
let end = off + usize::from(len);
if end > data.len() {
return Err(PreflightBail);
}
match TY_CLASS[usize::from(ty)] {
TyClass::Reject => return Err(PreflightBail),
TyClass::Fixed(width) => {
if len != u16::from(width) {
return Err(PreflightBail);
}
}
TyClass::Utf16 => {
if len % 2 != 0 {
return Err(PreflightBail);
}
}
TyClass::SizeT => {
if !(len == 4 || len == 8) {
return Err(PreflightBail);
}
}
TyClass::Sid => {
let &[_revision, sub_count, ..] = &data[off..end] else {
return Err(PreflightBail);
};
if usize::from(len) != 8 + 4 * usize::from(sub_count) {
return Err(PreflightBail);
}
}
TyClass::StrArray => {
if len % 2 != 0 || !prog.expand_slots().contains(&(i as u16)) {
return Err(PreflightBail);
}
}
TyClass::Sized | TyClass::Ansi => {}
}
let mut slot = RawSlot {
off: off as u32,
len,
ty,
nested: NO_NESTED,
aux: NO_AUX,
};
if ty == value_ty::ANSI_STRING && len > 0 {
let raw = &data[off..end];
let filtered: Vec<u8> = raw.iter().copied().filter(|&b| b != 0).collect();
let decoded = settings
.get_ansi_codec()
.decode(&filtered, encoding::DecoderTrap::Strict)
.map_err(|_| PreflightBail)?;
slot.aux = self.ansi.len() as u32;
self.ansi.push(decoded);
} else if ty == value_ty::STR_ARRAY {
let items_start = self.array_items.len() as u32;
let mut item_off = off;
for (pi, pair) in data[off..end].chunks_exact(2).enumerate() {
if let &[0, 0] = pair {
let nul_at = off + pi * 2;
self.array_items
.push((item_off as u32, (nul_at - item_off) as u16));
item_off = nul_at + 2;
}
}
let count = self.array_items.len() as u32 - items_start;
if item_off != end || count == 0 {
return Err(PreflightBail);
}
slot.aux = self.arrays.len() as u32;
self.arrays.push((items_start, count));
}
self.slots.push(slot);
off = end;
}
let slot_range = (slot_start, self.slots.len() as u32);
for &(slot_id, child_indent) in prog.elem_slots() {
if u32::from(slot_id) >= slot_range.1 - slot_range.0 {
continue; }
let idx = slot_start as usize + slot_id as usize;
let s = self.slots[idx];
if !s.is_binxml_payload() {
continue;
}
let frag = s.bytes(data);
let Some(inst_off) = single_instance_offset(frag) else {
if P::ALLOW_GENERIC_FRAGS {
continue;
}
return Err(PreflightBail);
};
let (nprog, nslots, nend) = self.scan_instance(
chunk,
s.off as usize + inst_off,
depth + 1,
cache,
progs,
settings,
child_indent,
false,
)?;
if !ends_stream(frag, nend - s.off as usize) {
return Err(PreflightBail);
}
if self.nested.len() >= usize::from(u16::MAX) {
return Err(PreflightBail);
}
self.slots[idx].nested = self.nested.len() as u16;
self.nested.push(NestedInst {
prog: nprog,
slots: nslots,
});
}
for &c in prog.constraints() {
let (slot_id, forbid_elem) = match c {
SlotConstraint::ForbidElem(s) => (s, true),
SlotConstraint::ElemOrEmpty(s) => (s, false),
};
if u32::from(slot_id) >= slot_range.1 - slot_range.0 {
continue; }
let s = self.slots[slot_start as usize + slot_id as usize];
let is_elem = s.is_binxml_payload();
let empty = self.slot_empty(&s, data);
if (forbid_elem && is_elem) || (!forbid_elem && !is_elem && !empty) {
return Err(PreflightBail);
}
}
Ok((prog, slot_range, off))
}
}
fn get_or_compile<'a, P: StoredProgram>(
chunk: &'a EvtxChunk<'a>,
def_offset: u32,
base_indent: u16,
is_root: bool,
cache: &mut IrTemplateCache<'a>,
progs: &mut ProgramCache<P>,
settings: &ParserSettings,
) -> Option<Arc<P>> {
let key = (def_offset, base_indent, is_root);
if let Some(entry) = progs.get(&key) {
return entry.clone();
}
let store = &*chunk.program_store;
let store_key = template_content_key(chunk, def_offset, &store.hasher)
.map(|(guid, size, hash)| (guid, size, hash, base_indent, is_root));
if let Some(sk) = store_key.as_ref()
&& let Some(entry) = P::shard(store)
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(sk)
{
progs.insert(key, entry.clone());
return entry.clone();
}
let compiled =
cache
.template_for_compile(chunk, def_offset)
.ok()
.and_then(|(tree, has_literal_array)| {
P::compile(&tree, has_literal_array, base_indent, is_root, settings).map(Arc::new)
});
if let Some(sk) = store_key {
P::shard(store)
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(sk, compiled.clone());
}
progs.insert(key, compiled.clone());
compiled
}
fn template_content_key(
chunk: &EvtxChunk<'_>,
def_offset: u32,
hasher: &ahash::RandomState,
) -> Option<([u8; 16], u32, u64)> {
let header = read_template_definition_header_at(chunk.data, def_offset).ok()?;
let body_start = (def_offset as usize).checked_add(TEMPLATE_DEFINITION_HEADER_SIZE)?;
let body = chunk
.data
.get(body_start..body_start.checked_add(header.data_size as usize)?)?;
Some((header.guid, header.data_size, hasher.hash_one(body)))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn try_render_xml_compiled<'a>(
bytes: &[u8],
chunk: &'a EvtxChunk<'a>,
cache: &mut IrTemplateCache<'a>,
progs: &mut XmlProgramCache,
pf: &mut Preflight<XmlProgram>,
settings: &ParserSettings,
vr: &mut ValueRenderer,
out: &mut Vec<u8>,
) -> bool {
let Some(inst_off) = single_instance_offset(bytes) else {
return false;
};
let data_start = chunk.data.as_ptr() as usize;
let slice_start = bytes.as_ptr() as usize;
if slice_start < data_start || slice_start + bytes.len() > data_start + chunk.data.len() {
return false;
}
let stream_offset = slice_start - data_start;
pf.clear();
let (prog, slot_range, end) = match pf.scan_instance(
chunk,
stream_offset + inst_off,
0,
cache,
progs,
settings,
0,
true,
) {
Ok(v) => v,
Err(PreflightBail) => return false,
};
if !ends_stream(bytes, end - stream_offset) {
return false;
}
let start = out.len();
match exec(&prog, slot_range, pf, chunk, cache, vr, out) {
Ok(()) => true,
Err(_) => {
out.truncate(start);
false
}
}
}
enum SlotClass {
Skip,
TextLike,
Element,
}
fn exec<'a>(
prog: &XmlProgram,
slot_range: SlotRange,
pf: &Preflight<XmlProgram>,
chunk: &'a EvtxChunk<'a>,
cache: &mut IrTemplateCache<'a>,
vr: &mut ValueRenderer,
out: &mut Vec<u8>,
) -> Result<()> {
let lits = &prog.lits;
let slot_at = |slot: u16| -> Option<RawSlot> {
if u32::from(slot) < slot_range.1 - slot_range.0 {
Some(pf.slots[slot_range.0 as usize + slot as usize])
} else {
None
}
};
let classify = |slot: u16, optional: bool| -> SlotClass {
match slot_at(slot) {
None => SlotClass::Skip,
Some(s) => {
if pf.slot_empty(&s, chunk.data) {
if optional {
SlotClass::Skip
} else {
SlotClass::TextLike
}
} else if s.is_binxml_payload() {
SlotClass::Element
} else {
SlotClass::TextLike
}
}
}
};
macro_rules! write_lit {
($r:expr) => {
out.extend_from_slice($r.of(lits))
};
}
macro_rules! write_val {
($s:expr, $in_attr:expr) => {{
let s = $s;
let vb = s.bytes(chunk.data);
let ansi = s.ansi_idx().map(|i| pf.ansi[i].as_str());
vr.write_raw_value_text(
out,
s.ty,
vb,
ansi,
StringEscapeMode::Xml {
in_attribute: $in_attr,
},
)?;
}};
}
for op in &prog.ops {
match op {
XOp::Lit(r) => write_lit!(*r),
XOp::Val { slot, in_attr } => {
if let Some(s) = slot_at(*slot) {
if s.is_binxml_payload() {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"element node inside attribute value",
));
}
write_val!(s, *in_attr);
}
}
XOp::AttrVal { slot, pre } => {
if let Some(s) = slot_at(*slot)
&& !pf.slot_empty(&s, chunk.data)
{
if s.is_binxml_payload() {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"element node inside attribute value",
));
}
write_lit!(*pre);
write_val!(s, true);
out.push(b'"');
}
}
XOp::Body {
slot,
optional,
indent,
tail_text,
tail_empty,
tail_elem,
} => {
match classify(*slot, *optional) {
SlotClass::Skip => write_lit!(*tail_empty),
SlotClass::TextLike => {
if let Some(s) = slot_at(*slot) {
write_val!(s, false);
}
write_lit!(*tail_text);
}
SlotClass::Element => {
let s = slot_at(*slot).expect("element class implies present");
if prog.indent_on {
out.push(b'\n');
}
render_element_slot(
&s,
pf,
chunk,
cache,
vr,
*indent + INDENT_WIDTH,
prog.indent_on,
out,
)?;
write_lit!(*tail_elem);
}
}
}
XOp::ChildSlot {
slot,
optional,
indent,
ind,
} => match classify(*slot, *optional) {
SlotClass::Skip => {}
SlotClass::TextLike => {
write_lit!(*ind);
if let Some(s) = slot_at(*slot) {
write_val!(s, false);
}
if prog.indent_on {
out.push(b'\n');
}
}
SlotClass::Element => {
let s = slot_at(*slot).expect("element class implies present");
render_element_slot(&s, pf, chunk, cache, vr, *indent, prog.indent_on, out)?;
}
},
XOp::Expand {
slot,
optional,
indent,
open,
tail_text,
tail_empty,
tail_elem,
} => {
if let Some(s) = slot_at(*slot).filter(|s| s.ty == value_ty::STR_ARRAY) {
let items = pf.str_array_items(&s);
let multi = items.len() > 1;
for &(ioff, ilen) in items {
write_lit!(*open);
if multi && ilen == 0 {
write_lit!(*tail_empty);
} else {
vr.write_raw_value_text(
out,
value_ty::UTF16_STRING,
&chunk.data[ioff as usize..ioff as usize + usize::from(ilen)],
None,
StringEscapeMode::Xml {
in_attribute: false,
},
)?;
write_lit!(*tail_text);
}
}
} else {
write_lit!(*open);
match classify(*slot, *optional) {
SlotClass::Skip => write_lit!(*tail_empty),
SlotClass::TextLike => {
if let Some(s) = slot_at(*slot) {
write_val!(s, false);
}
write_lit!(*tail_text);
}
SlotClass::Element => {
let s = slot_at(*slot).expect("element class implies present");
if prog.indent_on {
out.push(b'\n');
}
render_element_slot(
&s,
pf,
chunk,
cache,
vr,
*indent + INDENT_WIDTH,
prog.indent_on,
out,
)?;
write_lit!(*tail_elem);
}
}
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn render_element_slot<'a>(
s: &RawSlot,
pf: &Preflight<XmlProgram>,
chunk: &'a EvtxChunk<'a>,
cache: &mut IrTemplateCache<'a>,
vr: &mut ValueRenderer,
indent: u16,
indent_on: bool,
out: &mut Vec<u8>,
) -> Result<()> {
if let Some(idx) = s.nested_idx() {
let inst = &pf.nested[idx];
return exec(&inst.prog, inst.slots, pf, chunk, cache, vr, out);
}
let frag = s.bytes(chunk.data);
let tree = build_tree_from_binxml_bytes_direct(frag, chunk, cache)?;
render_subtree_xml(&tree, indent, indent_on, out)
}
#[derive(Debug, Clone)]
enum JAttrPart {
Literal(LitRange),
Placeholder { key: LitRange, slot: u16 },
}
#[derive(Debug, Clone)]
enum JOp {
Lit(LitRange),
LeafVal { slot: u16, empty: LitRange },
Elem {
attrs: Box<[JAttrPart]>,
content: Option<u16>,
empty: LitRange,
},
SlotChild {
slot: u16,
static_names: Box<[(LitRange, u16)]>,
lead_comma: bool,
},
}
pub(crate) struct JsonProgram {
lits: Vec<u8>,
ops: Vec<JOp>,
elem_slots: Vec<(u16, u16)>,
constraints: Vec<SlotConstraint>,
expand_slots: Vec<u16>,
root_name: Vec<u8>,
}
impl TemplateProgram for JsonProgram {
const ALLOW_GENERIC_FRAGS: bool = false;
fn elem_slots(&self) -> &[(u16, u16)] {
&self.elem_slots
}
fn constraints(&self) -> &[SlotConstraint] {
&self.constraints
}
fn expand_slots(&self) -> &[u16] {
&self.expand_slots
}
fn compile(
tree: &IrTree<'_>,
has_literal_array: bool,
_base_indent: u16,
is_root: bool,
settings: &ParserSettings,
) -> Option<Self> {
compile_json_template(tree, has_literal_array, is_root, settings)
}
}
struct JsonCompiler<'t, 'a> {
tree: Option<&'t IrTree<'a>>,
lits: Vec<u8>,
ops: Vec<JOp>,
run_start: usize,
elem_slots: Vec<(u16, u16)>,
constraints: Vec<SlotConstraint>,
expand_slots: Vec<u16>,
vr: ValueRenderer,
formatter: sonic_rs::format::CompactFormatter,
separate: bool,
}
fn compile_json_template(
tree: &IrTree<'_>,
has_literal_array: bool,
is_root: bool,
settings: &ParserSettings,
) -> Option<JsonProgram> {
if has_literal_array || settings.should_separate_json_attributes() {
return None;
}
let root = tree.root_element();
let root_container = is_data_container_name(root.name.as_str());
let mut c = JsonCompiler {
tree: Some(tree),
lits: Vec::with_capacity(512),
ops: Vec::with_capacity(32),
run_start: 0,
elem_slots: Vec::new(),
constraints: Vec::new(),
expand_slots: Vec::new(),
vr: ValueRenderer::new(),
formatter: sonic_rs::format::CompactFormatter,
separate: false,
};
if is_root {
c.lits.push(b'{');
c.lits.push(b'"');
c.lits.extend_from_slice(root.name.as_str().as_bytes());
c.lits.extend_from_slice(b"\":");
}
match c.compile_element_value(tree.root(), root_container) {
Ok(()) => {
if is_root {
c.lits.push(b'}');
}
c.flush_lit_run();
Some(JsonProgram {
lits: c.lits,
ops: c.ops,
elem_slots: c.elem_slots,
constraints: c.constraints,
expand_slots: c.expand_slots,
root_name: root.name.as_str().as_bytes().to_vec(),
})
}
Err(Bail) => None,
}
}
fn is_data_container_name(name: &str) -> bool {
name == "EventData" || name == "UserData"
}
fn is_data_element_name(name: &str) -> bool {
name == "Data"
}
fn literal_nonempty(node: &Node<'_>) -> bool {
match node {
Node::Text(t) | Node::CData(t) => !t.is_empty(),
Node::EntityRef(_) | Node::CharRef(_) => true,
Node::Value(v) => !crate::model::ir::is_optional_empty(v),
Node::Element(_) | Node::Placeholder(_) | Node::PITarget(_) | Node::PIData(_) => false,
}
}
impl<'t, 'a> JsonCompiler<'t, 'a> {
fn tree(&self) -> &'t IrTree<'a> {
self.tree.expect("tree-bound walk")
}
fn flush_lit_run(&mut self) {
let end = self.lits.len();
if end > self.run_start {
self.ops
.push(JOp::Lit(LitRange(self.run_start as u32, end as u32)));
}
self.run_start = end;
}
fn side_range(&mut self, f: impl FnOnce(&mut Self)) -> LitRange {
debug_assert_eq!(self.run_start, self.lits.len(), "unflushed lit run");
let start = self.lits.len() as u32;
f(self);
self.run_start = self.lits.len();
LitRange(start, self.lits.len() as u32)
}
fn compile_element_value(
&mut self,
id: ElementId,
container: bool,
) -> std::result::Result<(), Bail> {
let element = self.element_ref(id);
if !subtree_has_placeholder(self.tree(), element) {
return self
.write_element_value_plain(element, container)
.map_err(|_| Bail);
}
let ph_attrs = element
.attrs
.iter()
.any(|a| a.value.iter().any(|n| matches!(n, Node::Placeholder(_))));
let static_attr_text = element.attrs.iter().any(|a| {
!a.value.iter().any(|n| matches!(n, Node::Placeholder(_)))
&& a.value.iter().any(literal_nonempty)
});
match classify_children(element) {
ChildrenKind::SinglePlaceholder(ph) => {
if !ph_attrs && !static_attr_text {
self.compile_leaf_val(ph.id, b"null", container)
} else {
self.compile_elem_op(element, Some(ph.id))
}
}
ChildrenKind::Empty => {
if !ph_attrs {
Err(Bail)
} else {
self.compile_elem_op(element, None)
}
}
ChildrenKind::StaticInline => Err(Bail), ChildrenKind::StaticLines => self.compile_object_body(element, container, ph_attrs),
ChildrenKind::Bail => Err(Bail),
}
}
fn compile_leaf_val(
&mut self,
slot: u16,
empty_form: &[u8],
container: bool,
) -> std::result::Result<(), Bail> {
if container {
return Err(Bail);
}
self.flush_lit_run();
let empty = self.side_range(|c| c.lits.extend_from_slice(empty_form));
self.elem_slots.push((slot, 0));
self.ops.push(JOp::LeafVal { slot, empty });
Ok(())
}
fn compile_elem_op(
&mut self,
element: &'t Element<'a>,
content: Option<u16>,
) -> std::result::Result<(), Bail> {
let mut parts: Vec<JAttrPart> = Vec::with_capacity(element.attrs.len());
self.flush_lit_run();
for attr in &element.attrs {
let n_ph = attr
.value
.iter()
.filter(|n| matches!(n, Node::Placeholder(_)))
.count();
match n_ph {
0 => {
if !attr.value.iter().any(literal_nonempty) {
continue; }
let start = self.lits.len() as u32;
self.lits.push(b'"');
self.lits.extend_from_slice(attr.name.as_str().as_bytes());
self.lits.extend_from_slice(b"\":");
let number = self
.try_as_number_plain(&attr.value, false)
.map_err(|_| Bail)?;
if !number {
self.lits.push(b'"');
self.text_content_plain(&attr.value, false)
.map_err(|_| Bail)?;
self.lits.push(b'"');
}
let lit_member = LitRange(start, self.lits.len() as u32);
self.run_start = self.lits.len();
parts.push(JAttrPart::Literal(lit_member));
}
1 if attr.value.len() == 1 => {
let Node::Placeholder(ph) = &attr.value[0] else {
return Err(Bail);
};
let name = attr.name.as_str().as_bytes();
let key = self.side_range(|c| {
c.lits.push(b'"');
c.lits.extend_from_slice(name);
c.lits.extend_from_slice(b"\":");
});
self.constraints.push(SlotConstraint::ForbidElem(ph.id));
parts.push(JAttrPart::Placeholder { key, slot: ph.id });
}
_ => return Err(Bail),
}
}
if let Some(slot) = content {
self.constraints.push(SlotConstraint::ForbidElem(slot));
}
let empty = self.side_range(|c| c.lits.extend_from_slice(b"null"));
self.ops.push(JOp::Elem {
attrs: parts.into_boxed_slice(),
content,
empty,
});
Ok(())
}
fn compile_object_body(
&mut self,
element: &'t Element<'a>,
container: bool,
ph_attrs: bool,
) -> std::result::Result<(), Bail> {
if ph_attrs {
return Err(Bail); }
self.lits.push(b'{');
let mut wrote_any = false;
if !element.attrs.is_empty() && self.attrs_object_plain(&element.attrs).map_err(|_| Bail)? {
wrote_any = true;
}
let mut flatten_named = false;
if container {
for node in &element.children {
if let Node::Element(id) = node {
let child = self.element_ref(*id);
if is_data_element_name(child.name.as_str())
&& let Some(attr) = child.attrs.iter().find(|a| a.name.as_str() == "Name")
{
if attr.value.iter().any(|n| matches!(n, Node::Placeholder(_))) {
return Err(Bail); }
if attr.value.iter().any(literal_nonempty) {
flatten_named = true;
break;
}
}
}
}
}
let positional_data: Vec<ElementId> = if container && !flatten_named {
element
.children
.iter()
.filter_map(|n| match n {
Node::Element(id)
if is_data_element_name(self.element_ref(*id).name.as_str()) =>
{
Some(*id)
}
_ => None,
})
.collect()
} else {
Vec::new()
};
let mut positional_emitted = false;
let mut static_names: Vec<(&[u8], u16)> = Vec::new();
let mut seen_slot_child = false;
for node in &element.children {
match node {
Node::Element(id) => {
if seen_slot_child {
return Err(Bail); }
let child = self.element_ref(*id);
let cname = child.name.as_str();
if container && is_data_element_name(cname) {
if flatten_named {
let Some(attr) = child.attrs.iter().find(|a| a.name.as_str() == "Name")
else {
continue; };
if !attr.value.iter().any(literal_nonempty) {
continue; }
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
self.lits.push(b'"');
self.text_content_plain(&attr.value, false)
.map_err(|_| Bail)?;
self.lits.extend_from_slice(b"\":");
self.compile_data_value(*id, false)?;
} else if !positional_emitted && !positional_data.is_empty() {
positional_emitted = true;
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
self.lits.extend_from_slice(b"\"Data\":{\"#text\":");
if positional_data.len() == 1 {
self.compile_data_value(positional_data[0], true)?;
} else {
self.lits.push(b'[');
for (i, did) in positional_data.iter().enumerate() {
if i > 0 {
self.lits.push(b',');
}
self.compile_data_value(*did, false)?;
}
self.lits.push(b']');
}
self.lits.push(b'}');
}
continue;
}
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
let suffix = next_name_suffix(cname.as_bytes(), &mut static_names);
self.lits.push(b'"');
self.lits.extend_from_slice(cname.as_bytes());
if suffix > 0 {
self.lits.push(b'_');
self.lits.extend_from_slice(suffix.to_string().as_bytes());
}
self.lits.extend_from_slice(b"\":");
self.compile_element_value(*id, is_data_container_name(cname))?;
}
Node::Placeholder(ph) => {
self.flush_lit_run();
let name_ranges: Vec<(LitRange, u16)> = static_names
.iter()
.map(|(n, c)| {
let r = self.side_range(|cc| cc.lits.extend_from_slice(n));
(r, *c)
})
.collect();
self.constraints.push(SlotConstraint::ElemOrEmpty(ph.id));
self.elem_slots.push((ph.id, 0));
self.ops.push(JOp::SlotChild {
slot: ph.id,
static_names: name_ranges.into_boxed_slice(),
lead_comma: wrote_any,
});
seen_slot_child = true;
}
other => {
if literal_nonempty(other) {
return Err(Bail); }
}
}
}
self.lits.push(b'}');
Ok(())
}
fn compile_data_value(
&mut self,
id: ElementId,
expandable: bool,
) -> std::result::Result<(), Bail> {
let element = self.element_ref(id);
if !subtree_has_placeholder(self.tree(), element) {
return self.data_element_value_plain(element).map_err(|_| Bail);
}
if element.children.len() == 1
&& let Node::Placeholder(ph) = &element.children[0]
{
self.flush_lit_run();
let empty = self.side_range(|c| c.lits.extend_from_slice(b"\"\""));
self.elem_slots.push((ph.id, 0));
if expandable {
self.expand_slots.push(ph.id);
}
self.ops.push(JOp::LeafVal { slot: ph.id, empty });
return Ok(());
}
Err(Bail)
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn try_render_json_compiled<'a>(
bytes: &[u8],
chunk: &'a EvtxChunk<'a>,
cache: &mut IrTemplateCache<'a>,
progs: &mut JsonProgramCache,
pf: &mut Preflight<JsonProgram>,
settings: &ParserSettings,
vr: &mut ValueRenderer,
out: &mut Vec<u8>,
) -> bool {
let Some(inst_off) = single_instance_offset(bytes) else {
return false;
};
let data_start = chunk.data.as_ptr() as usize;
let slice_start = bytes.as_ptr() as usize;
if slice_start < data_start || slice_start + bytes.len() > data_start + chunk.data.len() {
return false;
}
let stream_offset = slice_start - data_start;
pf.clear();
let (prog, slot_range, end) = match pf.scan_instance(
chunk,
stream_offset + inst_off,
0,
cache,
progs,
settings,
0,
true,
) {
Ok(v) => v,
Err(PreflightBail) => return false,
};
if !ends_stream(bytes, end - stream_offset) {
return false;
}
let start = out.len();
match exec_json(&prog, slot_range, pf, chunk, vr, out) {
Ok(()) => true,
Err(_) => {
out.truncate(start);
false
}
}
}
fn json_bare_type(ty: u8) -> bool {
matches!(ty, value_ty::INT8..=value_ty::UINT64 | value_ty::BOOL)
}
fn exec_json(
prog: &JsonProgram,
slot_range: SlotRange,
pf: &Preflight<JsonProgram>,
chunk: &EvtxChunk<'_>,
vr: &mut ValueRenderer,
out: &mut Vec<u8>,
) -> Result<()> {
let lits = &prog.lits;
let slot_at = |slot: u16| -> Option<RawSlot> {
if u32::from(slot) < slot_range.1 - slot_range.0 {
Some(pf.slots[slot_range.0 as usize + slot as usize])
} else {
None
}
};
macro_rules! write_lit {
($r:expr) => {
out.extend_from_slice($r.of(lits))
};
}
macro_rules! write_scalar {
($s:expr) => {{
let s = $s;
let vb = s.bytes(chunk.data);
let ansi = s.ansi_idx().map(|i| pf.ansi[i].as_str());
if json_bare_type(s.ty) {
vr.write_raw_value_text(out, s.ty, vb, ansi, StringEscapeMode::Json)?;
} else {
out.push(b'"');
vr.write_raw_value_text(out, s.ty, vb, ansi, StringEscapeMode::Json)?;
out.push(b'"');
}
}};
}
for op in &prog.ops {
match op {
JOp::Lit(r) => write_lit!(*r),
JOp::LeafVal { slot, empty } => match slot_at(*slot) {
None => write_lit!(*empty),
Some(s) if s.ty == value_ty::STR_ARRAY => {
let items = pf.str_array_items(&s);
let multi = items.len() > 1;
if multi {
out.push(b'[');
}
for (i, &(ioff, ilen)) in items.iter().enumerate() {
if i > 0 {
out.push(b',');
}
out.push(b'"');
vr.write_raw_value_text(
out,
value_ty::UTF16_STRING,
&chunk.data[ioff as usize..ioff as usize + usize::from(ilen)],
None,
StringEscapeMode::Json,
)?;
out.push(b'"');
}
if multi {
out.push(b']');
}
}
Some(s) => {
if pf.slot_empty(&s, chunk.data) {
write_lit!(*empty);
} else if s.is_binxml_payload() {
let Some(idx) = s.nested_idx() else {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"unresolved nested instance in compiled JSON",
));
};
let inst = &pf.nested[idx];
out.push(b'{');
out.push(b'"');
out.extend_from_slice(&inst.prog.root_name);
out.extend_from_slice(b"\":");
exec_json(&inst.prog, inst.slots, pf, chunk, vr, out)?;
out.push(b'}');
} else {
write_scalar!(s);
}
}
},
JOp::Elem {
attrs,
content,
empty,
} => {
let attr_present = attrs.iter().any(|part| match *part {
JAttrPart::Literal(_) => true,
JAttrPart::Placeholder { slot, .. } => {
slot_at(slot).is_some_and(|s| !pf.slot_empty(&s, chunk.data))
}
});
let content_slot =
content.and_then(|c| slot_at(c).filter(|s| !pf.slot_empty(s, chunk.data)));
match (attr_present, content_slot) {
(false, None) => write_lit!(*empty),
(false, Some(s)) => write_scalar!(s),
(true, content_slot) => {
out.extend_from_slice(b"{\"#attributes\":{");
let mut first = true;
for part in attrs.iter() {
match *part {
JAttrPart::Literal(member) => {
if !first {
out.push(b',');
}
first = false;
write_lit!(member);
}
JAttrPart::Placeholder { key, slot } => {
if let Some(s) = slot_at(slot)
&& !pf.slot_empty(&s, chunk.data)
{
if !first {
out.push(b',');
}
first = false;
write_lit!(key);
write_scalar!(s);
}
}
}
}
out.push(b'}');
if let Some(s) = content_slot {
out.extend_from_slice(b",\"#text\":");
write_scalar!(s);
}
out.push(b'}');
}
}
}
JOp::SlotChild {
slot,
static_names,
lead_comma,
} => {
let Some(s) = slot_at(*slot) else { continue };
if pf.slot_empty(&s, chunk.data) || !s.is_binxml_payload() {
continue; }
let Some(idx) = s.nested_idx() else {
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"unresolved nested instance in compiled JSON",
));
};
let inst = &pf.nested[idx];
let name = inst.prog.root_name.as_slice();
let mut count: u16 = 0;
for (r, c) in static_names.iter() {
if &lits[r.0 as usize..r.1 as usize] == name {
count = *c;
break;
}
}
if *lead_comma {
out.push(b',');
}
out.push(b'"');
out.extend_from_slice(name);
if count > 0 {
out.push(b'_');
out.extend_from_slice(count.to_string().as_bytes());
}
out.extend_from_slice(b"\":");
exec_json(&inst.prog, inst.slots, pf, chunk, vr, out)?;
}
}
}
Ok(())
}
#[cfg(feature = "bench")]
pub(crate) fn bench_json_text_content(out: &mut Vec<u8>, nodes: &[Node<'_>]) -> Result<()> {
let mut c = JsonCompiler {
tree: None,
lits: std::mem::take(out),
ops: Vec::new(),
run_start: 0,
elem_slots: Vec::new(),
constraints: Vec::new(),
expand_slots: Vec::new(),
vr: ValueRenderer::new(),
formatter: sonic_rs::format::CompactFormatter,
separate: false,
};
let res = c.text_content_plain(nodes, false);
*out = c.lits;
res
}
pub(crate) fn render_tree_json(
tree: &IrTree<'_>,
settings: &ParserSettings,
out: &mut Vec<u8>,
) -> Result<()> {
let mut c = JsonCompiler {
tree: Some(tree),
lits: std::mem::take(out),
ops: Vec::new(),
run_start: 0,
elem_slots: Vec::new(),
constraints: Vec::new(),
expand_slots: Vec::new(),
vr: ValueRenderer::new(),
formatter: sonic_rs::format::CompactFormatter,
separate: settings.should_separate_json_attributes(),
};
let res = c.render_root_plain();
debug_assert!(c.ops.is_empty(), "materialized walk produced ops");
*out = c.lits;
res
}
impl<'t, 'a> JsonCompiler<'t, 'a> {
fn render_root_plain(&mut self) -> Result<()> {
let root = self.tree().root_element();
self.lits.push(b'{');
if self.separate {
if !root.attrs.is_empty() && self.render_separate_attrs_plain(root, 0)? {
self.lits.push(b',');
}
self.key_with_suffix_plain(root.name.as_str(), 0);
self.write_element_value_no_attrs_plain(root, false)?;
} else {
self.lits.push(b'"');
self.lits.extend_from_slice(root.name.as_str().as_bytes());
self.lits.extend_from_slice(b"\":");
self.write_element_value_plain(root, false)?;
}
self.lits.push(b'}');
Ok(())
}
fn element_ref(&self, id: ElementId) -> &'t Element<'a> {
self.tree().arena().get(id).expect("invalid element id")
}
fn key_with_suffix_plain(&mut self, name: &str, suffix: u16) {
self.lits.push(b'"');
self.lits.extend_from_slice(name.as_bytes());
if suffix > 0 {
self.lits.push(b'_');
self.lits.extend_from_slice(suffix.to_string().as_bytes());
}
self.lits.extend_from_slice(b"\":");
}
fn json_escaped_str_plain(&mut self, s: &str) -> Result<()> {
use sonic_rs::format::Formatter;
self.formatter
.write_string_fast(&mut self.lits, s, false)
.map_err(crate::err::EvtxError::from)?;
Ok(())
}
fn json_text_plain(&mut self, text: &Text<'_>) -> Result<()> {
if text.is_empty() {
return Ok(());
}
match text {
Text::Utf16(value) => {
let bytes = value.as_bytes();
let units = bytes.len() / 2;
if units > 0 {
utf16_simd::write_json_utf16le(&mut self.lits, bytes, units, false)
.map_err(crate::err::EvtxError::from)?;
}
Ok(())
}
Text::Utf8(value) => self.json_escaped_str_plain(value),
}
}
fn write_u64_plain(&mut self, v: u64) -> Result<()> {
use sonic_rs::format::Formatter;
self.formatter
.write_u64(&mut self.lits, v)
.map_err(crate::err::EvtxError::from)?;
Ok(())
}
fn write_i64_plain(&mut self, v: i64) -> Result<()> {
use sonic_rs::format::Formatter;
self.formatter
.write_i64(&mut self.lits, v)
.map_err(crate::err::EvtxError::from)?;
Ok(())
}
fn value_as_number_plain(
&mut self,
value: &crate::binxml::value_variant::BinXmlValue<'_>,
) -> Result<bool> {
use crate::binxml::value_variant::BinXmlValue;
match value {
BinXmlValue::Int8Type(v) => self.write_i64_plain(i64::from(*v)).map(|_| true),
BinXmlValue::Int16Type(v) => self.write_i64_plain(i64::from(*v)).map(|_| true),
BinXmlValue::Int32Type(v) => self.write_i64_plain(i64::from(*v)).map(|_| true),
BinXmlValue::Int64Type(v) => self.write_i64_plain(*v).map(|_| true),
BinXmlValue::UInt8Type(v) => self.write_u64_plain(u64::from(*v)).map(|_| true),
BinXmlValue::UInt16Type(v) => self.write_u64_plain(u64::from(*v)).map(|_| true),
BinXmlValue::UInt32Type(v) => self.write_u64_plain(u64::from(*v)).map(|_| true),
BinXmlValue::UInt64Type(v) => self.write_u64_plain(*v).map(|_| true),
BinXmlValue::BoolType(v) => {
self.lits
.extend_from_slice(if *v { b"true" } else { b"false" });
Ok(true)
}
_ => Ok(false),
}
}
fn node_is_content_plain(node: &Node<'_>) -> bool {
match node {
Node::Text(t) | Node::CData(t) => !t.is_empty(),
Node::EntityRef(_) | Node::CharRef(_) => true,
Node::Value(v) => !crate::model::ir::is_optional_empty(v),
Node::Placeholder(_) => true,
Node::Element(_) | Node::PITarget(_) | Node::PIData(_) => false,
}
}
fn has_text_content_plain(nodes: &[Node<'_>]) -> bool {
nodes.iter().any(Self::node_is_content_plain)
}
fn content_layout_plain(element: &Element<'_>) -> (bool, bool) {
let mut has_text = false;
let mut has_element_child = element.has_element_child;
for node in &element.children {
if matches!(node, Node::Element(_)) {
has_element_child = true;
} else if Self::node_is_content_plain(node) {
has_text = true;
}
if has_text && has_element_child {
break;
}
}
(has_text, has_element_child)
}
fn attr_flags_plain(attrs: &[Attr<'_>]) -> (bool, bool) {
if attrs.is_empty() {
return (false, false);
}
for attr in attrs {
if Self::has_text_content_plain(&attr.value) {
return (true, true);
}
}
(true, false)
}
fn text_content_plain(&mut self, nodes: &[Node<'a>], skip_elements: bool) -> Result<()> {
for node in nodes {
match node {
Node::Text(text) | Node::CData(text) => self.json_text_plain(text)?,
Node::Value(value) => {
let mut sink = std::mem::take(&mut self.lits);
let res = self.vr.write_json_value_text(&mut sink, value);
self.lits = sink;
res?;
}
Node::CharRef(ch) => {
if let Some(ch) = char::from_u32(u32::from(*ch)) {
let mut buf = [0_u8; 4];
let s = ch.encode_utf8(&mut buf);
self.json_escaped_str_plain(s)?;
} else {
self.lits.extend_from_slice(b"&#");
self.write_u64_plain(u64::from(*ch))?;
self.lits.push(b';');
}
}
Node::EntityRef(name) => {
let resolved = match name.as_str() {
"quot" => Some("\""),
"apos" => Some("'"),
"amp" => Some("&"),
"lt" => Some("<"),
"gt" => Some(">"),
_ => None,
};
match resolved {
Some(s) => self.json_escaped_str_plain(s)?,
None => {
self.lits.push(b'&');
self.lits.extend_from_slice(name.as_str().as_bytes());
self.lits.push(b';');
}
}
}
Node::PITarget(_) | Node::PIData(_) => {}
Node::Placeholder(_) => return Err(unresolved_placeholder()),
Node::Element(_) => {
if skip_elements {
continue;
}
return Err(crate::err::EvtxError::FailedToCreateRecordModel(
"unexpected element node in text context",
));
}
}
}
Ok(())
}
fn try_as_number_plain(&mut self, nodes: &[Node<'a>], skip_elements: bool) -> Result<bool> {
let mut single: Option<&Node<'a>> = None;
for node in nodes {
match node {
Node::Element(_) => {
if skip_elements {
continue;
}
return Ok(false);
}
Node::Text(t) | Node::CData(t) => {
if skip_elements {
if !t.is_empty() {
return Ok(false);
}
} else if single.is_some() || !t.is_empty() {
return Ok(false);
} else {
single = Some(node);
}
}
Node::Value(v) => {
if skip_elements && crate::model::ir::is_optional_empty(v) {
continue;
}
if single.is_some() {
return Ok(false);
}
single = Some(node);
}
Node::CharRef(_) | Node::EntityRef(_) => return Ok(false),
Node::PITarget(_) | Node::PIData(_) => {
if !skip_elements && single.is_some() {
return Ok(false);
}
if !skip_elements {
single = Some(node);
}
}
Node::Placeholder(_) => {
if skip_elements {
return Err(unresolved_placeholder());
}
return Ok(false);
}
}
}
match single {
Some(Node::Value(value)) => self.value_as_number_plain(value),
_ => Ok(false),
}
}
fn content_as_json_value_plain(
&mut self,
nodes: &[Node<'a>],
skip_elements: bool,
) -> Result<()> {
if self.try_as_number_plain(nodes, skip_elements)? {
return Ok(());
}
self.lits.push(b'"');
self.text_content_plain(nodes, skip_elements)?;
self.lits.push(b'"');
Ok(())
}
fn attrs_object_body_plain(&mut self, attrs: &[Attr<'a>]) -> Result<bool> {
let mut wrote_any = false;
let mut first = true;
for attr in attrs {
if !Self::has_text_content_plain(&attr.value) {
continue;
}
if !first {
self.lits.push(b',');
}
first = false;
wrote_any = true;
self.lits.push(b'"');
self.lits.extend_from_slice(attr.name.as_str().as_bytes());
self.lits.extend_from_slice(b"\":");
if self.try_as_number_plain(&attr.value, false)? {
continue;
}
self.lits.push(b'"');
self.text_content_plain(&attr.value, false)?;
self.lits.push(b'"');
}
Ok(wrote_any)
}
fn attrs_object_plain(&mut self, attrs: &[Attr<'a>]) -> Result<bool> {
let (_has_any, has_text) = Self::attr_flags_plain(attrs);
if !has_text {
return Ok(false);
}
self.lits.extend_from_slice(b"\"#attributes\":{");
let wrote_any = self.attrs_object_body_plain(attrs)?;
self.lits.push(b'}');
Ok(wrote_any)
}
fn render_separate_attrs_plain(&mut self, element: &Element<'a>, suffix: u16) -> Result<bool> {
if element.attrs.is_empty() {
return Ok(false);
}
let (_has_any, has_text) = Self::attr_flags_plain(&element.attrs);
if !has_text {
return Ok(false);
}
self.lits.push(b'"');
self.lits
.extend_from_slice(element.name.as_str().as_bytes());
if suffix > 0 {
self.lits.push(b'_');
self.write_u64_plain(u64::from(suffix))?;
}
self.lits.extend_from_slice(b"_attributes\":{");
let wrote_any = self.attrs_object_body_plain(&element.attrs)?;
self.lits.push(b'}');
Ok(wrote_any)
}
fn try_leaf_value_plain(
&mut self,
element: &Element<'a>,
empty_as_string: bool,
) -> Result<bool> {
if element.has_element_child || element.children.len() != 1 {
return Ok(false);
}
let empty: &[u8] = if empty_as_string { b"\"\"" } else { b"null" };
match &element.children[0] {
Node::Text(text) => {
if text.is_empty() {
self.lits.extend_from_slice(empty);
} else {
self.lits.push(b'"');
self.json_text_plain(text)?;
self.lits.push(b'"');
}
Ok(true)
}
Node::Value(value) => {
if crate::model::ir::is_optional_empty(value) {
self.lits.extend_from_slice(empty);
return Ok(true);
}
if self.value_as_number_plain(value)? {
return Ok(true);
}
self.lits.push(b'"');
let mut sink = std::mem::take(&mut self.lits);
let res = self.vr.write_json_value_text(&mut sink, value);
self.lits = sink;
res?;
self.lits.push(b'"');
Ok(true)
}
_ => Ok(false),
}
}
fn data_element_value_plain(&mut self, element: &Element<'a>) -> Result<()> {
if self.try_leaf_value_plain(element, true)? {
return Ok(());
}
let (has_text, has_element_child) = Self::content_layout_plain(element);
if !has_text && !has_element_child {
self.lits.extend_from_slice(b"\"\"");
return Ok(());
}
if has_element_child {
self.element_body_plain(element, false, true, has_text)
} else {
self.content_as_json_value_plain(&element.children, false)
}
}
fn write_element_value_no_attrs_plain(
&mut self,
element: &Element<'a>,
child_is_container: bool,
) -> Result<()> {
if self.try_leaf_value_plain(element, false)? {
return Ok(());
}
let (has_text, has_element_child) = Self::content_layout_plain(element);
if !has_element_child && !has_text {
self.lits.extend_from_slice(b"null");
Ok(())
} else if !has_element_child {
self.content_as_json_value_plain(&element.children, false)
} else {
self.element_body_plain(element, child_is_container, true, has_text)
}
}
fn write_element_value_plain(
&mut self,
element: &Element<'a>,
child_is_container: bool,
) -> Result<()> {
if element.attrs.is_empty() && self.try_leaf_value_plain(element, false)? {
return Ok(());
}
let (has_text, has_element_child) = Self::content_layout_plain(element);
let (_has_attrs_any, has_attrs_text) = Self::attr_flags_plain(&element.attrs);
if !has_element_child && !has_text && !has_attrs_text {
self.lits.extend_from_slice(b"null");
Ok(())
} else if !has_element_child && !has_attrs_text {
self.content_as_json_value_plain(&element.children, false)
} else {
self.element_body_plain(element, child_is_container, false, has_text)
}
}
fn element_body_plain(
&mut self,
element: &Element<'a>,
in_data_container: bool,
omit_attributes: bool,
has_text: bool,
) -> Result<()> {
let mut should_flatten_named_data = false;
if in_data_container {
for node in &element.children {
let Node::Element(id) = node else { continue };
let child = self.element_ref(*id);
if !is_data_element_name(child.name.as_str()) {
continue;
}
let Some(name_nodes) = child
.attrs
.iter()
.find(|a| a.name.as_str() == "Name")
.map(|a| &a.value)
else {
continue;
};
if Self::has_text_content_plain(name_nodes) {
should_flatten_named_data = true;
break;
}
}
}
let mut name_counts: Vec<(&'t str, u16)> = Vec::new();
self.lits.push(b'{');
let mut wrote_any = false;
if !omit_attributes
&& !element.attrs.is_empty()
&& self.attrs_object_plain(&element.attrs)?
{
wrote_any = true;
}
if has_text {
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
self.lits.extend_from_slice(b"\"#text\":");
self.content_as_json_value_plain(&element.children, true)?;
}
let positional_data_count = if in_data_container && !should_flatten_named_data {
element
.children
.iter()
.filter(|n| {
matches!(n, Node::Element(id)
if is_data_element_name(self.element_ref(*id).name.as_str()))
})
.count()
} else {
0
};
let mut positional_data_emitted = false;
for node in &element.children {
let Node::Element(id) = node else { continue };
let child = self.element_ref(*id);
if in_data_container && is_data_element_name(child.name.as_str()) {
if should_flatten_named_data {
let Some(name_nodes) = child
.attrs
.iter()
.find(|a| a.name.as_str() == "Name")
.map(|a| &a.value)
else {
continue;
};
if !Self::has_text_content_plain(name_nodes) {
continue;
}
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
self.lits.push(b'"');
self.text_content_plain(name_nodes, false)?;
self.lits.extend_from_slice(b"\":");
self.data_element_value_plain(child)?;
} else if !positional_data_emitted && positional_data_count > 0 {
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
positional_data_emitted = true;
self.lits.extend_from_slice(b"\"Data\":{\"#text\":");
if positional_data_count == 1 {
self.data_element_value_plain(child)?;
} else {
self.lits.push(b'[');
let mut first = true;
for node2 in &element.children {
let Node::Element(id2) = node2 else { continue };
let candidate = self.element_ref(*id2);
if !is_data_element_name(candidate.name.as_str()) {
continue;
}
if !first {
self.lits.push(b',');
}
first = false;
self.data_element_value_plain(candidate)?;
}
self.lits.push(b']');
}
self.lits.push(b'}');
}
continue;
}
let cname = child.name.as_str();
let suffix = next_name_suffix(cname, &mut name_counts);
if wrote_any {
self.lits.push(b',');
}
wrote_any = true;
let child_is_container = is_data_container_name(cname);
if self.separate {
let wrote_attrs = self.render_separate_attrs_plain(child, suffix)?;
let (child_text, child_elem) = Self::content_layout_plain(child);
let child_has_value = child_elem || child_text;
let write_value = child_has_value || !wrote_attrs;
if wrote_attrs && write_value {
self.lits.push(b',');
}
if write_value {
self.key_with_suffix_plain(cname, suffix);
self.write_element_value_no_attrs_plain(child, child_is_container)?;
}
} else {
self.key_with_suffix_plain(cname, suffix);
self.write_element_value_plain(child, child_is_container)?;
}
}
self.lits.push(b'}');
Ok(())
}
}