// Bounded XML reader for ODF data import with internal DTD/entity processing.
// No external entity I/O or recovery. Comments and processing instructions are ignored.
///|
priv enum ReadPart {
ReadText(String)
ReadElement(ReadElement)
}
///|
priv struct ReadElement {
name : String
attrs : Map[String, String]
parts : Array[ReadPart]
ns : Map[String, String]
}
///|
priv struct ReadXml {
chars : Array[Char]
mut at : Int32
mut nodes : Int32
normalize_lines : Bool
document_part : String
mut location : String
events : Array[XmlProcessorEvent]
mut standalone : Bool
mut dtd : DtdInfo?
expansion : ReadEntityExpansion
declared_types : Map[(Int32, String), String]
other_ids : Array[(Int32, String, String)]
}
///|
priv struct ReadEntityExpansion {
mut units : Int32
active : Array[String]
}
///|
fn ReadXml::peek(self : ReadXml) -> Char {
if self.at < self.chars.length() {
self.chars[self.at]
} else {
'\u0000'
}
}
///|
fn ReadXml::take(self : ReadXml) -> Char raise OdsError {
if self.at >= self.chars.length() {
raise Invalid("truncated XML")
}
let c = self.chars[self.at]
self.at += 1
c
}
///|
fn ReadXml::expect(self : ReadXml, c : Char) -> Unit raise OdsError {
if self.take() != c {
raise Invalid("malformed XML delimiter")
}
}
///|
fn ReadXml::space(self : ReadXml) -> Unit {
while self.peek() == ' ' ||
self.peek() == '\n' ||
self.peek() == '\r' ||
self.peek() == '\t' {
self.at += 1
}
}
///|
fn xml_name_start(c : Char) -> Bool {
let n = c.to_int()
c == ':' ||
c == '_' ||
(n >= 0x41 && n <= 0x5A) ||
(n >= 0x61 && n <= 0x7A) ||
(n >= 0xC0 && n <= 0xD6) ||
(n >= 0xD8 && n <= 0xF6) ||
(n >= 0xF8 && n <= 0x2FF) ||
(n >= 0x370 && n <= 0x37D) ||
(n >= 0x37F && n <= 0x1FFF) ||
(n >= 0x200C && n <= 0x200D) ||
(n >= 0x2070 && n <= 0x218F) ||
(n >= 0x2C00 && n <= 0x2FEF) ||
(n >= 0x3001 && n <= 0xD7FF) ||
(n >= 0xF900 && n <= 0xFDCF) ||
(n >= 0xFDF0 && n <= 0xFFFD) ||
(n >= 0x10000 && n <= 0xEFFFF)
}
///|
fn xml_name_char(c : Char) -> Bool {
let n = c.to_int()
xml_name_start(c) ||
c == '-' ||
c == '.' ||
(c >= '0' && c <= '9') ||
n == 0xB7 ||
(n >= 0x300 && n <= 0x36F) ||
(n >= 0x203F && n <= 0x2040)
}
///|
fn ReadXml::name(self : ReadXml) -> String raise OdsError {
let b = StringBuilder()
let first = self.peek()
if !xml_name_start(first) {
raise Invalid("unsupported XML name")
}
while xml_name_char(self.peek()) {
b.write_char(self.take())
if b.to_string().length() > 256 {
raise Invalid("XML name limit")
}
}
b.to_string()
}
///|
fn ReadXml::text(
self : ReadXml,
stop : Char,
attribute : Bool,
) -> String raise OdsError {
let b = StringBuilder()
let mut units = 0
while self.at < self.chars.length() && self.peek() != stop {
if !attribute && self.general_entity_ahead() {
break
}
let c = self.take()
if !attribute &&
c == ']' &&
self.at + 1 < self.chars.length() &&
self.chars[self.at] == ']' &&
self.chars[self.at + 1] == '>' {
raise Invalid("raw ]]> is forbidden in XML text")
}
if attribute && c == '<' {
raise Invalid("raw less-than in XML attribute")
}
if c == '&' {
let e = StringBuilder()
while self.peek() != ';' {
e.write_char(self.take())
if e.to_string().length() > 16 {
raise Invalid("entity length limit")
}
}
self.expect(';')
let entity = e.to_string()
let decoded = match entity {
"amp" => '&'
"lt" => '<'
"gt" => '>'
"quot" => '"'
"apos" => '\''
_ => {
let mut n = 0
let mut digits = 0
let mut base = 10
let mut index = 0
for d in entity {
if index == 0 {
if d != '#' {
raise Invalid("custom XML entities forbidden")
}
} else if index == 1 && d == 'x' {
base = 16
} else {
let v = if d >= '0' && d <= '9' {
d.to_int() - 48
} else if d >= 'a' && d <= 'f' {
d.to_int() - 87
} else if d >= 'A' && d <= 'F' {
d.to_int() - 55
} else {
-1
}
if v < 0 || v >= base || n > (0x10FFFF - v) / base {
raise Invalid("invalid numeric XML entity")
}
n = n * base + v
digits += 1
}
index += 1
}
if digits == 0 || n > 0x10FFFF || (n >= 0xD800 && n <= 0xDFFF) {
raise Invalid("invalid XML code point")
}
n.unsafe_to_char()
}
}
b.write_char(decoded)
} else if c == '\r' && self.normalize_lines {
if self.peek() == '\n' {
self.at += 1
}
b.write_char(if attribute { ' ' } else { '\n' })
} else {
b.write_char(if attribute && (c == '\n' || c == '\t') { ' ' } else { c })
}
units += 1
if units > 32767 {
raise Invalid("XML text/attribute exceeds limit")
}
}
let s = b.to_string()
validate_text(s)
s
}
///|
fn ReadXml::starts_with(self : ReadXml, expected : String) -> Bool {
let mut offset = 0
for c in expected {
if self.at + offset >= self.chars.length() ||
self.chars[self.at + offset] != c {
return false
}
offset += 1
}
true
}
///|
fn ReadXml::cdata(self : ReadXml) -> String raise OdsError {
for c in "") {
let c = self.take()
if c == '\r' && self.normalize_lines {
if self.peek() == '\n' {
self.at += 1
}
b.write_char('\n')
} else {
b.write_char(c)
}
units += 1
if units > 32767 {
raise Invalid("CDATA text exceeds limit")
}
}
self.at += 3
let value = b.to_string()
validate_text(value)
value
}
///|
fn ReadXml::comment(self : ReadXml) -> Unit raise OdsError {
for c in "") {
if self.starts_with("--") {
raise Invalid("double hyphen in XML comment")
}
b.write_char(self.take())
units += 1
if units > 32767 {
raise Invalid("XML comment limit")
}
}
self.at += 3
validate_text(b.to_string())
}
///|
fn ReadXml::instruction(self : ReadXml) -> Unit raise OdsError {
self.expect('<')
self.expect('?')
let target = self.name()
if !xml_id_ncname(target) {
raise Invalid("PI target must be an NCName")
}
match target {
"xml" | "xmL" | "xMl" | "xML" | "Xml" | "XmL" | "XMl" | "XML" =>
raise Invalid("reserved XML PI target")
_ => ()
}
if !self.starts_with("?>") &&
self.peek() != ' ' &&
self.peek() != '\t' &&
self.peek() != '\r' &&
self.peek() != '\n' {
raise Invalid("XML PI target separator required")
}
self.space()
let b = StringBuilder()
let mut units = 0
while !self.starts_with("?>") {
let c = self.take()
if c == '\r' && self.normalize_lines {
if self.peek() == '\n' {
self.at += 1
}
b.write_char('\n')
} else {
b.write_char(c)
}
units += 1
if units > 32767 {
raise Invalid("XML PI limit")
}
}
self.at += 2
validate_text(b.to_string())
self.event("PROCESSING_INSTRUCTION", target, b.to_string())
}
///|
fn ReadXml::misc(self : ReadXml) -> Unit raise OdsError {
while true {
self.space()
if self.starts_with("