use super::*;
use rt::xml::{Encoding, Event, EventKind, Scanner, Step, UTF8};
mod generate;
#[derive(Clone, Copy, Debug, Default)]
pub(super) struct Registers {
text: (usize, usize),
ntext: (usize, usize),
namespace: (usize, usize),
nnamespace: (usize, usize),
prefix: (usize, usize),
nprefix: (usize, usize),
}
fn special(name: &str) -> Ref {
Ref { name: name.into(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos: Pos::default() }
}
fn national(text: &str) -> Vec<u8> {
text.encode_utf16().flat_map(u16::to_be_bytes).collect()
}
impl<'p> Machine<'p, '_, '_> {
pub(super) fn xml_register(&mut self, r: &Ref) -> R<Option<Loc>> {
if !compile::markup::xml_register(self.layout, r) {
return Ok(None);
}
let x = self.xml;
let ((offset, len), kind) = match r.name.as_str() {
"XML-TEXT" => (x.text, Kind::Alnum { justified: false }),
"XML-NTEXT" => (x.ntext, Kind::National),
"XML-NAMESPACE" => (x.namespace, Kind::Alnum { justified: false }),
"XML-NNAMESPACE" => (x.nnamespace, Kind::National),
"XML-NAMESPACE-PREFIX" => (x.prefix, Kind::Alnum { justified: false }),
_ => (x.nprefix, Kind::National),
};
let mut loc = Loc { offset, len, kind, item: usize::MAX };
if let Some(rm) = &r.refmod {
let unit = if kind == Kind::National { 2 } else { 1 };
let start = self.integer(&rm.start, r.pos)?;
let length = match &rm.length {
Some(l) => self.integer(l, r.pos)?,
None => (len / unit) as i64 - start + 1,
};
if start < 1 || length < 0 || (start - 1 + length) as usize * unit > len {
return Err(Abend::ironwork(format!("reference modification ({start}:{length}) of {} is outside its {len} bytes", r.name), r.pos));
}
loc = Loc { offset: offset + (start as usize - 1) * unit, len: length as usize * unit, kind, item: usize::MAX };
}
Ok(Some(loc))
}
fn xml_code(&mut self, pos: Pos) -> R<i64> {
let r = special("XML-CODE");
self.integer(&Expr::Operand(Operand::Ref(r)), pos)
}
fn fragment(&mut self, bytes: &[u8]) -> (usize, usize) {
if bytes.is_empty() {
return (0, 0);
}
(self.unit.push_temporary(bytes), bytes.len())
}
fn xml_event(&mut self, e: &Event, code: i64, encoding: Encoding, national_out: bool, range: (usize, usize), pos: Pos) -> R<Flow> {
let name = self.page.encode_lossy(&format!("{:<30}", e.kind.name()));
let event = special("XML-EVENT");
let loc = self.locate(&event)?;
self.unit.write(loc.offset, &name[..30]);
self.set_integer(&special("XML-CODE"), code, pos)?;
self.set_integer(&special("XML-INFORMATION"), e.information.into(), pos)?;
let national_character = matches!(e.kind, EventKind::ContentNationalCharacter | EventKind::AttributeNationalCharacter);
let mut registers = Registers::default();
if national_out || national_character {
registers.ntext = self.fragment(&national(&e.text));
registers.nnamespace = self.fragment(&national(&e.namespace));
registers.nprefix = self.fragment(&national(&e.prefix));
} else {
registers.text = self.fragment(&encoding.encode(&e.text));
registers.namespace = self.fragment(&encoding.encode(&e.namespace));
registers.prefix = self.fragment(&encoding.encode(&e.prefix));
}
self.xml = registers;
self.perform_range(range.0, range.1, None, None)
}
fn xml_document(&mut self, x: &XmlParse, encoding: Encoding, carry: &mut Vec<u8>) -> R<String> {
let loc = self.locate(&x.document)?;
Ok(encoding.decode_segment(carry, store::bytes(&self.unit.mem, loc)))
}
pub(super) fn xml_parse(&mut self, x: &'p XmlParse) -> R<Flow> {
self.unit.unfollowed("XML PARSE");
let (start, first_end) = self.procedure(&x.procedure, x.pos)?;
let end = match &x.thru {
Some(t) => self.procedure(t, x.pos)?.1,
None => first_end,
};
let document = self.locate(&x.document)?;
let ccsid = match &x.encoding {
Some(op) => match self.operand(op, x.pos)? {
Val::Num(n) => n.to_i128().and_then(|c| u16::try_from(c).ok()),
_ => None,
},
None => None,
};
let encoding = match (document.kind, ccsid) {
(Kind::National, _) => Encoding::National,
(_, Some(UTF8)) => Encoding::Utf8,
(_, Some(c)) => Encoding::Page(CodePage::by_ccsid(c).ok_or_else(|| Abend::ironwork(format!("XML PARSE: CCSID {c} is not a code page ironwork for COBOL carries"), x.pos))?),
(_, None) => Encoding::Page(self.page),
};
let national_out = matches!(encoding, Encoding::National) || x.returning_national;
let representable: Box<dyn Fn(char) -> bool> = match encoding {
Encoding::Page(page) if !national_out => Box::new(move |c| page.encode_char(c).is_some()),
_ => Box::new(|_| true),
};
let mut carry = Vec::new();
let text = self.xml_document(x, encoding, &mut carry)?;
let mut seen = text.clone();
let mut segment_start = 0;
let mut scanner = Scanner::new(&text, &*representable);
let mark = self.unit.mem.len();
let code = loop {
let (event, exception, warning) = match scanner.advance() {
Step::Event(e) if e.kind == EventKind::Exception => {
let code = i64::from(e.code);
(e, Some(code), true)
}
Step::Event(e) => (e, None, false),
Step::EndOfInput => (Event { kind: EventKind::EndOfInput, text: String::new(), namespace: String::new(), prefix: String::new(), information: 0, code: 0 }, None, false),
Step::Error(m) => {
let upto: String = seen.chars().take(m.offset).skip(segment_start).collect();
let code = m.why.code();
(Event { kind: EventKind::Exception, text: upto, namespace: String::new(), prefix: String::new(), information: 0, code }, Some(i64::from(code)), false)
}
Step::Done => break 0,
};
let flow = self.xml_event(&event, exception.unwrap_or(0), encoding, national_out, (start, end), x.pos);
self.xml = Registers::default();
self.unit.release_temporaries(mark);
match flow? {
Flow::Next => {}
other => return Ok(other),
}
let code = self.xml_code(x.pos)?;
match event.kind {
EventKind::Exception if warning && code == 0 => {}
EventKind::Exception => break exception.unwrap_or(code),
_ if code == -1 => break -1,
EventKind::EndOfInput if code == 1 => {
let segment = self.xml_document(x, encoding, &mut carry)?;
segment_start = seen.chars().count();
seen.push_str(&segment);
scanner.feed(&segment);
}
EventKind::EndOfInput if code == 0 => {
if !carry.is_empty() {
let rest = encoding.decode(&std::mem::take(&mut carry));
seen.push_str(&rest);
scanner.feed(&rest);
}
scanner.finish();
}
EventKind::EndOfDocument if code == 0 => break 0,
_ if code == 0 => {}
kind => {
let message = format!("IGZ0230S XML PARSE: the processing procedure set XML-CODE to {code} at event {}", kind.name());
return Err(Abend { code: AbendCode::user(4038), message, pos: x.pos, file: None });
}
}
};
self.set_integer(&special("XML-CODE"), code, x.pos)?;
let handler = if code == 0 { &x.not_on_exception } else { &x.on_exception };
match handler {
Some(body) => self.run_block(body),
None => Ok(Flow::Next),
}
}
}