use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::io::{BufReader, Cursor, Read, Seek, Write};
use std::path::Path;
use chrono::{Duration, NaiveDateTime};
use quick_xml::events::{BytesStart, Event};
use zip::read::ZipFile;
use zip::ZipArchive;
use crate::attrmap2::AttrMap2;
use crate::condition::{Condition, ValueCondition};
use crate::config::{Config, ConfigItem, ConfigItemType, ConfigValue};
use crate::ds::bufstack::BufStack;
use crate::ds::detach::Detach;
use crate::error::OdsError;
use crate::format::{FormatPart, FormatPartType, ValueFormatTrait};
use crate::io::parse::{
parse_bool, parse_currency, parse_datetime, parse_duration, parse_f64, parse_i16, parse_i32,
parse_i64, parse_u32, parse_visibility,
};
use crate::io::{DUMP_UNUSED, DUMP_XML};
use crate::refs::{parse_cellranges, parse_cellref};
use crate::style::stylemap::StyleMap;
use crate::style::tabstop::TabStop;
use crate::style::{
ColStyle, FontFaceDecl, GraphicStyle, HeaderFooter, MasterPage, PageStyle, ParagraphStyle,
RowStyle, StyleOrigin, StyleUse, TableStyle, TextStyle,
};
use crate::text::{TextP, TextTag};
use crate::validation::{MessageType, Validation, ValidationError, ValidationHelp};
use crate::xmltree::XmlTag;
use crate::{
CellData, CellStyle, ColRange, Length, RowRange, Sheet, SplitMode, Value, ValueFormatBoolean,
ValueFormatCurrency, ValueFormatDateTime, ValueFormatNumber, ValueFormatPercentage,
ValueFormatText, ValueFormatTimeDuration, ValueType, Visibility, WorkBook,
};
use quick_xml::events::attributes::Attribute;
use std::borrow::Cow;
use std::str::from_utf8;
pub fn read_ods_buf(buf: &[u8]) -> Result<WorkBook, OdsError> {
let zip = ZipArchive::new(Cursor::new(buf))?;
read_ods_impl(zip)
}
pub fn read_ods<P: AsRef<Path>>(path: P) -> Result<WorkBook, OdsError> {
let file = File::open(path.as_ref())?;
let zip = ZipArchive::new(file)?;
read_ods_impl(zip)
}
fn read_ods_impl<R: Read + Seek>(mut zip: ZipArchive<R>) -> Result<WorkBook, OdsError> {
let mut book = WorkBook::new_empty();
let mut bufstack = BufStack::new();
read_content(&mut bufstack, &mut book, &mut zip.by_name("content.xml")?)?;
read_styles(&mut bufstack, &mut book, &mut zip.by_name("styles.xml")?)?;
if let Ok(mut z) = zip.by_name("settings.xml") {
read_settings(&mut bufstack, &mut book, &mut z)?;
} else {
book.config = default_settings();
}
read_filebuf(&mut book, &mut zip)?;
calc_derived(&mut book)?;
Ok(book)
}
fn read_filebuf<R: Read + Seek>(
book: &mut WorkBook,
zip: &mut ZipArchive<R>,
) -> Result<(), OdsError> {
for idx in 0..zip.len() {
let mut ze = zip.by_index(idx)?;
if !matches!(ze.name(), "settings.xml" | "styles.xml" | "content.xml") {
if ze.is_dir() {
book.filebuf.push_dir(ze.name());
} else if ze.is_file() {
let mut buf = Vec::new();
ze.read_to_end(&mut buf)?;
book.filebuf.push_file(ze.name(), buf);
}
}
}
Ok(())
}
fn calc_derived(book: &mut WorkBook) -> Result<(), OdsError> {
let v = book
.config
.get_value(&["ooo:view-settings", "Views", "0", "ActiveTable"]);
if let Some(ConfigValue::String(n)) = v {
book.config_mut().active_table = n.clone();
}
let v = book
.config
.get_value(&["ooo:view-settings", "Views", "0", "HasSheetTabs"]);
if let Some(ConfigValue::Boolean(n)) = v {
book.config_mut().has_sheet_tabs = *n;
}
let v = book
.config
.get_value(&["ooo:view-settings", "Views", "0", "ShowGrid"]);
if let Some(ConfigValue::Boolean(n)) = v {
book.config_mut().show_grid = *n;
}
let v = book
.config
.get_value(&["ooo:view-settings", "Views", "0", "ShowPageBreaks"]);
if let Some(ConfigValue::Boolean(n)) = v {
book.config_mut().show_page_breaks = *n;
}
for i in 0..book.num_sheets() {
let mut sheet = book.detach_sheet(i);
for ch in sheet.col_header.values_mut() {
if let Some(style_name) = &ch.style {
if let Some(style) = book.colstyle(style_name) {
if style.use_optimal_col_width()? {
ch.set_width(Length::Default);
} else {
ch.set_width(style.col_width()?);
}
}
}
}
for rh in sheet.row_header.values_mut() {
if let Some(style_name) = &rh.style {
if let Some(style) = book.rowstyle(style_name) {
if style.use_optimal_row_height()? {
rh.set_height(Length::Default);
} else {
rh.set_height(style.row_height()?);
}
}
}
}
let v = book.config.get(&[
"ooo:view-settings",
"Views",
"0",
"Tables",
sheet.name().as_str(),
]);
if let Some(cc) = v {
if let Some(ConfigValue::Int(n)) = cc.get_value_rec(&["CursorPositionX"]) {
sheet.config_mut().cursor_x = *n as u32;
}
if let Some(ConfigValue::Int(n)) = cc.get_value_rec(&["CursorPositionY"]) {
sheet.config_mut().cursor_y = *n as u32;
}
if let Some(ConfigValue::Short(n)) = cc.get_value_rec(&["HorizontalSplitMode"]) {
sheet.config_mut().hor_split_mode = SplitMode::try_from(*n)?;
}
if let Some(ConfigValue::Short(n)) = cc.get_value_rec(&["VerticalSplitMode"]) {
sheet.config_mut().vert_split_mode = SplitMode::try_from(*n)?;
}
if let Some(ConfigValue::Int(n)) = cc.get_value_rec(&["HorizontalSplitPosition"]) {
sheet.config_mut().hor_split_pos = *n as u32;
}
if let Some(ConfigValue::Int(n)) = cc.get_value_rec(&["VerticalSplitPosition"]) {
sheet.config_mut().vert_split_pos = *n as u32;
}
if let Some(ConfigValue::Short(n)) = cc.get_value_rec(&["ActiveSplitRange"]) {
sheet.config_mut().active_split_range = *n;
}
if let Some(ConfigValue::Short(n)) = cc.get_value_rec(&["ZoomType"]) {
sheet.config_mut().zoom_type = *n;
}
if let Some(ConfigValue::Int(n)) = cc.get_value_rec(&["ZoomValue"]) {
sheet.config_mut().zoom_value = *n;
}
if let Some(ConfigValue::Boolean(n)) = cc.get_value_rec(&["ShowGrid"]) {
sheet.config_mut().show_grid = *n;
}
}
book.attach_sheet(sheet);
}
Ok(())
}
fn read_content(
bs: &mut BufStack,
book: &mut WorkBook,
zip_file: &mut ZipFile<'_>,
) -> Result<(), OdsError> {
let mut xml = quick_xml::Reader::from_reader(BufReader::new(zip_file));
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
let empty_tag = matches!(evt, Event::Empty(_));
if DUMP_XML {
println!(" read_content {:?}", evt);
}
match evt {
Event::Decl(_) => {}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:body"
|| xml_tag.name().as_ref() == b"office:spreadsheet" => {
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"office:body"
|| xml_tag.name().as_ref() == b"office:spreadsheet" => {
}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:document-content" => {
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"office:version" => {
book.set_version(attr.unescape_value()?.to_string());
}
_ => {
}
}
}
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"office:document-content" => {
}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:font-face-decls" =>
read_fonts(bs, book, StyleOrigin::Content, &mut xml)?,
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:styles" =>
read_styles_tag(bs, book, StyleOrigin::Content, &mut xml)?,
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:automatic-styles" =>
read_auto_styles(bs, book, StyleOrigin::Content, &mut xml)?,
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:master-styles" =>
read_master_styles(bs, book, StyleOrigin::Content, &mut xml)?,
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"table:content-validations" =>
read_validations(bs, book, &mut xml)?,
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"table:table" =>
book.push_sheet(read_table(bs, &mut xml, xml_tag)?),
Event::Empty(xml_tag) |
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"office:scripts" ||
xml_tag.name().as_ref() == b"table:tracked-changes" ||
xml_tag.name().as_ref() == b"text:variable-decls" ||
xml_tag.name().as_ref() == b"text:sequence-decls" ||
xml_tag.name().as_ref() == b"text:user-field-decls" ||
xml_tag.name().as_ref() == b"text:dde-connection-decls" ||
xml_tag.name().as_ref() == b"table:calculation-settings" ||
xml_tag.name().as_ref() == b"table:label-ranges" ||
xml_tag.name().as_ref() == b"table:named-expressions" ||
xml_tag.name().as_ref() == b"table:database-ranges" ||
xml_tag.name().as_ref() == b"table:data-pilot-tables" ||
xml_tag.name().as_ref() == b"table:consolidation" ||
xml_tag.name().as_ref() == b"table:dde-links" => {
let v = read_xml(bs, xml_tag.name().as_ref(), &mut xml, &xml_tag, empty_tag)?;
book.extra.push(v);
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"office:scripts" ||
xml_tag.name().as_ref() == b"table:tracked-changes" ||
xml_tag.name().as_ref() == b"text:variable-decls" ||
xml_tag.name().as_ref() == b"text:sequence-decls" ||
xml_tag.name().as_ref() == b"text:user-field-decls" ||
xml_tag.name().as_ref() == b"text:dde-connection-decls" ||
xml_tag.name().as_ref() == b"table:calculation-settings" ||
xml_tag.name().as_ref() == b"table:label-ranges" ||
xml_tag.name().as_ref() == b"table:named-expressions" ||
xml_tag.name().as_ref() == b"table:database-ranges" ||
xml_tag.name().as_ref() == b"table:data-pilot-tables" ||
xml_tag.name().as_ref() == b"table:consolidation" ||
xml_tag.name().as_ref() == b"table:dde-links" => {
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_content", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_table(
bs: &mut BufStack,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: BytesStart<'_>,
) -> Result<Sheet, OdsError> {
let mut sheet = Sheet::new("");
read_table_attr(&mut sheet, xml_tag)?;
let mut table_col: u32 = 0;
let mut row: u32 = 0;
let mut col: u32 = 0;
let mut row_repeat: u32 = 1;
let mut rowstyle: Option<String> = None;
let mut row_cellstyle: Option<String> = None;
let mut row_visible: Visibility = Default::default();
let mut col_range_from = 0;
let mut row_range_from = 0;
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
let empty_tag = matches!(evt, Event::Empty(_));
if DUMP_XML {
println!(" read_table {:?}", evt);
}
match evt {
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"table:table" => {
let mut it = sheet.row_header.iter_mut().rev();
if let Some((_row, last)) = it.next() {
if last.repeat > 1000 {
last.repeat = 1;
}
}
if let Some((_row, last)) = it.next() {
if last.repeat > 1000 {
last.repeat = 1;
}
}
break;
}
Event::Start(xml_tag) |
Event::Empty(xml_tag)
if xml_tag.name().as_ref() == b"table:title" ||
xml_tag.name().as_ref() == b"table:desc" ||
xml_tag.name().as_ref() == b"table:table-source" ||
xml_tag.name().as_ref() == b"office:dde-source" ||
xml_tag.name().as_ref() == b"table:scenario" ||
xml_tag.name().as_ref() == b"office:forms" ||
xml_tag.name().as_ref() == b"table:shapes" ||
xml_tag.name().as_ref() == b"table:named-expressions" ||
xml_tag.name().as_ref() == b"calcext:conditional-formats" => {
sheet.extra.push(read_xml(bs, xml_tag.name().as_ref(), xml, &xml_tag, empty_tag)?);
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"table:title" ||
xml_tag.name().as_ref() == b"table:desc" ||
xml_tag.name().as_ref() == b"table:table-source" ||
xml_tag.name().as_ref() == b"office:dde-source" ||
xml_tag.name().as_ref() == b"table:scenario" ||
xml_tag.name().as_ref() == b"office:forms" ||
xml_tag.name().as_ref() == b"table:shapes" ||
xml_tag.name().as_ref() == b"table:named-expressions" ||
xml_tag.name().as_ref() == b"calcext:conditional-formats" => {}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"table:table-header-columns" => {
col_range_from = table_col;
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"table:table-header-columns" => {
sheet.header_cols = Some(ColRange::new(col_range_from, table_col - 1));
}
Event::Empty(xml_tag)
if xml_tag.name().as_ref() == b"table:table-column" => {
table_col = read_table_col_attr(&mut sheet, table_col, &xml_tag)?;
}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"table:table-header-rows" => {
row_range_from = row;
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"table:table-header-rows" => {
sheet.header_rows = Some(RowRange::new(row_range_from, row - 1));
}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"table:table-row" => {
let (repeat, style, cellstyle, visible) = read_table_row_attr( xml_tag)?;
row_repeat = repeat;
rowstyle = style;
row_cellstyle = cellstyle;
row_visible = visible;
}
Event::End(xml_tag)
if xml_tag.name().as_ref() == b"table:table-row" => {
if row_repeat > 1 {
sheet.set_row_repeat(row, row_repeat);
}
if let Some(rowstyle) = rowstyle {
sheet.set_rowstyle(row, &rowstyle.into());
}
rowstyle = None;
if let Some(row_cellstyle) = row_cellstyle {
sheet.set_row_cellstyle(row, &row_cellstyle.into());
}
row_cellstyle = None;
if row_visible != Visibility::Visible {
sheet.set_row_visible(row, row_visible);
}
row_visible = Default::default();
row += row_repeat;
col = 0;
row_repeat = 1;
}
Event::Empty(xml_tag)
if xml_tag.name().as_ref() == b"table:table-cell" || xml_tag.name().as_ref() == b"table:covered-table-cell" => {
col = read_empty_table_cell(&mut sheet, row, col, xml_tag)?;
}
Event::Start(xml_tag)
if xml_tag.name().as_ref() == b"table:table-cell" || xml_tag.name().as_ref() == b"table:covered-table-cell" => {
col = read_table_cell2(bs, &mut sheet, row, col, xml, xml_tag)?;
}
_ => {
dump_unused2("read_table", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(sheet)
}
fn read_table_attr(sheet: &mut Sheet, xml_tag: BytesStart<'_>) -> Result<(), OdsError> {
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:name" => {
sheet.set_name(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:style-name" => {
sheet.set_style(&attr.unescape_value()?.as_ref().into());
}
attr if attr.key.as_ref() == b"table:print" => {
sheet.set_print(parse_bool(&attr.value)?);
}
attr if attr.key.as_ref() == b"table:display" => {
sheet.set_display(parse_bool(&attr.value)?);
}
attr if attr.key.as_ref() == b"table:print-ranges" => {
let v = attr.unescape_value()?;
let mut pos = 0usize;
sheet.print_ranges = parse_cellranges(v.as_ref(), &mut pos)?;
}
attr => {
dump_unused("read_table_attr", xml_tag.name().as_ref(), &attr)?;
}
}
}
Ok(())
}
fn read_table_row_attr(
xml_tag: BytesStart<'_>,
) -> Result<(u32, Option<String>, Option<String>, Visibility), OdsError> {
let mut row_repeat: u32 = 1;
let mut row_visible: Visibility = Default::default();
let mut rowstyle: Option<String> = None;
let mut row_cellstyle: Option<String> = None;
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:number-rows-repeated" => {
row_repeat = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:style-name" => {
rowstyle = Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:default-cell-style-name" => {
row_cellstyle = Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:visibility" => {
row_visible = parse_visibility(&attr.value)?;
}
attr => {
dump_unused("read_table_row_attr", xml_tag.name().as_ref(), &attr)?;
}
}
}
Ok((row_repeat, rowstyle, row_cellstyle, row_visible))
}
fn read_table_col_attr(
sheet: &mut Sheet,
mut table_col: u32,
xml_tag: &BytesStart<'_>,
) -> Result<u32, OdsError> {
let mut style = None;
let mut cellstyle = None;
let mut repeat: u32 = 1;
let mut visible: Visibility = Default::default();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:number-columns-repeated" => {
repeat = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:style-name" => {
style = Some(attr.unescape_value()?);
}
attr if attr.key.as_ref() == b"table:default-cell-style-name" => {
cellstyle = Some(attr.unescape_value()?);
}
attr if attr.key.as_ref() == b"table:visibility" => {
visible = parse_visibility(&attr.value)?;
}
attr => {
dump_unused("read_table_col_attr", xml_tag.name().as_ref(), &attr)?;
}
}
}
while repeat > 0 {
if let Some(style) = &style {
sheet.set_colstyle(table_col, &style.as_ref().into());
}
if let Some(cellstyle) = &cellstyle {
sheet.set_col_cellstyle(table_col, &cellstyle.as_ref().into());
}
sheet.set_col_visible(table_col, visible);
table_col += 1;
repeat -= 1;
}
Ok(table_col)
}
#[derive(Debug)]
#[allow(variant_size_differences)]
enum TextContent2 {
Empty,
Text(String),
Xml(TextTag),
XmlVec(Vec<TextTag>),
}
#[derive(Debug)]
struct ReadTableCell2 {
val_type: ValueType,
val_datetime: Option<NaiveDateTime>,
val_duration: Option<Duration>,
val_float: Option<f64>,
val_bool: Option<bool>,
val_string: Option<String>,
val_currency: Option<String>,
content: TextContent2,
}
fn read_table_cell2(
bs: &mut BufStack,
sheet: &mut Sheet,
row: u32,
mut col: u32,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: BytesStart<'_>,
) -> Result<u32, OdsError> {
let tag_name = xml_tag.name();
let mut cell_repeat: u32 = 1;
let mut cell = CellData {
value: Default::default(),
formula: None,
style: None,
validation_name: None,
span: Default::default(),
};
let mut tc = ReadTableCell2 {
val_type: ValueType::Empty,
val_datetime: None,
val_duration: None,
val_float: None,
val_bool: None,
val_string: None,
val_currency: None,
content: TextContent2::Empty,
};
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:number-columns-repeated" => {
cell_repeat = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:number-rows-spanned" => {
cell.span.row_span = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:number-columns-spanned" => {
cell.span.col_span = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:content-validation-name" => {
cell.validation_name = Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"calcext:value-type" => {
}
attr if attr.key.as_ref() == b"office:value-type" => {
tc.val_type = match attr.value.as_ref() {
b"string" => ValueType::Text,
b"float" => ValueType::Number,
b"percentage" => ValueType::Percentage,
b"date" => ValueType::DateTime,
b"time" => ValueType::TimeDuration,
b"boolean" => ValueType::Boolean,
b"currency" => ValueType::Currency,
other => return Err(OdsError::Parse(format!("Unknown cell-type {:?}", other))),
}
}
attr if attr.key.as_ref() == b"office:date-value" => {
tc.val_datetime = Some(parse_datetime(&attr.value)?);
}
attr if attr.key.as_ref() == b"office:time-value" => {
tc.val_duration = Some(parse_duration(&attr.value)?);
}
attr if attr.key.as_ref() == b"office:value" => {
tc.val_float = Some(parse_f64(&attr.value)?);
}
attr if attr.key.as_ref() == b"office:boolean-value" => {
tc.val_bool = Some(parse_bool(&attr.value)?);
}
attr if attr.key.as_ref() == b"office:string-value" => {
tc.val_string = Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"office:currency" => {
tc.val_currency = Some(parse_currency(&attr.value)?);
}
attr if attr.key.as_ref() == b"table:formula" => {
cell.formula = Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:style-name" => {
cell.style = Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused("read_table_cell2", xml_tag.name().as_ref(), &attr)?;
}
}
}
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_table_cell {:?}", evt);
}
match evt {
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"text:p" => {
let new_txt = read_text_or_tag(bs, b"text:p", xml, &xml_tag, false)?;
tc.content = append_text(new_txt, tc.content);
}
Event::Empty(xml_tag) if xml_tag.name().as_ref() == b"text:p" => {
}
Event::End(xml_tag) if xml_tag.name() == tag_name => {
parse_value2(tc, &mut cell)?;
while cell_repeat > 1 {
sheet.add_cell_data(row, col, cell.clone());
col += 1;
cell_repeat -= 1;
}
sheet.add_cell_data(row, col, cell);
col += 1;
break;
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_table_cell", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(col)
}
fn append_text(new_txt: TextContent2, mut content: TextContent2) -> TextContent2 {
content = match content {
TextContent2::Empty => new_txt,
TextContent2::Text(txt) => {
let p = TextP::new().text(txt).into_xmltag();
let mut vec = vec![p];
match new_txt {
TextContent2::Empty => {}
TextContent2::Text(txt) => {
let p2 = TextP::new().text(txt).into_xmltag();
vec.push(p2);
}
TextContent2::Xml(xml) => {
vec.push(xml);
}
TextContent2::XmlVec(_) => {
unreachable!();
}
}
TextContent2::XmlVec(vec)
}
TextContent2::Xml(xml) => {
let mut vec = vec![xml];
match new_txt {
TextContent2::Empty => {}
TextContent2::Text(txt) => {
let p2 = TextP::new().text(txt).into_xmltag();
vec.push(p2);
}
TextContent2::Xml(xml) => {
vec.push(xml);
}
TextContent2::XmlVec(_) => {
unreachable!();
}
}
TextContent2::XmlVec(vec)
}
TextContent2::XmlVec(mut vec) => {
match new_txt {
TextContent2::Empty => {}
TextContent2::Text(txt) => {
let p2 = TextP::new().text(txt).into_xmltag();
vec.push(p2);
}
TextContent2::Xml(xml) => {
vec.push(xml);
}
TextContent2::XmlVec(_) => {
unreachable!();
}
}
TextContent2::XmlVec(vec)
}
};
content
}
fn parse_value2(tc: ReadTableCell2, cell: &mut CellData) -> Result<(), OdsError> {
match tc.val_type {
ValueType::Empty => {
}
ValueType::Boolean => {
if let Some(v) = tc.val_bool {
cell.value = Value::Boolean(v);
} else {
return Err(OdsError::Parse("no boolean value".to_string()));
}
}
ValueType::Number => {
if let Some(v) = tc.val_float {
cell.value = Value::Number(v);
} else {
return Err(OdsError::Parse("no float value".to_string()));
}
}
ValueType::Percentage => {
if let Some(v) = tc.val_float {
cell.value = Value::Percentage(v);
} else {
return Err(OdsError::Parse("no float value".to_string()));
}
}
ValueType::Currency => {
if let Some(v) = tc.val_float {
if let Some(c) = tc.val_currency {
cell.value = Value::Currency(v, c);
} else {
return Err(OdsError::Parse("no currency value".to_string()));
}
} else {
return Err(OdsError::Parse("no float value".to_string()));
}
}
ValueType::Text => {
if let Some(v) = tc.val_string {
cell.value = Value::Text(v);
} else {
match tc.content {
TextContent2::Empty => {
}
TextContent2::Text(txt) => {
cell.value = Value::Text(txt);
}
TextContent2::Xml(xml) => {
cell.value = Value::TextXml(vec![xml]);
}
TextContent2::XmlVec(vec) => {
cell.value = Value::TextXml(vec);
}
}
}
}
ValueType::TextXml => {
unreachable!();
}
ValueType::DateTime => {
if let Some(v) = tc.val_datetime {
cell.value = Value::DateTime(v);
} else {
return Err(OdsError::Parse("no datetime value".to_string()));
}
}
ValueType::TimeDuration => {
if let Some(v) = tc.val_duration {
cell.value = Value::TimeDuration(v);
} else {
return Err(OdsError::Parse("no duration value".to_string()));
}
}
}
Ok(())
}
fn read_empty_table_cell(
sheet: &mut Sheet,
row: u32,
mut col: u32,
xml_tag: BytesStart<'_>,
) -> Result<u32, OdsError> {
let mut cell = None;
let mut cell_repeat = 1;
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:number-columns-repeated" => {
cell_repeat = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:formula" => {
cell.get_or_insert_with(CellData::new).formula =
Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:style-name" => {
cell.get_or_insert_with(CellData::new).style =
Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:number-rows-spanned" => {
cell.get_or_insert_with(CellData::new).span.row_span = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:number-columns-spanned" => {
cell.get_or_insert_with(CellData::new).span.col_span = parse_u32(&attr.value)?;
}
attr if attr.key.as_ref() == b"table:content-validation-name" => {
cell.get_or_insert_with(CellData::new).validation_name =
Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused("read_empty_table_cell", xml_tag.name().as_ref(), &attr)?;
}
}
}
if let Some(cell) = cell {
while cell_repeat > 1 {
sheet.add_cell_data(row, col, cell.clone());
col += 1;
cell_repeat -= 1;
}
sheet.add_cell_data(row, col, cell);
col += 1;
} else {
col += cell_repeat;
}
Ok(col)
}
fn read_fonts(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(), OdsError> {
let mut font: FontFaceDecl = FontFaceDecl::new_empty();
font.set_origin(origin);
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_fonts {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:font-face" => {
let name = proc_style_attr(font.attrmap_mut(), xml_tag)?;
font.set_name(name);
book.add_font(font);
font = FontFaceDecl::new_empty();
font.set_origin(StyleOrigin::Content);
}
_ => {
dump_unused2("read_fonts", &evt)?;
}
}
}
Event::End(ref e) => {
if e.name().as_ref() == b"office:font-face-decls" {
break;
}
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_fonts", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_page_style(
bs: &mut BufStack,
book: &mut WorkBook,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
) -> Result<(), OdsError> {
let mut pl = PageStyle::new_empty();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"style:name" => {
pl.set_name(&attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"style:page-usage" => {
pl.master_page_usage = Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused("read_page_style", xml_tag.name().as_ref(), &attr)?;
}
}
}
let mut headerstyle = false;
let mut footerstyle = false;
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_page_layout {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:page-layout-properties" => copy_attr2(pl.style_mut(), xml_tag)?,
b"style:header-style" => headerstyle = true,
b"style:footer-style" => footerstyle = true,
b"style:header-footer-properties" => {
if headerstyle {
copy_attr2(pl.headerstyle_mut().style_mut(), xml_tag)?;
}
if footerstyle {
copy_attr2(pl.footerstyle_mut().style_mut(), xml_tag)?;
}
}
b"style:background-image" => {
}
_ => {
dump_unused2("read_page_layout", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref end) => match end.name().as_ref() {
b"style:page-layout" => break,
b"style:page-layout-properties" => {}
b"style:header-style" => headerstyle = false,
b"style:footer-style" => footerstyle = false,
b"style:header-footer-properties" => {}
_ => {
dump_unused2("read_page_layout", &evt)?;
}
},
Event::Eof => break,
_ => {
dump_unused2("read_page_layout", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
book.add_pagestyle(pl);
Ok(())
}
fn read_validations(
bs: &mut BufStack,
book: &mut WorkBook,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(), OdsError> {
let mut valid = Validation::new();
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
let empty_tag = matches!(evt, Event::Empty(_));
if DUMP_XML {
println!(" read_master_styles {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"table:content-validation" => {
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:name" => {
valid.set_name(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"table:condition" => {
let v = attr.unescape_value()?.to_string();
valid.set_condition(Condition::new(v.split_at(3).1));
}
attr if attr.key.as_ref() == b"table:allow-empty-cell" => {
valid.set_allow_empty(parse_bool(&attr.value)?);
}
attr if attr.key.as_ref() == b"table:base-cell-address" => {
let v = attr.unescape_value()?;
let mut pos = 0usize;
valid.set_base_cell(parse_cellref(&v, &mut pos)?);
}
attr if attr.key.as_ref() == b"table:display-list" => {
valid.set_display(attr.value.as_ref().try_into()?);
}
attr => {
dump_unused(
"read_validations",
xml_tag.name().as_ref(),
&attr,
)?;
}
}
}
if empty_tag {
book.add_validation(valid);
valid = Validation::new();
}
}
b"table:error-message" => {
let mut ve = ValidationError::new();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:display" => {
ve.set_display(parse_bool(&attr.value)?);
}
attr if attr.key.as_ref() == b"table:message-type" => {
let mt = match attr.value.as_ref() {
b"stop" => MessageType::Error,
b"warning" => MessageType::Warning,
b"information" => MessageType::Info,
_ => {
return Err(OdsError::Parse(format!(
"unknown message-type {}",
attr.unescape_value()?
)))
}
};
ve.set_msg_type(mt);
}
attr if attr.key.as_ref() == b"table:title" => {
ve.set_title(Some(attr.unescape_value()?.to_string()));
}
attr => {
dump_unused(
"read_validations",
xml_tag.name().as_ref(),
&attr,
)?;
}
}
}
let txt =
read_text_or_tag(bs, b"table:error-message", xml, xml_tag, empty_tag)?;
match txt {
TextContent2::Empty => {}
TextContent2::Xml(txt) => {
ve.set_text(Some(txt));
}
_ => {
return Err(OdsError::Xml(quick_xml::Error::UnexpectedToken(
format!("table:error-message invalid {:?}", txt),
)));
}
}
valid.set_err(Some(ve));
}
b"table:help-message" => {
let mut vh = ValidationHelp::new();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"table:display" => {
vh.set_display(parse_bool(&attr.value)?);
}
attr if attr.key.as_ref() == b"table:title" => {
vh.set_title(Some(attr.unescape_value()?.to_string()));
}
attr => {
dump_unused(
"read_validations",
xml_tag.name().as_ref(),
&attr,
)?;
}
}
}
let txt =
read_text_or_tag(bs, b"table:help-message", xml, xml_tag, empty_tag)?;
match txt {
TextContent2::Empty => {}
TextContent2::Xml(txt) => {
vh.set_text(Some(txt));
}
_ => {
return Err(OdsError::Xml(quick_xml::Error::UnexpectedToken(
format!("table:help-message invalid {:?}", txt),
)));
}
}
valid.set_help(Some(vh));
}
_ => {
dump_unused2("read_validations", &evt)?;
}
}
}
Event::End(ref e) => match e.name().as_ref() {
b"table:content-validation" => {
book.add_validation(valid);
valid = Validation::new();
}
b"table:content-validations" => {
break;
}
_ => {}
},
Event::Text(_) => (),
Event::Eof => break,
_ => {
dump_unused2("read_validations", &evt)?;
}
}
}
bs.push(buf);
Ok(())
}
fn read_master_styles(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(), OdsError> {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_master_styles {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:master-page" => {
read_master_page(bs, book, origin, xml, xml_tag)?;
}
_ => {
dump_unused2("read_master_styles", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == b"office:master-styles" {
break;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_master_styles", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_master_page(
bs: &mut BufStack,
book: &mut WorkBook,
_origin: StyleOrigin,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
) -> Result<(), OdsError> {
let mut masterpage = MasterPage::new_empty();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"style:name" => {
masterpage.set_name(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"style:page-layout-name" => {
masterpage.set_pagestyle(&attr.unescape_value()?.as_ref().into());
}
attr => {
dump_unused("read_master_page", xml_tag.name().as_ref(), &attr)?;
}
}
}
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_master_page {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) => match xml_tag.name().as_ref() {
b"style:header" => {
masterpage.set_header(read_headerfooter(bs, b"style:header", xml, xml_tag)?);
}
b"style:header-first" => {
masterpage.set_header_first(read_headerfooter(
bs,
b"style:header-first",
xml,
xml_tag,
)?);
}
b"style:header-left" => {
masterpage.set_header_left(read_headerfooter(
bs,
b"style:header-left",
xml,
xml_tag,
)?);
}
b"style:footer" => {
masterpage.set_footer(read_headerfooter(bs, b"style:footer", xml, xml_tag)?);
}
b"style:footer-first" => {
masterpage.set_footer_first(read_headerfooter(
bs,
b"style:footer-first",
xml,
xml_tag,
)?);
}
b"style:footer-left" => {
masterpage.set_footer_left(read_headerfooter(
bs,
b"style:footer-left",
xml,
xml_tag,
)?);
}
_ => {
dump_unused2("read_master_page", &evt)?;
}
},
Event::Empty(_) => {
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == b"style:master-page" {
break;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_master_page", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
book.add_masterpage(masterpage);
Ok(())
}
fn read_headerfooter(
bs: &mut BufStack,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
) -> Result<HeaderFooter, OdsError> {
let mut hf = HeaderFooter::new();
let mut content = TextContent2::Empty;
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"style:display" => {
hf.set_display(parse_bool(&attr.value)?);
}
attr => {
dump_unused("read_headerfooter", xml_tag.name().as_ref(), &attr)?;
}
}
}
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
let empty_tag = matches!(evt, Event::Empty(_));
if DUMP_XML {
println!(" read_headerfooter {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:region-left" => {
let reg = read_xml(bs, b"style:region-left", xml, xml_tag, empty_tag)?;
hf.set_left(reg.into_vec()?);
}
b"style:region-center" => {
let reg = read_xml(bs, b"style:region-center", xml, xml_tag, empty_tag)?;
hf.set_center(reg.into_vec()?);
}
b"style:region-right" => {
let reg = read_xml(bs, b"style:region-right", xml, xml_tag, empty_tag)?;
hf.set_right(reg.into_vec()?);
}
b"text:p" => {
let new_txt = read_text_or_tag(bs, b"text:p", xml, xml_tag, empty_tag)?;
content = append_text(new_txt, content);
}
b"text:h" => {
let new_txt = read_text_or_tag(bs, b"text:p", xml, xml_tag, empty_tag)?;
content = append_text(new_txt, content);
}
_ => {
dump_unused2("read_headerfooter", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
hf.set_content(match content {
TextContent2::Empty => Vec::new(),
TextContent2::Text(v) => vec![TextP::new().text(v).into()],
TextContent2::Xml(v) => vec![v],
TextContent2::XmlVec(v) => v,
});
break;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_headerfooter", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(hf)
}
fn read_styles_tag(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(), OdsError> {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
let empty_tag = matches!(evt, Event::Empty(_));
if DUMP_XML {
println!(" read_styles_tag {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:style" => {
read_style_style(
bs,
book,
origin,
StyleUse::Named,
b"style:style",
xml,
xml_tag,
empty_tag,
)?;
}
b"style:default-style" => {
read_style_style(
bs,
book,
origin,
StyleUse::Default,
b"style:default-style",
xml,
xml_tag,
empty_tag,
)?;
}
b"number:boolean-style"
| b"number:date-style"
| b"number:time-style"
| b"number:number-style"
| b"number:currency-style"
| b"number:percentage-style"
| b"number:text-style" => {
read_value_format(bs, book, origin, StyleUse::Named, xml, xml_tag)?;
}
_ => {
dump_unused2("read_styles_tag", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == b"office:styles" {
break;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_styles_tag", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_auto_styles(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(), OdsError> {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
let empty_tag = matches!(evt, Event::Empty(_));
if DUMP_XML {
println!(" read_auto_styles {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:style" => {
read_style_style(
bs,
book,
origin,
StyleUse::Automatic,
b"style:style",
xml,
xml_tag,
empty_tag,
)?;
}
b"number:boolean-style"
| b"number:date-style"
| b"number:time-style"
| b"number:number-style"
| b"number:currency-style"
| b"number:percentage-style"
| b"number:text-style" => {
read_value_format(bs, book, origin, StyleUse::Automatic, xml, xml_tag)?;
}
b"style:page-layout" => {
read_page_style(bs, book, xml, xml_tag)?;
}
_ => {
dump_unused2("read_auto_styles", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == b"office:automatic-styles" {
break;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_auto_styles", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_value_format(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
) -> Result<(), OdsError> {
match xml_tag.name().as_ref() {
b"number:boolean-style" => {
let mut valuestyle = ValueFormatBoolean::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_boolean_format(valuestyle);
}
b"number:date-style" => {
let mut valuestyle = ValueFormatDateTime::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_datetime_format(valuestyle);
}
b"number:time-style" => {
let mut valuestyle = ValueFormatTimeDuration::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_timeduration_format(valuestyle);
}
b"number:number-style" => {
let mut valuestyle = ValueFormatNumber::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_number_format(valuestyle);
}
b"number:currency-style" => {
let mut valuestyle = ValueFormatCurrency::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_currency_format(valuestyle);
}
b"number:percentage-style" => {
let mut valuestyle = ValueFormatPercentage::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_percentage_format(valuestyle);
}
b"number:text-style" => {
let mut valuestyle = ValueFormatText::new_empty();
read_value_format_parts(bs, origin, styleuse, &mut valuestyle, xml, xml_tag)?;
book.add_text_format(valuestyle);
}
_ => {
if DUMP_UNUSED {
let tag_name = xml_tag.name();
let n = xml.decoder().decode(tag_name.as_ref())?;
println!(" read_value_format unused {n}");
}
}
}
Ok(())
}
fn read_value_format_parts<T: ValueFormatTrait>(
bs: &mut BufStack,
origin: StyleOrigin,
styleuse: StyleUse,
valuestyle: &mut T,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
) -> Result<(), OdsError> {
valuestyle.set_origin(origin);
valuestyle.set_styleuse(styleuse);
let name = proc_style_attr(valuestyle.attrmap_mut(), xml_tag)?;
valuestyle.set_name(name.as_str());
let mut valuestyle_part = None;
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_value_format_parts {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"number:boolean" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Boolean)?)
}
b"number:number" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Number)?)
}
b"number:scientific-number" => valuestyle.push_part(read_part(
bs,
xml,
xml_tag,
FormatPartType::ScientificNumber,
)?),
b"number:day" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Day)?)
}
b"number:month" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Month)?)
}
b"number:year" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Year)?)
}
b"number:era" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Era)?)
}
b"number:day-of-week" => valuestyle.push_part(read_part(
bs,
xml,
xml_tag,
FormatPartType::DayOfWeek,
)?),
b"number:week-of-year" => valuestyle.push_part(read_part(
bs,
xml,
xml_tag,
FormatPartType::WeekOfYear,
)?),
b"number:quarter" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Quarter)?)
}
b"number:hours" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Hours)?)
}
b"number:minutes" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Minutes)?)
}
b"number:seconds" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Seconds)?)
}
b"number:fraction" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Fraction)?)
}
b"number:am-pm" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::AmPm)?)
}
b"number:text-content" => valuestyle.push_part(read_part(
bs,
xml,
xml_tag,
FormatPartType::TextContent,
)?),
b"style:text" => {
valuestyle.push_part(read_part(bs, xml, xml_tag, FormatPartType::Day)?)
}
b"style:map" => valuestyle.push_stylemap(read_stylemap(xml_tag)?),
b"number:fill-character" => {
valuestyle_part =
Some(read_part(bs, xml, xml_tag, FormatPartType::FillCharacter)?);
if let Event::Empty(_) = evt {
if let Some(part) = valuestyle_part {
valuestyle.push_part(part);
}
valuestyle_part = None;
}
}
b"number:currency-symbol" => {
valuestyle_part =
Some(read_part(bs, xml, xml_tag, FormatPartType::CurrencySymbol)?);
if let Event::Empty(_) = evt {
if let Some(part) = valuestyle_part {
valuestyle.push_part(part);
}
valuestyle_part = None;
}
}
b"number:text" => {
valuestyle_part = Some(read_part(bs, xml, xml_tag, FormatPartType::Text)?);
if let Event::Empty(_) = evt {
if let Some(part) = valuestyle_part {
valuestyle.push_part(part);
}
valuestyle_part = None;
}
}
b"style:text-properties" => copy_attr2(valuestyle.textstyle_mut(), xml_tag)?,
_ => {
dump_unused2("read_value_format", &evt)?;
}
}
}
Event::Text(ref e) => {
if let Some(part) = &mut valuestyle_part {
part.set_content(xml.decoder().decode(e.unescape()?.as_bytes())?);
}
}
Event::End(ref e) => match e.name().as_ref() {
b"number:boolean-style"
| b"number:date-style"
| b"number:time-style"
| b"number:number-style"
| b"number:currency-style"
| b"number:percentage-style"
| b"number:text-style" => {
break;
}
b"number:currency-symbol" | b"number:text" | b"number:fill-character" => {
if let Some(part) = valuestyle_part {
valuestyle.push_part(part);
}
valuestyle_part = None;
}
_ => {
dump_unused2("read_value_format_parts", &evt)?;
}
},
Event::Eof => break,
_ => {
dump_unused2("read_value_format_parts", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_part(
_bs: &mut BufStack,
_xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
part_type: FormatPartType,
) -> Result<FormatPart, OdsError> {
let mut part = FormatPart::new(part_type);
copy_attr2(part.attrmap_mut(), xml_tag)?;
Ok(part)
}
#[allow(clippy::too_many_arguments)]
fn read_style_style(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"style:family" => {
match attr.value.as_ref() {
b"table" => read_tablestyle(
bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag,
)?,
b"table-column" => {
read_colstyle(bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag)?
}
b"table-row" => {
read_rowstyle(bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag)?
}
b"table-cell" => read_cellstyle(
bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag,
)?,
b"graphic" => read_graphicstyle(
bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag,
)?,
b"paragraph" => read_paragraphstyle(
bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag,
)?,
b"text" => read_textstyle(
bs, book, origin, styleuse, end_tag, xml, xml_tag, empty_tag,
)?,
value => {
return Err(OdsError::Ods(format!(
"style:family unknown {} ",
from_utf8(value)?
)))
}
};
}
attr => {
dump_unused("read_style_style", xml_tag.name().as_ref(), &attr)?;
}
}
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_tablestyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = TableStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_tablestyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_table_style {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:table-properties" => copy_attr2(style.tablestyle_mut(), xml_tag)?,
_ => {
dump_unused2("read_table_style", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_tablestyle(style);
break;
} else {
dump_unused2("read_table_style", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_table_style", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_rowstyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = RowStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_rowstyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_rowstyle {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:table-row-properties" => copy_attr2(style.rowstyle_mut(), xml_tag)?,
_ => {
dump_unused2("read_rowstyle", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_rowstyle(style);
break;
} else {
dump_unused2("read_rowstyle", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_rowstyle", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_colstyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = ColStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_colstyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_colstyle {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => match xml_tag
.name()
.as_ref()
{
b"style:table-column-properties" => copy_attr2(style.colstyle_mut(), xml_tag)?,
_ => {
dump_unused2("read_colstyle", &evt)?;
}
},
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_colstyle(style);
break;
} else {
dump_unused2("read_colstyle", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_colstyle", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_cellstyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = CellStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_cellstyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_cellstyle {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => match xml_tag
.name()
.as_ref()
{
b"style:table-cell-properties" => copy_attr2(style.cellstyle_mut(), xml_tag)?,
b"style:text-properties" => copy_attr2(style.textstyle_mut(), xml_tag)?,
b"style:paragraph-properties" => {
copy_attr2(style.paragraphstyle_mut(), xml_tag)?
}
b"style:map" => style.push_stylemap(read_stylemap(xml_tag)?),
_ => {
dump_unused2("read_cellstyle", &evt)?;
}
},
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_cellstyle(style);
break;
} else if e.name().as_ref() == b"style:paragraph-properties" {
} else {
dump_unused2("read_cellstyle", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_cellstyle", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_paragraphstyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = ParagraphStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_paragraphstyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_paragraphstyle {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:text-properties" => copy_attr2(style.textstyle_mut(), xml_tag)?,
b"style:paragraph-properties" => {
copy_attr2(style.paragraphstyle_mut(), xml_tag)?
}
b"style:tab-stops" => (),
b"style:tab-stop" => {
let mut ts = TabStop::new();
copy_attr2(ts.attrmap_mut(), xml_tag)?;
style.add_tabstop(ts);
}
_ => {
dump_unused2("read_paragraphstyle", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_paragraphstyle(style);
break;
} else if e.name().as_ref() == b"style:tab-stops"
|| e.name().as_ref() == b"style:paragraph-properties"
{
} else {
dump_unused2("read_paragraphstyle", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_paragraphstyle", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_textstyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = TextStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_textstyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_textstyle {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => {
match xml_tag.name().as_ref() {
b"style:text-properties" => copy_attr2(style.textstyle_mut(), xml_tag)?,
_ => {
dump_unused2("read_textstyle", &evt)?;
}
}
}
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_textstyle(style);
break;
} else {
dump_unused2("read_textstyle", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_textstyle", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::too_many_arguments)]
fn read_graphicstyle(
bs: &mut BufStack,
book: &mut WorkBook,
origin: StyleOrigin,
styleuse: StyleUse,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<(), OdsError> {
let mut style = GraphicStyle::new_empty();
style.set_origin(origin);
style.set_styleuse(styleuse);
let name = proc_style_attr(style.attrmap_mut(), xml_tag)?;
style.set_name(name);
if empty_tag {
book.add_graphicstyle(style);
} else {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_graphicstyle {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) | Event::Empty(ref xml_tag) => match xml_tag
.name()
.as_ref()
{
b"style:graphic-properties" => copy_attr2(style.graphicstyle_mut(), xml_tag)?,
_ => {
dump_unused2("read_graphicstyle", &evt)?;
}
},
Event::Text(_) => (),
Event::End(ref e) => {
if e.name().as_ref() == end_tag {
book.add_graphicstyle(style);
break;
} else {
dump_unused2("read_graphicstyle", &evt)?;
}
}
Event::Eof => break,
_ => {
dump_unused2("read_graphicstyle", &evt)?;
}
}
}
bs.push(buf);
}
Ok(())
}
fn read_stylemap(xml_tag: &BytesStart<'_>) -> Result<StyleMap, OdsError> {
let mut sm = StyleMap::default();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"style:condition" => {
sm.set_condition(ValueCondition::new(attr.unescape_value()?.to_string()));
}
attr if attr.key.as_ref() == b"style:apply-style-name" => {
sm.set_applied_style(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"style:base-cell-address" => {
let v = attr.unescape_value()?;
let mut pos = 0usize;
sm.set_base_cell(parse_cellref(v.as_ref(), &mut pos)?);
}
attr => {
dump_unused("read_stylemap", xml_tag.name().as_ref(), &attr)?;
}
}
}
Ok(sm)
}
fn proc_style_attr<'a>(
attrmap: &mut AttrMap2,
xml_tag: &'a BytesStart<'_>,
) -> Result<String, OdsError> {
let mut name = None;
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"style:name" => {
name = Some(attr.unescape_value()?.to_string());
}
attr => {
let k = from_utf8(attr.key.as_ref())?;
let v = attr.unescape_value()?.to_string();
attrmap.set_attr(k, v);
}
}
}
Ok(name.unwrap_or_default())
}
fn copy_attr2(attrmap: &mut AttrMap2, xml_tag: &BytesStart<'_>) -> Result<(), OdsError> {
for attr in xml_tag.attributes().with_checks(false) {
let attr = attr?;
let k = from_utf8(attr.key.as_ref())?;
let v = attr.unescape_value()?.to_string();
attrmap.set_attr(k, v);
}
Ok(())
}
fn read_styles(
bs: &mut BufStack,
book: &mut WorkBook,
zip_file: &mut ZipFile<'_>,
) -> Result<(), OdsError> {
let mut xml = quick_xml::Reader::from_reader(BufReader::new(zip_file));
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_styles {:?}", evt);
}
match evt {
Event::Decl(_) => {}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:document-styles" => {
}
Event::End(xml_tag) if xml_tag.name().as_ref() == b"office:document-styles" => {
}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:font-face-decls" => {
read_fonts(bs, book, StyleOrigin::Styles, &mut xml)?
}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:styles" => {
read_styles_tag(bs, book, StyleOrigin::Styles, &mut xml)?
}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:automatic-styles" => {
read_auto_styles(bs, book, StyleOrigin::Styles, &mut xml)?
}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:master-styles" => {
read_master_styles(bs, book, StyleOrigin::Styles, &mut xml)?
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_styles", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
#[allow(unused_variables)]
pub(crate) fn default_settings() -> Detach<Config> {
let mut dc = Detach::new(Config::new());
let p0 = dc.create_path(&[("ooo:view-settings", ConfigItemType::Set)]);
p0.insert("VisibleAreaTop", 0);
p0.insert("VisibleAreaLeft", 0);
p0.insert("VisibleAreaWidth", 2540);
p0.insert("VisibleAreaHeight", 1270);
let p0 = dc.create_path(&[
("ooo:view-settings", ConfigItemType::Set),
("Views", ConfigItemType::Vec),
("0", ConfigItemType::Entry),
]);
p0.insert("ViewId", "view1");
let p0 = dc.create_path(&[
("ooo:view-settings", ConfigItemType::Set),
("Views", ConfigItemType::Vec),
("0", ConfigItemType::Entry),
("Tables", ConfigItemType::Map),
]);
let p0 = dc.create_path(&[
("ooo:view-settings", ConfigItemType::Set),
("Views", ConfigItemType::Vec),
("0", ConfigItemType::Entry),
]);
p0.insert("ActiveTable", "");
p0.insert("HorizontalScrollbarWidth", 702);
p0.insert("ZoomType", 0i16);
p0.insert("ZoomValue", 100);
p0.insert("PageViewZoomValue", 60);
p0.insert("ShowPageBreakPreview", false);
p0.insert("ShowZeroValues", true);
p0.insert("ShowNotes", true);
p0.insert("ShowGrid", true);
p0.insert("GridColor", 12632256);
p0.insert("ShowPageBreaks", false);
p0.insert("HasColumnRowHeaders", true);
p0.insert("HasSheetTabs", true);
p0.insert("IsOutlineSymbolsSet", true);
p0.insert("IsValueHighlightingEnabled", false);
p0.insert("IsSnapToRaster", false);
p0.insert("RasterIsVisible", false);
p0.insert("RasterResolutionX", 1000);
p0.insert("RasterResolutionY", 1000);
p0.insert("RasterSubdivisionX", 1);
p0.insert("RasterSubdivisionY", 1);
p0.insert("IsRasterAxisSynchronized", true);
p0.insert("AnchoredTextOverflowLegacy", false);
let p0 = dc.create_path(&[("ooo:configuration-settings", ConfigItemType::Set)]);
p0.insert("HasSheetTabs", true);
p0.insert("ShowNotes", true);
p0.insert("EmbedComplexScriptFonts", true);
p0.insert("ShowZeroValues", true);
p0.insert("ShowGrid", true);
p0.insert("GridColor", 12632256);
p0.insert("ShowPageBreaks", false);
p0.insert("IsKernAsianPunctuation", false);
p0.insert("LinkUpdateMode", 3i16);
p0.insert("HasColumnRowHeaders", true);
p0.insert("EmbedLatinScriptFonts", true);
p0.insert("IsOutlineSymbolsSet", true);
p0.insert("EmbedLatinScriptFonts", true);
p0.insert("IsOutlineSymbolsSet", true);
p0.insert("IsSnapToRaster", false);
p0.insert("RasterIsVisible", false);
p0.insert("RasterResolutionX", 1000);
p0.insert("RasterResolutionY", 1000);
p0.insert("RasterSubdivisionX", 1);
p0.insert("RasterSubdivisionY", 1);
p0.insert("IsRasterAxisSynchronized", true);
p0.insert("AutoCalculate", true);
p0.insert("ApplyUserData", true);
p0.insert("PrinterName", "");
p0.insert("PrinterSetup", ConfigValue::Base64Binary("".to_string()));
p0.insert("SaveThumbnail", true);
p0.insert("CharacterCompressionType", 0i16);
p0.insert("SaveVersionOnClose", false);
p0.insert("UpdateFromTemplate", true);
p0.insert("AllowPrintJobCancel", true);
p0.insert("LoadReadonly", false);
p0.insert("IsDocumentShared", false);
p0.insert("EmbedFonts", false);
p0.insert("EmbedOnlyUsedFonts", false);
p0.insert("EmbedAsianScriptFonts", true);
p0.insert("SyntaxStringRef", 7i16);
dc
}
fn read_settings(
bs: &mut BufStack,
book: &mut WorkBook,
zip_file: &mut ZipFile<'_>,
) -> Result<(), OdsError> {
let mut xml = quick_xml::Reader::from_reader(BufReader::new(zip_file));
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_settings {:?}", evt);
}
match evt {
Event::Decl(_) => {}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:document-settings" => {
}
Event::End(xml_tag) if xml_tag.name().as_ref() == b"office:document-settings" => {
}
Event::Start(xml_tag) if xml_tag.name().as_ref() == b"office:settings" => {
book.config = Detach::new(read_office_settings(bs, &mut xml)?);
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_settings", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(())
}
fn read_office_settings(
bs: &mut BufStack,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<Config, OdsError> {
let mut config = Config::new();
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_office_settings {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) if xml_tag.name().as_ref() == b"config:config-item-set" => {
let (name, set) = read_config_item_set(bs, xml_tag, xml)?;
config.insert(name, set);
}
Event::End(ref e) if e.name().as_ref() == b"office:settings" => {
break;
}
Event::Eof => break,
_ => {
dump_unused2("read_office_settings", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok(config)
}
fn read_config_item_set(
bs: &mut BufStack,
xml_tag: &BytesStart<'_>,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(String, ConfigItem), OdsError> {
let mut name = None;
let mut config_set = ConfigItem::new_set();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"config:name" => {
name = Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused("read_config_item_set", xml_tag.name().as_ref(), &attr)?;
}
}
}
let name = if let Some(name) = name {
name
} else {
return Err(OdsError::Ods("config-item-set without name".to_string()));
};
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_office_item_set {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) if xml_tag.name().as_ref() == b"config:config-item" => {
let (name, val) = read_config_item(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::Start(ref xml_tag) if xml_tag.name().as_ref() == b"config:config-item-set" => {
let (name, val) = read_config_item_set(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::Start(ref xml_tag)
if xml_tag.name().as_ref() == b"config:config-item-map-indexed" =>
{
let (name, val) = read_config_item_map_indexed(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::Start(ref xml_tag)
if xml_tag.name().as_ref() == b"config:config-item-map-named" =>
{
let (name, val) = read_config_item_map_named(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::End(ref e) if e.name().as_ref() == b"config:config-item-set" => {
break;
}
Event::Eof => break,
_ => {
dump_unused2("read_config_item_set", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok((name, config_set))
}
fn read_config_item_map_indexed(
bs: &mut BufStack,
xml_tag: &BytesStart<'_>,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(String, ConfigItem), OdsError> {
let mut name = None;
let mut config_vec = ConfigItem::new_vec();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"config:name" => {
name = Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused(
"read_config_item_map_indexed",
xml_tag.name().as_ref(),
&attr,
)?;
}
}
}
let name = if let Some(name) = name {
name
} else {
return Err(OdsError::Ods(
"config-item-map-indexed without name".to_string(),
));
};
let mut index = 0;
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_office_item_set {:?}", evt);
}
match evt {
Event::Start(ref xml_tag)
if xml_tag.name().as_ref() == b"config:config-item-map-entry" =>
{
let (_, entry) = read_config_item_map_entry(bs, xml_tag, xml)?;
config_vec.insert(index.to_string(), entry);
index += 1;
}
Event::End(ref e) if e.name().as_ref() == b"config:config-item-map-indexed" => {
break;
}
Event::Eof => break,
_ => {
dump_unused2("read_config_item_map_indexed", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok((name, config_vec))
}
fn read_config_item_map_named(
bs: &mut BufStack,
xml_tag: &BytesStart<'_>,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(String, ConfigItem), OdsError> {
let mut name = None;
let mut config_map = ConfigItem::new_map();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"config:name" => {
name = Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused("read_config_item_map_named", xml_tag.name().as_ref(), &attr)?;
}
}
}
let name = if let Some(name) = name {
name
} else {
return Err(OdsError::Ods(
"config-item-map-named without name".to_string(),
));
};
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_config_item_map_named {:?}", evt);
}
match evt {
Event::Start(ref xml_tag)
if xml_tag.name().as_ref() == b"config:config-item-map-entry" =>
{
let (name, entry) = read_config_item_map_entry(bs, xml_tag, xml)?;
let name = if let Some(name) = name {
name
} else {
return Err(OdsError::Ods(
"config-item-map-entry without name".to_string(),
));
};
config_map.insert(name, entry);
}
Event::End(ref e) if e.name().as_ref() == b"config:config-item-map-named" => {
break;
}
Event::Eof => break,
_ => {
dump_unused2("read_config_item_map_named", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok((name, config_map))
}
fn read_config_item_map_entry(
bs: &mut BufStack,
xml_tag: &BytesStart<'_>,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(Option<String>, ConfigItem), OdsError> {
let mut name = None;
let mut config_set = ConfigItem::new_entry();
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"config:name" => {
name = Some(attr.unescape_value()?.to_string());
}
attr => {
dump_unused("read_config_item_map_entry", xml_tag.name().as_ref(), &attr)?;
}
}
}
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_config_item_map_entry {:?}", evt);
}
match evt {
Event::Start(ref xml_tag) if xml_tag.name().as_ref() == b"config:config-item" => {
let (name, val) = read_config_item(bs, xml_tag, xml)?;
config_set.insert(name, ConfigItem::from(val));
}
Event::Start(ref xml_tag) if xml_tag.name().as_ref() == b"config:config-item-set" => {
let (name, val) = read_config_item_set(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::Start(ref xml_tag)
if xml_tag.name().as_ref() == b"config:config-item-map-indexed" =>
{
let (name, val) = read_config_item_map_indexed(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::Start(ref xml_tag)
if xml_tag.name().as_ref() == b"config:config-item-map-named" =>
{
let (name, val) = read_config_item_map_named(bs, xml_tag, xml)?;
config_set.insert(name, val);
}
Event::End(ref e) if e.name().as_ref() == b"config:config-item-map-entry" => {
break;
}
Event::Eof => break,
_ => {
dump_unused2("read_config_item_map_entry", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
Ok((name, config_set))
}
fn read_config_item(
bs: &mut BufStack,
xml_tag: &BytesStart<'_>,
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
) -> Result<(String, ConfigValue), OdsError> {
#[derive(PartialEq)]
enum ConfigValueType {
None,
Base64Binary,
Boolean,
DateTime,
Double,
Int,
Long,
Short,
String,
}
let mut name = None;
let mut val_type = ConfigValueType::None;
let mut config_val = None;
for attr in xml_tag.attributes().with_checks(false) {
match attr? {
attr if attr.key.as_ref() == b"config:name" => {
name = Some(attr.unescape_value()?.to_string());
}
attr if attr.key.as_ref() == b"config:type" => {
val_type = match attr.value.as_ref() {
b"base64Binary" => ConfigValueType::Base64Binary,
b"boolean" => ConfigValueType::Boolean,
b"datetime" => ConfigValueType::DateTime,
b"double" => ConfigValueType::Double,
b"int" => ConfigValueType::Int,
b"long" => ConfigValueType::Long,
b"short" => ConfigValueType::Short,
b"string" => ConfigValueType::String,
x => {
return Err(OdsError::Ods(format!(
"unknown config:type {}",
from_utf8(x)?
)));
}
};
}
attr => {
dump_unused("read_config_item", xml_tag.name().as_ref(), &attr)?;
}
}
}
let name = if let Some(name) = name {
name
} else {
return Err(OdsError::Ods(
"config value without config:name".to_string(),
));
};
if val_type == ConfigValueType::None {
return Err(OdsError::Ods(
"config value without config:type".to_string(),
));
};
let mut value = bs.get_buf();
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
match evt {
Event::Text(ref txt) => {
value.write_all(txt.unescape()?.as_bytes())?;
}
Event::End(ref e) if e.name().as_ref() == b"config:config-item" => {
let value = <Cow<'_, [u8]> as From<&Vec<u8>>>::from(value.as_ref());
match val_type {
ConfigValueType::None => {}
ConfigValueType::Base64Binary => {
config_val =
Some(ConfigValue::Base64Binary(from_utf8(&value)?.to_string()));
}
ConfigValueType::Boolean => {
config_val = Some(ConfigValue::Boolean(parse_bool(&value)?));
}
ConfigValueType::DateTime => {
config_val = Some(ConfigValue::DateTime(parse_datetime(&value)?));
}
ConfigValueType::Double => {
config_val = Some(ConfigValue::Double(parse_f64(&value)?));
}
ConfigValueType::Int => {
config_val = Some(ConfigValue::Int(parse_i32(&value)?));
}
ConfigValueType::Long => {
config_val = Some(ConfigValue::Long(parse_i64(&value)?));
}
ConfigValueType::Short => {
config_val = Some(ConfigValue::Short(parse_i16(&value)?));
}
ConfigValueType::String => {
config_val =
Some(ConfigValue::String(from_utf8(value.as_ref())?.to_string()));
}
}
break;
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_config_item", &evt)?;
}
}
if DUMP_XML {
println!(" read_config_item {:?}", evt);
}
buf.clear();
}
bs.push(buf);
bs.push(value);
let config_val = if let Some(config_val) = config_val {
config_val
} else {
return Err(OdsError::Ods("config-item without value???".to_string()));
};
Ok((name, config_val))
}
fn read_xml(
bs: &mut BufStack,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<XmlTag, OdsError> {
let mut stack = Vec::new();
let mut tag = XmlTag::new(xml.decoder().decode(xml_tag.name().as_ref())?);
copy_attr2(tag.attrmap_mut(), xml_tag)?;
stack.push(tag);
if !empty_tag {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_xml {:?}", evt);
}
match evt {
Event::Start(xmlbytes) => {
let mut tag = XmlTag::new(xml.decoder().decode(xmlbytes.name().as_ref())?);
copy_attr2(tag.attrmap_mut(), &xmlbytes)?;
stack.push(tag);
}
Event::End(xmlbytes) => {
if xmlbytes.name().as_ref() == end_tag {
break;
} else {
let tag = stack.pop().unwrap();
if let Some(parent) = stack.last_mut() {
parent.add_tag(tag);
} else {
unreachable!()
}
}
}
Event::Empty(xmlbytes) => {
let mut emptytag = XmlTag::new(xml.decoder().decode(xmlbytes.name().as_ref())?);
copy_attr2(emptytag.attrmap_mut(), &xmlbytes)?;
if let Some(parent) = stack.last_mut() {
parent.add_tag(emptytag);
} else {
unreachable!()
}
}
Event::Text(xmlbytes) => {
if let Some(parent) = stack.last_mut() {
parent.add_text(
xml.decoder()
.decode(xmlbytes.unescape().unwrap().as_bytes())
.unwrap(),
);
} else {
unreachable!()
}
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_xml", &evt)?;
}
}
buf.clear();
}
bs.push(buf);
}
assert_eq!(stack.len(), 1);
Ok(stack.pop().unwrap())
}
fn read_text_or_tag(
bs: &mut BufStack,
end_tag: &[u8],
xml: &mut quick_xml::Reader<BufReader<&mut ZipFile<'_>>>,
xml_tag: &BytesStart<'_>,
empty_tag: bool,
) -> Result<TextContent2, OdsError> {
let mut stack = Vec::new();
let mut cellcontent = TextContent2::Empty;
let create_toplevel = |t: Option<String>| -> Result<XmlTag, OdsError> {
let mut toplevel = XmlTag::new(from_utf8(xml_tag.name().as_ref())?);
copy_attr2(toplevel.attrmap_mut(), xml_tag)?;
if let Some(t) = t {
toplevel.add_text(t);
}
Ok(toplevel)
};
if !empty_tag {
let mut buf = bs.get_buf();
loop {
let evt = xml.read_event_into(&mut buf)?;
if DUMP_XML {
println!(" read_xml {:?}", evt);
}
match evt {
Event::Text(xmlbytes) => {
let v = xmlbytes.unescape()?;
cellcontent = match cellcontent {
TextContent2::Empty => {
TextContent2::Text(v.to_string())
}
TextContent2::Text(mut old_txt) => {
old_txt.push_str(&v);
TextContent2::Text(old_txt)
}
TextContent2::Xml(mut xml) => {
xml.add_text(v);
TextContent2::Xml(xml)
}
TextContent2::XmlVec(_) => {
unreachable!()
}
};
}
Event::Start(xmlbytes) => {
match cellcontent {
TextContent2::Empty => {
stack.push(create_toplevel(None)?);
}
TextContent2::Text(old_txt) => {
stack.push(create_toplevel(Some(old_txt))?);
}
TextContent2::Xml(parent) => {
stack.push(parent);
}
TextContent2::XmlVec(_) => {
unreachable!()
}
}
let mut new_tag = XmlTag::new(xml.decoder().decode(xmlbytes.name().as_ref())?);
copy_attr2(new_tag.attrmap_mut(), &xmlbytes)?;
cellcontent = TextContent2::Xml(new_tag)
}
Event::End(xmlbytes) => {
if xmlbytes.name().as_ref() == end_tag {
if !stack.is_empty() {
return Err(OdsError::Xml(quick_xml::Error::UnexpectedToken(format!("XML corrupted. Endtag {} occured before all elements are closed: {:?}", from_utf8(end_tag)?, stack))));
}
break;
}
cellcontent = match cellcontent {
TextContent2::Empty | TextContent2::Text(_) => {
return Err(OdsError::Xml(quick_xml::Error::UnexpectedToken(format!(
"XML corrupted. Endtag {} occured without start tag",
from_utf8(xmlbytes.name().as_ref())?
))));
}
TextContent2::Xml(tag) => {
if let Some(mut parent) = stack.pop() {
parent.add_tag(tag);
TextContent2::Xml(parent)
} else {
return Err(OdsError::Xml(quick_xml::Error::UnexpectedToken(
format!(
"XML corrupted. Endtag {} occured without start tag",
from_utf8(xmlbytes.name().as_ref())?
),
)));
}
}
TextContent2::XmlVec(_) => {
unreachable!()
}
}
}
Event::Empty(xmlbytes) => {
match cellcontent {
TextContent2::Empty => {
stack.push(create_toplevel(None)?);
}
TextContent2::Text(txt) => {
stack.push(create_toplevel(Some(txt))?);
}
TextContent2::Xml(parent) => {
stack.push(parent);
}
TextContent2::XmlVec(_) => {
unreachable!()
}
}
if let Some(mut parent) = stack.pop() {
let mut emptytag =
XmlTag::new(xml.decoder().decode(xmlbytes.name().as_ref())?);
copy_attr2(emptytag.attrmap_mut(), &xmlbytes)?;
parent.add_tag(emptytag);
cellcontent = TextContent2::Xml(parent);
} else {
unreachable!()
}
}
Event::Eof => {
break;
}
_ => {
dump_unused2("read_text_or_tag", &evt)?;
}
}
}
bs.push(buf);
}
Ok(cellcontent)
}
fn dump_unused(func: &str, tag: &[u8], attr: &Attribute<'_>) -> Result<(), OdsError> {
if DUMP_UNUSED {
let tag = from_utf8(tag)?;
let key = from_utf8(attr.key.as_ref())?;
let value = from_utf8(attr.value.as_ref())?;
println!("unused attr: {} {} ({}:{})", func, tag, key, value);
}
Ok(())
}
fn dump_unused2(func: &str, evt: &Event<'_>) -> Result<(), OdsError> {
if DUMP_UNUSED {
println!("unused attr: {} ({:?})", func, evt);
}
Ok(())
}