///|
priv struct FieldContext {
mut instruction : String
mut link : String?
}
///|
priv struct State {
mut style : Style
mut paragraph : ParagraphStyle
mut destination : String
mut region : Region
mut uc : Int
mut codepage : Int
mut starred : Bool
mut owned : Bool
mut capture : StringBuilder
mut font_id : Int
mut charset : Int
mut font_codepage : Int?
mut family : String
mut red : Int
mut green : Int
mut blue : Int
mut color_set : Bool
mut field : FieldContext?
mut in_table : Bool
mut width : Int?
mut height : Int?
start : Int
mut time : DocumentTime
}
///|
fn State::new(start : Int) -> State {
{
style: Style::default(),
paragraph: ParagraphStyle::default(),
destination: "body",
region: Body,
uc: 1,
codepage: 1252,
starred: false,
owned: false,
capture: StringBuilder(),
font_id: -1,
charset: 0,
font_codepage: None,
family: "nil",
red: 0,
green: 0,
blue: 0,
color_set: false,
field: None,
in_table: false,
width: None,
height: None,
start,
time: { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, },
}
}
///|
fn State::child(self : State, start : Int) -> State {
{
style: self.style,
paragraph: self.paragraph,
destination: self.destination,
region: self.region,
uc: self.uc,
codepage: self.codepage,
starred: false,
owned: false,
capture: self.capture,
font_id: self.font_id,
charset: self.charset,
font_codepage: self.font_codepage,
family: self.family,
red: self.red,
green: self.green,
blue: self.blue,
color_set: self.color_set,
field: self.field,
in_table: self.in_table,
width: self.width,
height: self.height,
start,
time: self.time,
}
}
///|
priv struct Parser {
input : Bytes
lexer : Lexer
limits : Limits
recover : Bool
stack : Array[State]
mut state : State
paragraphs : Array[Paragraph]
fonts : Array[Font]
font_indices : Map[Int, Int]
colors : Array[Color]
metadata : Array[Metadata]
attachments : Array[Attachment]
diagnostics : Array[Diagnostic]
runs : Array[Run]
buffer : StringBuilder
mut buffer_start : Int
mut buffer_end : Int
mut paragraph_start : Int
mut fallback : Int
mut pending_high : Int?
mut utf8_remaining : Int
mut utf8_value : Int
mut utf8_minimum : Int
mut utf8_span : Span
mut high_span : Span
mut units : Int
mut nodes : Int
mut row : Int
mut cell : Int
mut default_font : Int
mut complete : Bool
mut stopped : Bool
mut closed_root : Bool
mut seen_version : Bool
warned : Map[String, Bool]
}
///|
fn Parser::new(input : Bytes, limits : Limits, recover : Bool) -> Parser {
{
input,
lexer: Lexer::new(input),
limits,
recover,
stack: [],
state: State::new(0),
paragraphs: [],
fonts: [],
font_indices: Map([]),
colors: [],
metadata: [],
attachments: [],
diagnostics: [],
runs: [],
buffer: StringBuilder(),
buffer_start: 0,
buffer_end: 0,
paragraph_start: 0,
fallback: 0,
pending_high: None,
utf8_remaining: 0,
utf8_value: 0,
utf8_minimum: 0,
utf8_span: { start: 0, end: 0, },
high_span: { start: 0, end: 0, },
units: 0,
nodes: 0,
row: 0,
cell: 0,
default_font: 0,
complete: true,
stopped: false,
closed_root: false,
seen_version: false,
warned: {},
}
}
///|
fn Parser::report(
self : Parser,
code : String,
severity : Severity,
message : String,
span : Span,
) -> Unit {
if self.diagnostics.length() >= self.limits.diagnostics {
self.complete = false
self.stopped = true
return
}
self.diagnostics.push({ code, severity, message, span, })
if severity == Error {
self.complete = false
if !self.recover {
self.stopped = true
}
}
}
///|
fn Parser::warn_once(
self : Parser,
code : String,
message : String,
span : Span,
) -> Unit {
if !self.warned.contains(code) {
self.warned[code] = true
self.report(code, Warning, message, span)
}
}
///|
fn Parser::take_node(self : Parser, span : Span) -> Bool {
if self.nodes >= self.limits.nodes {
self.report("node_limit", Error, "Document node limit exceeded", span)
self.stopped = true
return false
}
self.nodes += 1
true
}
///|
fn Parser::flush_run(self : Parser) -> Unit {
if self.buffer.is_empty() {
return
}
let text = self.buffer.to_string()
self.buffer.reset()
let span : Span = { start: self.buffer_start, end: self.buffer_end, }
if !self.take_node(span) {
return
}
let link = match self.state.field {
Some(field) => field.link
None => None
}
self.runs.push({ text, style: self.state.style, span, link, })
}
///|
fn Parser::finish_paragraph(self : Parser, end : Int, force : Bool) -> Unit {
self.flush_run()
if self.runs.is_empty() && !force {
self.paragraph_start = end
return
}
let span : Span = { start: self.paragraph_start, end, }
if !self.take_node(span) {
self.runs.clear()
return
}
self.paragraphs.push({
runs: self.runs.copy(),
style: self.state.paragraph,
region: self.state.region,
table_row: if self.state.in_table {
Some(self.row)
} else {
None
},
table_cell: if self.state.in_table {
Some(self.cell)
} else {
None
},
span,
})
self.runs.clear()
self.paragraph_start = end
}
///|
fn Parser::append_scalar(self : Parser, scalar : Int, span : Span) -> Unit {
if self.stopped {
return
}
let cost = if scalar > 0xFFFF { 2 } else { 1 }
if cost > self.limits.output_units - self.units {
self.report("output_limit", Error, "Decoded text limit exceeded", span)
self.stopped = true
return
}
self.units += cost
let c = scalar.unsafe_to_char()
match self.state.destination {
"skip" | "info" | "field" | "upr" | "pict" | "object" => ()
"fonttbl" =>
if scalar == 59 {
self.finish_font(span)
} else {
self.state.capture.write_char(c)
}
"colortbl" => if scalar == 59 { self.finish_color(span) }
"body" | "fldrslt" => {
if self.buffer.is_empty() {
self.buffer_start = span.start
}
self.buffer_end = span.end
self.buffer.write_char(c)
}
_ => self.state.capture.write_char(c)
}
}
///|
fn Parser::flush_surrogate(self : Parser) -> Unit {
if self.pending_high is Some(_) {
self.pending_high = None
self.report(
"unpaired_surrogate",
Error,
"High surrogate has no matching low surrogate",
self.high_span,
)
self.append_scalar(0xFFFD, self.high_span)
}
}
///|
fn Parser::emit_unicode(self : Parser, value : Int, span : Span) -> Unit {
match unicode_unit(value) {
None => {
self.report(
"unicode_range",
Error,
"Unicode control value is out of range",
span,
)
self.append_scalar(0xFFFD, span)
}
Some(unit) =>
if high_surrogate(unit) {
self.flush_surrogate()
self.pending_high = Some(unit)
self.high_span = span
} else if low_surrogate(unit) {
match self.pending_high {
Some(high) => {
self.pending_high = None
self.append_scalar(combine_surrogates(high, unit), {
start: self.high_span.start,
end: span.end,
})
}
None => {
self.report(
"unpaired_surrogate",
Error,
"Low surrogate has no preceding high surrogate",
span,
)
self.append_scalar(0xFFFD, span)
}
}
} else {
self.flush_surrogate()
self.append_scalar(unit, span)
}
}
self.fallback = self.state.uc
}
///|
fn Parser::effective_codepage(self : Parser) -> Int {
if self.font_indices.get(self.state.style.font) is Some(index) {
let font = self.fonts[index]
if font.codepage is Some(cp) {
return cp
}
if font.charset != 0 && font.charset != 1 {
return charset_codepage(font.charset).unwrap_or(-1)
}
}
self.state.codepage
}
///|
fn Parser::emit_byte(self : Parser, b : Int, span : Span) -> Unit {
if b == 10 || b == 13 {
return
}
if self.fallback > 0 {
self.fallback -= 1
return
}
if self.state.destination == "skip" ||
self.state.destination == "pict" ||
self.state.destination == "object" {
return
}
self.flush_surrogate()
let cp = self.effective_codepage()
if cp == 65001 {
self.emit_utf8(b, span)
return
}
if b < 128 {
if b == 0 {
self.report("nul_text", Warning, "NUL in text was omitted", span)
} else {
self.append_scalar(b, span)
}
} else if cp == 1252 {
match decode_1252(b) {
Some(scalar) => self.append_scalar(scalar, span)
None => {
self.report(
"undefined_codepoint",
Error,
"Undefined Windows-1252 byte",
span,
)
self.append_scalar(0xFFFD, span)
}
}
} else {
self.warn_once(
"unsupported_codepage_\{cp}",
"Code page \{cp} is not supported; affected bytes become replacement characters",
span,
)
self.complete = false
self.append_scalar(0xFFFD, span)
}
}