///|
fn metadata_key(name : String) -> Bool {
match name {
"title"
| "subject"
| "author"
| "manager"
| "company"
| "operator"
| "category"
| "keywords"
| "comment"
| "doccomm"
| "hlinkbase"
| "generator" => true
_ => false
}
}
///|
fn ignored_destination(name : String) -> Bool {
match name {
"stylesheet"
| "filetbl"
| "revtbl"
| "rsidtbl"
| "listtable"
| "listoverridetable"
| "latentstyles"
| "xmlnstbl"
| "datastore"
| "themedata"
| "colorschememapping"
| "fontemb"
| "fontfile"
| "datafield"
| "objdata"
| "objclass"
| "objname"
| "shp"
| "shpinst"
| "nonshppict"
| "pntext"
| "listtext"
| "pn"
| "bkmkstart"
| "bkmkend"
| "xe"
| "tc"
| "private"
| "mmath"
| "mmathPr"
| "password"
| "formfield" => true
_ => false
}
}
///|
fn Parser::destination(self : Parser, name : String, span : Span) -> Bool {
if self.state.destination == "skip" {
return true
}
if metadata_key(name) || time_destination(name) {
self.flush_surrogate()
self.flush_run()
self.state.destination = name
self.state.capture = StringBuilder()
self.state.owned = true
return true
}
if ignored_destination(name) {
self.flush_surrogate()
self.flush_run()
self.state.destination = "skip"
if name == "listtable" || name == "listoverridetable" {
self.warn_once(
"list_numbering", "Automatic list numbering is not reconstructed", span,
)
}
if name == "mmath" || name == "shp" {
self.warn_once(
"unsupported_drawing", "Drawing or equation content was omitted", span,
)
}
return true
}
match name {
"fonttbl" | "colortbl" | "info" => {
self.flush_surrogate()
self.flush_run()
self.state.destination = name
self.state.capture = StringBuilder()
self.state.owned = true
true
}
"pict" | "object" => {
self.flush_surrogate()
self.flush_run()
self.state.destination = name
self.state.owned = true
self.warn_once(
"embedded_content", "Embedded pictures and objects are recorded but not rendered",
span,
)
true
}
"header"
| "headerl"
| "headerr"
| "headerf"
| "footer"
| "footerl"
| "footerr"
| "footerf"
| "footnote"
| "annotation" => {
self.flush_surrogate()
self.finish_paragraph(span.start, false)
self.state.region = match name {
"footnote" => Footnote
"annotation" => Annotation
"header" | "headerl" | "headerr" | "headerf" => Header
_ => Footer
}
self.state.destination = "body"
self.state.owned = true
true
}
"field" => {
self.flush_surrogate()
self.flush_run()
self.state.field = Some({ instruction: "", link: None, })
self.state.destination = "field"
true
}
"fldinst" => {
self.state.destination = "fldinst"
self.state.capture = StringBuilder()
self.state.owned = true
true
}
"fldrslt" => {
self.state.destination = "fldrslt"
true
}
"upr" => {
self.state.destination = "upr"
true
}
"ud" => {
self.state.destination = "body"
true
}
_ => false
}
}
///|
fn Parser::finish_font(self : Parser, span : Span) -> Unit {
let name = self.state.capture.to_string().trim().to_owned()
self.state.capture.reset()
if self.state.font_id < 0 || name.is_empty() {
return
}
if !self.take_node(span) {
return
}
let font : Font = {
id: self.state.font_id,
name,
charset: self.state.charset,
codepage: self.state.font_codepage,
family: self.state.family,
}
if self.font_indices.get(font.id) is Some(i) {
self.report(
"duplicate_font",
Warning,
"Duplicate font identifier; last declaration wins",
span,
)
self.fonts[i] = font
return
}
self.font_indices[font.id] = self.fonts.length()
self.fonts.push(font)
}
///|
fn Parser::finish_color(self : Parser, span : Span) -> Unit {
if self.take_node(span) {
self.colors.push({
red: self.state.red,
green: self.state.green,
blue: self.state.blue,
automatic: !self.state.color_set,
})
}
self.state.red = 0
self.state.green = 0
self.state.blue = 0
self.state.color_set = false
}
///|
fn Parser::close_destination(self : Parser, span : Span) -> Unit {
if !self.state.owned {
return
}
let name = self.state.destination
if time_destination(name) {
if self.state.time.valid() && self.take_node(span) {
self.metadata.push({
key: name,
value: self.state.time.to_iso(),
span: { start: self.state.start, end: span.end, },
})
} else {
self.report(
"invalid_timestamp",
Warning,
"Incomplete or invalid document timestamp was omitted",
span,
)
}
} else if metadata_key(name) {
if self.take_node(span) {
self.metadata.push({
key: name,
value: self.state.capture.to_string(),
span: { start: self.state.start, end: span.end, },
})
}
} else if name == "fldinst" {
if self.state.field is Some(field) {
field.instruction = self.state.capture.to_string().trim().to_owned()
field.link = hyperlink_target(field.instruction)
}
} else if name == "pict" || name == "object" {
if self.take_node(span) {
self.attachments.push({
kind: name,
span: { start: self.state.start, end: span.end, },
declared_width: self.state.width,
declared_height: self.state.height,
})
}
} else if name == "body" && self.state.region != Body {
self.finish_paragraph(span.end, false)
}
}
///|
fn hyperlink_target(instruction : String) -> String? {
let chars = instruction.to_array()
let mut pos = 0
while pos < chars.length() && chars[pos] != ' ' && chars[pos] != '\t' {
pos += 1
}
let head = String::from_array(chars[:pos]).to_upper()
if head != "HYPERLINK" {
return None
}
while pos < chars.length() && (chars[pos] == ' ' || chars[pos] == '\t') {
pos += 1
}
if pos >= chars.length() {
return None
}
let out = StringBuilder()
let quoted = chars[pos] == '"'
if quoted {
pos += 1
}
while pos < chars.length() {
let c = chars[pos]
if (quoted && c == '"') || (!quoted && (c == ' ' || c == '\t')) {
break
}
out.write_char(c)
pos += 1
}
let value = out.to_string()
if value.is_empty() || value.has_prefix("\\") {
None
} else {
Some(value)
}
}