use std::collections::HashMap;
use chrono::{DateTime, TimeZone, Utc};
use quick_xml::events::Event as XmlEvent;
use quick_xml::Reader;
use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct Event {
pub record_id: u64,
pub event_id: u32,
pub time_created: DateTime<Utc>,
pub provider: String,
pub channel: String,
pub xml: String,
pub data: HashMap<String, String>,
}
pub fn rendered_xml(e: &Event) -> String {
e.xml.clone()
}
pub fn parse_event_xml(xml: &str, default_channel: &str) -> Result<Event> {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut record_id: u64 = 0;
let mut event_id: u32 = 0;
let mut time_created: Option<DateTime<Utc>> = None;
let mut provider = String::new();
let mut channel = String::from(default_channel);
let mut data: HashMap<String, String> = HashMap::new();
enum State {
Idle,
InSystemElement(&'static str), InEventData,
InDataNamed(String),
InDataAnon(usize),
}
let mut state = State::Idle;
let mut anon_counter = 0usize;
let mut in_event_data = false;
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(XmlEvent::Start(e)) => {
let name = e.name();
let local = std::str::from_utf8(name.as_ref()).unwrap_or("");
match local {
"EventData" => {
in_event_data = true;
state = State::InEventData;
}
"Data" if in_event_data => {
let mut named: Option<String> = None;
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"Name" {
if let Ok(v) = attr.unescape_value() {
named = Some(v.into_owned());
}
}
}
state = match named {
Some(n) => State::InDataNamed(n),
None => {
let idx = anon_counter;
anon_counter += 1;
State::InDataAnon(idx)
}
};
}
"Provider" => {
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"Name" {
if let Ok(v) = attr.unescape_value() {
provider = v.into_owned();
}
}
}
}
"EventID" => state = State::InSystemElement("event_id"),
"EventRecordID" => state = State::InSystemElement("record_id"),
"Channel" => state = State::InSystemElement("channel"),
"TimeCreated" => {
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"SystemTime" {
if let Ok(v) = attr.unescape_value() {
if let Ok(t) = DateTime::parse_from_rfc3339(&v) {
time_created = Some(t.with_timezone(&Utc));
} else if let Ok(t) = chrono::NaiveDateTime::parse_from_str(
v.trim_end_matches('Z'),
"%Y-%m-%dT%H:%M:%S%.f",
) {
time_created = Some(Utc.from_utc_datetime(&t));
}
}
}
}
}
_ => {}
}
}
Ok(XmlEvent::Empty(e)) => {
let name = e.name();
let local = std::str::from_utf8(name.as_ref()).unwrap_or("");
if local == "Data" && in_event_data {
let mut named: Option<String> = None;
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"Name" {
if let Ok(v) = attr.unescape_value() {
named = Some(v.into_owned());
}
}
}
match named {
Some(n) => {
data.insert(n, String::new());
}
None => {
data.insert(format!("Data_{}", anon_counter), String::new());
anon_counter += 1;
}
}
} else if local == "TimeCreated" {
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"SystemTime" {
if let Ok(v) = attr.unescape_value() {
if let Ok(t) = DateTime::parse_from_rfc3339(&v) {
time_created = Some(t.with_timezone(&Utc));
}
}
}
}
} else if local == "Provider" {
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"Name" {
if let Ok(v) = attr.unescape_value() {
provider = v.into_owned();
}
}
}
}
}
Ok(XmlEvent::Text(t)) => {
let txt = t.unescape().map(|c| c.into_owned()).unwrap_or_default();
match &state {
State::InSystemElement("event_id") => {
event_id = txt.trim().parse().unwrap_or(0);
}
State::InSystemElement("record_id") => {
record_id = txt.trim().parse().unwrap_or(0);
}
State::InSystemElement("channel") => {
channel = txt.trim().to_string();
}
State::InDataNamed(name) => {
data.entry(name.clone())
.and_modify(|s| s.push_str(&txt))
.or_insert_with(|| txt.clone());
}
State::InDataAnon(idx) => {
let key = format!("Data_{}", idx);
data.entry(key)
.and_modify(|s| s.push_str(&txt))
.or_insert_with(|| txt.clone());
}
_ => {}
}
}
Ok(XmlEvent::End(e)) => {
let name = e.name();
let local = std::str::from_utf8(name.as_ref()).unwrap_or("");
if local == "EventData" {
in_event_data = false;
state = State::Idle;
} else if matches!(local, "EventID" | "EventRecordID" | "Channel" | "Data") {
state = if in_event_data {
State::InEventData
} else {
State::Idle
};
}
}
Ok(XmlEvent::Eof) => break,
Err(e) => return Err(Error::Xml(e.to_string())),
_ => {}
}
buf.clear();
}
Ok(Event {
record_id,
event_id,
time_created: time_created.unwrap_or_else(Utc::now),
provider,
channel,
xml: xml.to_string(),
data,
})
}
pub fn filetime_to_utc(ft: u64) -> Result<DateTime<Utc>> {
const SEC_1601_TO_1970: i64 = 11_644_473_600;
let ticks = ft as i128;
let secs = (ticks / 10_000_000) as i64 - SEC_1601_TO_1970;
let nsecs = ((ticks % 10_000_000) * 100) as u32;
Utc.timestamp_opt(secs, nsecs)
.single()
.ok_or(Error::BadFileTime(ft))
}