Skip to main content

spreadsheet_ods/io/
write.rs

1use crate::cell_::CellData;
2use crate::config::{ConfigItem, ConfigItemType, ConfigValue};
3use crate::draw::{Annotation, DrawFrame, DrawFrameContent, DrawImage};
4use crate::error::OdsError;
5use crate::format::{FormatPartType, ValueFormatTrait};
6use crate::io::format::{format_duration2, format_validation_condition};
7use crate::io::xmlwriter::XmlWriter;
8use crate::io::NamespaceMap;
9use crate::manifest::Manifest;
10use crate::metadata::MetaValue;
11use crate::refs::{format_cellranges, CellRange};
12use crate::sheet::Visibility;
13use crate::sheet_::{dedup_colheader, CellDataIter};
14use crate::style::{
15    CellStyle, ColStyle, FontFaceDecl, GraphicStyle, HeaderFooter, MasterPage, MasterPageRef,
16    PageStyle, PageStyleRef, ParagraphStyle, RowStyle, RubyStyle, StyleOrigin, StyleUse,
17    TableStyle, TextStyle,
18};
19use crate::validation::ValidationDisplay;
20use crate::workbook::{EventListener, Script};
21use crate::xmltree::{XmlContent, XmlTag};
22use crate::HashMap;
23use crate::{Length, Sheet, Value, ValueType, WorkBook};
24use std::borrow::Cow;
25use std::cmp::max;
26use std::collections::{BTreeMap, HashSet};
27use std::fs::File;
28use std::io::{BufWriter, Cursor, Seek, Write};
29use std::path::Path;
30use std::{io, mem};
31use zip::write::FileOptions;
32use zip::{CompressionMethod, ZipWriter};
33
34#[cfg(test)]
35mod tests;
36
37type OdsXmlWriter<'a> = XmlWriter<&'a mut dyn Write>;
38
39const DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.f";
40
41#[allow(dead_code)]
42trait SeekWrite: Seek + Write {}
43
44impl<T> SeekWrite for T where T: Seek + Write {}
45
46/// Write options for ods-files.
47#[derive(Debug, Default)]
48pub struct OdsWriteOptions {
49    method: CompressionMethod,
50    level: Option<i64>,
51}
52
53impl OdsWriteOptions {
54    /// Zip compression method
55    pub fn compression_method(mut self, method: CompressionMethod) -> Self {
56        self.method = method;
57        self
58    }
59
60    /// Zip compression level
61    pub fn compression_level(mut self, level: Option<i64>) -> Self {
62        self.level = level;
63        self
64    }
65
66    /// Write the ods to the given writer.
67    pub fn write_ods<T: Write + Seek>(
68        self,
69        book: &mut WorkBook,
70        mut write: T,
71    ) -> Result<(), OdsError> {
72        let w = ZipWriter::new(&mut write);
73
74        write_ods_impl(self, w, book)?;
75
76        Ok(())
77    }
78}
79
80/// Writes the ODS file into a supplied buffer.
81pub fn write_ods_buf_uncompressed(book: &mut WorkBook, buf: Vec<u8>) -> Result<Vec<u8>, OdsError> {
82    let mut cursor = Cursor::new(buf);
83
84    OdsWriteOptions::default()
85        .compression_method(CompressionMethod::Stored)
86        .write_ods(book, &mut cursor)?;
87
88    Ok(cursor.into_inner())
89}
90
91/// Writes the ODS file into a supplied buffer.
92pub fn write_ods_buf(book: &mut WorkBook, buf: Vec<u8>) -> Result<Vec<u8>, OdsError> {
93    let mut cursor = Cursor::new(buf);
94
95    OdsWriteOptions::default()
96        .compression_method(CompressionMethod::Deflated)
97        .write_ods(book, &mut cursor)?;
98
99    Ok(cursor.into_inner())
100}
101
102/// Writes the ODS file to the given Write.
103pub fn write_ods_to<T: Write + Seek>(book: &mut WorkBook, mut write: T) -> Result<(), OdsError> {
104    OdsWriteOptions::default()
105        .compression_method(CompressionMethod::Deflated)
106        .write_ods(book, &mut write)?;
107
108    Ok(())
109}
110
111/// Writes the ODS file.
112pub fn write_ods<P: AsRef<Path>>(book: &mut WorkBook, ods_path: P) -> Result<(), OdsError> {
113    let mut write = BufWriter::new(File::create(ods_path)?);
114
115    OdsWriteOptions::default()
116        .compression_method(CompressionMethod::Deflated)
117        .write_ods(book, &mut write)?;
118
119    write.flush()?;
120
121    Ok(())
122}
123
124/// Writes the FODS file into a supplied buffer.
125pub fn write_fods_buf(book: &mut WorkBook, mut buf: Vec<u8>) -> Result<Vec<u8>, OdsError> {
126    let write: &mut dyn Write = &mut buf;
127
128    write_fods_impl(write, book)?;
129
130    Ok(buf)
131}
132
133/// Writes the FODS file to the given Write.
134pub fn write_fods_to<T: Write + Seek>(book: &mut WorkBook, mut write: T) -> Result<(), OdsError> {
135    let write: &mut dyn Write = &mut write;
136
137    write_fods_impl(write, book)?;
138
139    Ok(())
140}
141
142/// Writes the FODS file.
143pub fn write_fods<P: AsRef<Path>>(book: &mut WorkBook, fods_path: P) -> Result<(), OdsError> {
144    let mut write = BufWriter::new(File::create(fods_path)?);
145    let write: &mut dyn Write = &mut write;
146
147    write_fods_impl(write, book)?;
148
149    Ok(())
150}
151
152/// Writes the ODS file.
153///
154fn write_fods_impl(writer: &mut dyn Write, book: &mut WorkBook) -> Result<(), OdsError> {
155    sanity_checks(book)?;
156    calculations(book)?;
157
158    convert(book)?;
159
160    let mut xml_out = XmlWriter::new(writer).line_break(true);
161    write_fods_content(book, &mut xml_out)?;
162
163    Ok(())
164}
165
166fn convert(book: &mut WorkBook) -> Result<(), OdsError> {
167    for v in book.tablestyles.values_mut() {
168        v.set_origin(StyleOrigin::Content);
169    }
170    for v in book.rowstyles.values_mut() {
171        v.set_origin(StyleOrigin::Content);
172    }
173    for v in book.colstyles.values_mut() {
174        v.set_origin(StyleOrigin::Content);
175    }
176    for v in book.cellstyles.values_mut() {
177        v.set_origin(StyleOrigin::Content);
178    }
179    for v in book.paragraphstyles.values_mut() {
180        v.set_origin(StyleOrigin::Content);
181    }
182    for v in book.textstyles.values_mut() {
183        v.set_origin(StyleOrigin::Content);
184    }
185    for v in book.rubystyles.values_mut() {
186        v.set_origin(StyleOrigin::Content);
187    }
188    for v in book.graphicstyles.values_mut() {
189        v.set_origin(StyleOrigin::Content);
190    }
191
192    for v in book.formats_boolean.values_mut() {
193        v.set_origin(StyleOrigin::Content);
194    }
195    for v in book.formats_number.values_mut() {
196        v.set_origin(StyleOrigin::Content);
197    }
198    for v in book.formats_percentage.values_mut() {
199        v.set_origin(StyleOrigin::Content);
200    }
201    for v in book.formats_currency.values_mut() {
202        v.set_origin(StyleOrigin::Content);
203    }
204    for v in book.formats_text.values_mut() {
205        v.set_origin(StyleOrigin::Content);
206    }
207    for v in book.formats_datetime.values_mut() {
208        v.set_origin(StyleOrigin::Content);
209    }
210    for v in book.formats_timeduration.values_mut() {
211        v.set_origin(StyleOrigin::Content);
212    }
213
214    Ok(())
215}
216
217fn write_fods_content(book: &mut WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
218    let xmlns = book
219        .xmlns
220        .entry("meta.xml".into())
221        .or_insert_with(NamespaceMap::new);
222
223    xmlns.insert_str(
224        "xmlns:office",
225        "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
226    );
227    xmlns.insert_str("xmlns:ooo", "http://openoffice.org/2004/office");
228    xmlns.insert_str(
229        "xmlns:fo",
230        "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
231    );
232    xmlns.insert_str("xmlns:xlink", "http://www.w3.org/1999/xlink");
233    xmlns.insert_str(
234        "xmlns:config",
235        "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
236    );
237    xmlns.insert_str("xmlns:dc", "http://purl.org/dc/elements/1.1/");
238    xmlns.insert_str(
239        "xmlns:meta",
240        "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
241    );
242    xmlns.insert_str(
243        "xmlns:style",
244        "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
245    );
246    xmlns.insert_str(
247        "xmlns:text",
248        "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
249    );
250    xmlns.insert_str("xmlns:rpt", "http://openoffice.org/2005/report");
251    xmlns.insert_str(
252        "xmlns:draw",
253        "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
254    );
255    xmlns.insert_str(
256        "xmlns:dr3d",
257        "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
258    );
259    xmlns.insert_str(
260        "xmlns:svg",
261        "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
262    );
263    xmlns.insert_str(
264        "xmlns:chart",
265        "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
266    );
267    xmlns.insert_str(
268        "xmlns:table",
269        "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
270    );
271    xmlns.insert_str(
272        "xmlns:number",
273        "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
274    );
275    xmlns.insert_str("xmlns:ooow", "http://openoffice.org/2004/writer");
276    xmlns.insert_str("xmlns:oooc", "http://openoffice.org/2004/calc");
277    xmlns.insert_str("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
278    xmlns.insert_str("xmlns:xforms", "http://www.w3.org/2002/xforms");
279    xmlns.insert_str("xmlns:tableooo", "http://openoffice.org/2009/table");
280    xmlns.insert_str(
281        "xmlns:calcext",
282        "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
283    );
284    xmlns.insert_str("xmlns:drawooo", "http://openoffice.org/2010/draw");
285    xmlns.insert_str(
286        "xmlns:loext",
287        "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
288    );
289    xmlns.insert_str(
290        "xmlns:field",
291        "urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
292    );
293    xmlns.insert_str("xmlns:math", "http://www.w3.org/1998/Math/MathML");
294    xmlns.insert_str(
295        "xmlns:form",
296        "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
297    );
298    xmlns.insert_str(
299        "xmlns:script",
300        "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
301    );
302    xmlns.insert_str(
303        "xmlns:formx",
304        "urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
305    );
306    xmlns.insert_str("xmlns:dom", "http://www.w3.org/2001/xml-events");
307    xmlns.insert_str("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
308    xmlns.insert_str("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
309    xmlns.insert_str("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
310    xmlns.insert_str("xmlns:grddl", "http://www.w3.org/2003/g/data-view#");
311    xmlns.insert_str("xmlns:css3t", "http://www.w3.org/TR/css3-text/");
312    xmlns.insert_str(
313        "xmlns:presentation",
314        "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
315    );
316
317    xml_out.dtd("UTF-8")?;
318
319    xml_out.elem("office:document")?;
320    write_xmlns(xmlns, xml_out)?;
321    xml_out.attr_esc("office:version", book.version())?;
322    xml_out.attr_esc(
323        "office:mimetype",
324        "application/vnd.oasis.opendocument.spreadsheet",
325    )?;
326
327    write_office_meta(book, xml_out)?;
328    write_office_settings(book, xml_out)?;
329    write_office_scripts(book, xml_out)?;
330    write_office_font_face_decls(book, StyleOrigin::Content, xml_out)?;
331    write_office_styles(book, StyleOrigin::Content, xml_out)?;
332    write_office_automatic_styles(book, StyleOrigin::Content, xml_out)?;
333    write_office_master_styles(book, xml_out)?;
334    write_office_body(book, xml_out)?;
335
336    xml_out.end_elem("office:document")?;
337
338    xml_out.close()?;
339
340    Ok(())
341}
342
343/// Writes the ODS file.
344///
345fn write_ods_impl<W: Write + Seek>(
346    cfg: OdsWriteOptions,
347    mut zip_writer: ZipWriter<W>,
348    book: &mut WorkBook,
349) -> Result<(), OdsError> {
350    sanity_checks(book)?;
351    calculations(book)?;
352
353    create_manifest(book)?;
354
355    zip_writer.start_file(
356        "mimetype",
357        FileOptions::<()>::default().compression_method(CompressionMethod::Stored),
358    )?;
359    write_ods_mimetype(&mut zip_writer)?;
360
361    zip_writer.add_directory("META-INF", FileOptions::<()>::default())?;
362    zip_writer.start_file(
363        "META-INF/manifest.xml",
364        FileOptions::<()>::default()
365            .compression_method(cfg.method)
366            .compression_level(cfg.level),
367    )?;
368    write_ods_manifest(book, &mut XmlWriter::new(&mut zip_writer))?;
369
370    zip_writer.start_file(
371        "meta.xml",
372        FileOptions::<()>::default()
373            .compression_method(cfg.method)
374            .compression_level(cfg.level),
375    )?;
376    write_ods_metadata(book, &mut XmlWriter::new(&mut zip_writer))?;
377
378    zip_writer.start_file(
379        "settings.xml",
380        FileOptions::<()>::default()
381            .compression_method(cfg.method)
382            .compression_level(cfg.level),
383    )?;
384    write_ods_settings(book, &mut XmlWriter::new(&mut zip_writer))?;
385
386    zip_writer.start_file(
387        "styles.xml",
388        FileOptions::<()>::default()
389            .compression_method(cfg.method)
390            .compression_level(cfg.level),
391    )?;
392    write_ods_styles(book, &mut XmlWriter::new(&mut zip_writer))?;
393
394    zip_writer.start_file(
395        "content.xml",
396        FileOptions::<()>::default()
397            .compression_method(cfg.method)
398            .compression_level(cfg.level),
399    )?;
400    write_ods_content(book, &mut XmlWriter::new(&mut zip_writer))?;
401
402    write_ods_extra(&cfg, &mut zip_writer, book)?;
403
404    zip_writer.finish()?;
405
406    Ok(())
407}
408
409/// Sanity checks.
410fn sanity_checks(book: &mut WorkBook) -> Result<(), OdsError> {
411    if book.sheets.is_empty() {
412        return Err(OdsError::Ods("Workbook contains no sheets.".to_string()));
413    }
414    Ok(())
415}
416
417/// Before write calculations.
418fn calculations(book: &mut WorkBook) -> Result<(), OdsError> {
419    calc_metadata(book)?;
420    calc_config(book)?;
421
422    calc_row_header_styles(book)?;
423    calc_col_header_styles(book)?;
424    calc_col_headers(book)?;
425
426    Ok(())
427}
428
429/// Compacting and normalizing column-headers.
430fn calc_col_headers(book: &mut WorkBook) -> Result<(), OdsError> {
431    for i in 0..book.num_sheets() {
432        let mut sheet = book.detach_sheet(i);
433
434        // deduplicate all col-headers
435        dedup_colheader(&mut sheet)?;
436
437        // resplit along column-groups and header-columns.
438        let mut split_pos = HashSet::new();
439        for grp in &sheet.group_cols {
440            split_pos.insert(grp.from);
441            split_pos.insert(grp.to + 1);
442        }
443        if let Some(header_cols) = &sheet.header_cols {
444            split_pos.insert(header_cols.from);
445            split_pos.insert(header_cols.to + 1);
446        }
447
448        let col_header = mem::take(&mut sheet.col_header);
449        let mut new_col_header = BTreeMap::new();
450
451        for (mut col, mut header) in col_header {
452            let mut cc = col;
453            loop {
454                if cc == col + header.span {
455                    new_col_header.insert(col, header);
456                    break;
457                }
458
459                if split_pos.contains(&cc) {
460                    let new_span = cc - col;
461                    if new_span > 0 {
462                        let mut new_header = header.clone();
463                        new_header.span = new_span;
464                        new_col_header.insert(col, new_header);
465                    }
466
467                    header.span -= new_span;
468                    col = cc;
469                }
470
471                cc += 1;
472            }
473        }
474
475        sheet.col_header = new_col_header;
476
477        book.attach_sheet(sheet);
478    }
479
480    Ok(())
481}
482
483/// Sync row/column styles with row/col header values.
484fn calc_col_header_styles(book: &mut WorkBook) -> Result<(), OdsError> {
485    for i in 0..book.num_sheets() {
486        let mut sheet = book.detach_sheet(i);
487
488        // Set the column widths.
489        for ch in sheet.col_header.values_mut() {
490            // Any non default values?
491            if ch.width != Length::Default && ch.style.is_none() {
492                let colstyle = book.add_colstyle(ColStyle::new_empty());
493                ch.style = Some(colstyle);
494            }
495
496            // Write back to the style.
497            if let Some(style_name) = ch.style.as_ref() {
498                if let Some(style) = book.colstyle_mut(style_name) {
499                    if ch.width == Length::Default {
500                        style.set_use_optimal_col_width(true);
501                        style.set_col_width(Length::Default);
502                    } else {
503                        style.set_col_width(ch.width);
504                    }
505                }
506            }
507        }
508
509        book.attach_sheet(sheet);
510    }
511
512    Ok(())
513}
514
515/// Sync row/column styles with row/col header values.
516fn calc_row_header_styles(book: &mut WorkBook) -> Result<(), OdsError> {
517    for i in 0..book.num_sheets() {
518        let mut sheet = book.detach_sheet(i);
519
520        for rh in sheet.row_header.values_mut() {
521            if rh.height != Length::Default && rh.style.is_none() {
522                let rowstyle = book.add_rowstyle(RowStyle::new_empty());
523                rh.style = Some(rowstyle);
524            }
525
526            if let Some(style_name) = rh.style.as_ref() {
527                if let Some(style) = book.rowstyle_mut(style_name) {
528                    if rh.height == Length::Default {
529                        style.set_use_optimal_row_height(true);
530                        style.set_row_height(Length::Default);
531                    } else {
532                        style.set_row_height(rh.height);
533                    }
534                }
535            }
536        }
537
538        book.attach_sheet(sheet);
539    }
540
541    Ok(())
542}
543
544/// Calculate metadata values.
545fn calc_metadata(book: &mut WorkBook) -> Result<(), OdsError> {
546    // Manifest
547    book.metadata.generator = format!("spreadsheet-ods {}", env!("CARGO_PKG_VERSION"));
548    book.metadata.document_statistics.table_count = book.sheets.len() as u32;
549    let mut cell_count = 0;
550    for sheet in book.iter_sheets() {
551        cell_count += sheet.data.len() as u32;
552    }
553    book.metadata.document_statistics.cell_count = cell_count;
554
555    Ok(())
556}
557
558/// - Syncs book.config back to the config tree structure.
559/// - Syncs row-heights and col-widths back to the corresponding styles.
560#[allow(clippy::collapsible_else_if)]
561#[allow(clippy::collapsible_if)]
562fn calc_config(book: &mut WorkBook) -> Result<(), OdsError> {
563    // Config
564    let mut config = book.config.detach(0);
565
566    let bc = config.create_path(&[
567        ("ooo:view-settings", ConfigItemType::Set),
568        ("Views", ConfigItemType::Vec),
569        ("0", ConfigItemType::Entry),
570    ]);
571    if book.config().active_table.is_empty() {
572        book.config_mut().active_table = book.sheet(0).name().clone();
573    }
574    bc.insert("ActiveTable", book.config().active_table.clone());
575    bc.insert("HasSheetTabs", book.config().has_sheet_tabs);
576    bc.insert("ShowGrid", book.config().show_grid);
577    bc.insert("ShowPageBreaks", book.config().show_page_breaks);
578
579    for i in 0..book.num_sheets() {
580        let sheet = book.detach_sheet(i);
581
582        let bc = config.create_path(&[
583            ("ooo:view-settings", ConfigItemType::Set),
584            ("Views", ConfigItemType::Vec),
585            ("0", ConfigItemType::Entry),
586            ("Tables", ConfigItemType::Map),
587            (sheet.name().as_str(), ConfigItemType::Entry),
588        ]);
589
590        bc.insert("CursorPositionX", sheet.config().cursor_x);
591        bc.insert("CursorPositionY", sheet.config().cursor_y);
592        bc.insert("HorizontalSplitMode", sheet.config().hor_split_mode as i16);
593        bc.insert("VerticalSplitMode", sheet.config().vert_split_mode as i16);
594        bc.insert("HorizontalSplitPosition", sheet.config().hor_split_pos);
595        bc.insert("VerticalSplitPosition", sheet.config().vert_split_pos);
596        bc.insert("ActiveSplitRange", sheet.config().active_split_range);
597        bc.insert("PositionLeft", sheet.config().position_left);
598        bc.insert("PositionRight", sheet.config().position_right);
599        bc.insert("PositionTop", sheet.config().position_top);
600        bc.insert("PositionBottom", sheet.config().position_bottom);
601        bc.insert("ZoomType", sheet.config().zoom_type);
602        bc.insert("ZoomValue", sheet.config().zoom_value);
603        bc.insert("PageViewZoomValue", sheet.config().page_view_zoom_value);
604        bc.insert("ShowGrid", sheet.config().show_grid);
605
606        let bc = config.create_path(&[
607            ("ooo:configuration-settings", ConfigItemType::Set),
608            ("ScriptConfiguration", ConfigItemType::Map),
609            (sheet.name().as_str(), ConfigItemType::Entry),
610        ]);
611        // maybe this is not accurate. there seem to be cases where the codename
612        // is not the same as the table name, but I can't find any uses of the
613        // codename anywhere.
614        bc.insert("CodeName", sheet.name().as_str().to_string());
615
616        book.attach_sheet(sheet);
617    }
618
619    book.config.attach(config);
620
621    Ok(())
622}
623
624// Create the standard manifest entries.
625fn create_manifest(book: &mut WorkBook) -> Result<(), OdsError> {
626    if !book.manifest.contains_key("/") {
627        book.add_manifest(Manifest {
628            full_path: "/".to_string(),
629            version: Some(book.version().clone()),
630            media_type: "application/vnd.oasis.opendocument.spreadsheet".to_string(),
631            buffer: None,
632        });
633    }
634    if !book.manifest.contains_key("manifest.rdf") {
635        book.add_manifest(create_manifest_rdf()?);
636    }
637    if !book.manifest.contains_key("styles.xml") {
638        book.add_manifest(Manifest::new("styles.xml", "text/xml"));
639    }
640    if !book.manifest.contains_key("meta.xml") {
641        book.add_manifest(Manifest::new("meta.xml", "text/xml"));
642    }
643    if !book.manifest.contains_key("content.xml") {
644        book.add_manifest(Manifest::new("content.xml", "text/xml"));
645    }
646    if !book.manifest.contains_key("settings.xml") {
647        book.add_manifest(Manifest::new("settings.xml", "text/xml"));
648    }
649
650    Ok(())
651}
652
653fn create_manifest_rdf() -> Result<Manifest, OdsError> {
654    let mut buf = Vec::new();
655    let mut xml_out = XmlWriter::new(&mut buf);
656
657    xml_out.dtd("UTF-8")?;
658    xml_out.elem("rdf:RDF")?;
659    xml_out.attr_str("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")?;
660    xml_out.elem("rdf:Description")?;
661    xml_out.attr_str("rdf:about", "content.xml")?;
662    xml_out.empty("rdf:type")?;
663    xml_out.attr_str(
664        "rdf:resource",
665        "http://docs.oasis-open.org/ns/office/1.2/meta/odf#ContentFile",
666    )?;
667    xml_out.end_elem("rdf:Description")?;
668    xml_out.elem("rdf:Description")?;
669    xml_out.attr_str("rdf:about", "")?;
670    xml_out.empty("ns0:hasPart")?;
671    xml_out.attr_str(
672        "xmlns:ns0",
673        "http://docs.oasis-open.org/ns/office/1.2/meta/pkg#",
674    )?;
675    xml_out.attr_str("rdf:resource", "content.xml")?;
676    xml_out.end_elem("rdf:Description")?;
677    xml_out.elem("rdf:Description")?;
678    xml_out.attr_str("rdf:about", "")?;
679    xml_out.empty("rdf:type")?;
680    xml_out.attr_str(
681        "rdf:resource",
682        "http://docs.oasis-open.org/ns/office/1.2/meta/pkg#Document",
683    )?;
684    xml_out.end_elem("rdf:Description")?;
685    xml_out.end_elem("rdf:RDF")?;
686    xml_out.close()?;
687
688    Ok(Manifest::with_buf(
689        "manifest.rdf",
690        "application/rdf+xml",
691        buf,
692    ))
693}
694
695fn write_ods_mimetype(write: &'_ mut dyn Write) -> Result<(), io::Error> {
696    write.write_all("application/vnd.oasis.opendocument.spreadsheet".as_bytes())?;
697    Ok(())
698}
699
700fn write_ods_manifest(book: &WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
701    xml_out.dtd("UTF-8")?;
702
703    xml_out.elem("manifest:manifest")?;
704    xml_out.attr_str(
705        "xmlns:manifest",
706        "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
707    )?;
708    xml_out.attr_esc("manifest:version", &book.version())?;
709
710    for manifest in book.manifest.values() {
711        xml_out.empty("manifest:file-entry")?;
712        xml_out.attr_esc("manifest:full-path", &manifest.full_path)?;
713        if let Some(version) = &manifest.version {
714            xml_out.attr_esc("manifest:version", version)?;
715        }
716        xml_out.attr_esc("manifest:media-type", &manifest.media_type)?;
717    }
718
719    xml_out.end_elem("manifest:manifest")?;
720
721    xml_out.close()?;
722
723    Ok(())
724}
725
726fn write_xmlns(xmlns: &NamespaceMap, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
727    for (k, v) in xmlns.entries() {
728        match k {
729            Cow::Borrowed(k) => {
730                xml_out.attr(k, v.as_ref())?;
731            }
732            Cow::Owned(k) => {
733                xml_out.attr_esc(k, v.as_ref())?;
734            }
735        }
736    }
737    Ok(())
738}
739
740fn write_ods_metadata(book: &mut WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
741    let xmlns = book
742        .xmlns
743        .entry("meta.xml".into())
744        .or_insert_with(NamespaceMap::new);
745
746    xmlns.insert_str(
747        "xmlns:meta",
748        "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
749    );
750    xmlns.insert_str(
751        "xmlns:office",
752        "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
753    );
754
755    xml_out.dtd("UTF-8")?;
756
757    xml_out.elem("office:document-meta")?;
758    write_xmlns(xmlns, xml_out)?;
759    xml_out.attr_esc("office:version", book.version())?;
760
761    write_office_meta(book, xml_out)?;
762
763    xml_out.end_elem("office:document-meta")?;
764
765    xml_out.close()?;
766
767    Ok(())
768}
769
770fn write_office_meta(book: &WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
771    xml_out.elem("office:meta")?;
772
773    xml_out.elem_text("meta:generator", &book.metadata.generator)?;
774    if !book.metadata.title.is_empty() {
775        xml_out.elem_text_esc("dc:title", &book.metadata.title)?;
776    }
777    if !book.metadata.description.is_empty() {
778        xml_out.elem_text_esc("dc:description", &book.metadata.description)?;
779    }
780    if !book.metadata.subject.is_empty() {
781        xml_out.elem_text_esc("dc:subject", &book.metadata.subject)?;
782    }
783    if !book.metadata.keyword.is_empty() {
784        xml_out.elem_text_esc("meta:keyword", &book.metadata.keyword)?;
785    }
786    if !book.metadata.initial_creator.is_empty() {
787        xml_out.elem_text_esc("meta:initial-creator", &book.metadata.initial_creator)?;
788    }
789    if !book.metadata.creator.is_empty() {
790        xml_out.elem_text_esc("dc:creator", &book.metadata.creator)?;
791    }
792    if !book.metadata.printed_by.is_empty() {
793        xml_out.elem_text_esc("meta:printed-by", &book.metadata.printed_by)?;
794    }
795    if let Some(v) = book.metadata.creation_date {
796        xml_out.elem_text("meta:creation-date", &v.format(DATETIME_FORMAT))?;
797    }
798    if let Some(v) = book.metadata.date {
799        xml_out.elem_text("dc:date", &v.format(DATETIME_FORMAT))?;
800    }
801    if let Some(v) = book.metadata.print_date {
802        xml_out.elem_text("meta:print-date", &v.format(DATETIME_FORMAT))?;
803    }
804    if !book.metadata.language.is_empty() {
805        xml_out.elem_text_esc("dc:language", &book.metadata.language)?;
806    }
807    if book.metadata.editing_cycles > 0 {
808        xml_out.elem_text("meta:editing-cycles", &book.metadata.editing_cycles)?;
809    }
810    if book.metadata.editing_duration.num_seconds() > 0 {
811        xml_out.elem_text(
812            "meta:editing-duration",
813            &format_duration2(book.metadata.editing_duration),
814        )?;
815    }
816
817    if !book.metadata.template.is_empty() {
818        xml_out.empty("meta:template")?;
819        if let Some(v) = book.metadata.template.date {
820            xml_out.attr("meta:date", &v.format(DATETIME_FORMAT))?;
821        }
822        if let Some(v) = book.metadata.template.actuate {
823            xml_out.attr("xlink:actuate", &v)?;
824        }
825        if let Some(v) = &book.metadata.template.href {
826            xml_out.attr_esc("xlink:href", v)?;
827        }
828        if let Some(v) = &book.metadata.template.title {
829            xml_out.attr_esc("xlink:title", v)?;
830        }
831        if let Some(v) = book.metadata.template.link_type {
832            xml_out.attr("xlink:type", &v)?;
833        }
834    }
835
836    if !book.metadata.auto_reload.is_empty() {
837        xml_out.empty("meta:auto_reload")?;
838        if let Some(v) = book.metadata.auto_reload.delay {
839            xml_out.attr("meta:delay", &format_duration2(v))?;
840        }
841        if let Some(v) = book.metadata.auto_reload.actuate {
842            xml_out.attr("xlink:actuate", &v)?;
843        }
844        if let Some(v) = &book.metadata.auto_reload.href {
845            xml_out.attr_esc("xlink:href", v)?;
846        }
847        if let Some(v) = &book.metadata.auto_reload.show {
848            xml_out.attr("xlink:show", v)?;
849        }
850        if let Some(v) = book.metadata.auto_reload.link_type {
851            xml_out.attr("xlink:type", &v)?;
852        }
853    }
854
855    if !book.metadata.hyperlink_behaviour.is_empty() {
856        xml_out.empty("meta:hyperlink-behaviour")?;
857        if let Some(v) = &book.metadata.hyperlink_behaviour.target_frame_name {
858            xml_out.attr_esc("office:target-frame-name", v)?;
859        }
860        if let Some(v) = &book.metadata.hyperlink_behaviour.show {
861            xml_out.attr("xlink:show", v)?;
862        }
863    }
864
865    xml_out.empty("meta:document-statistic")?;
866    xml_out.attr(
867        "meta:table-count",
868        &book.metadata.document_statistics.table_count,
869    )?;
870    xml_out.attr(
871        "meta:cell-count",
872        &book.metadata.document_statistics.cell_count,
873    )?;
874    xml_out.attr(
875        "meta:object-count",
876        &book.metadata.document_statistics.object_count,
877    )?;
878    xml_out.attr(
879        "meta:ole-object-count",
880        &book.metadata.document_statistics.ole_object_count,
881    )?;
882
883    for userdef in &book.metadata.user_defined {
884        xml_out.elem("meta:user-defined")?;
885        xml_out.attr("meta:name", &userdef.name)?;
886        if !matches!(userdef.value, MetaValue::String(_)) {
887            xml_out.attr_str(
888                "meta:value-type",
889                match &userdef.value {
890                    MetaValue::Boolean(_) => "boolean",
891                    MetaValue::Datetime(_) => "date",
892                    MetaValue::Float(_) => "float",
893                    MetaValue::TimeDuration(_) => "time",
894                    MetaValue::String(_) => unreachable!(),
895                },
896            )?;
897        }
898        match &userdef.value {
899            MetaValue::Boolean(v) => xml_out.text_str(if *v { "true" } else { "false" })?,
900            MetaValue::Datetime(v) => xml_out.text(&v.format(DATETIME_FORMAT))?,
901            MetaValue::Float(v) => xml_out.text(&v)?,
902            MetaValue::TimeDuration(v) => xml_out.text(&format_duration2(*v))?,
903            MetaValue::String(v) => xml_out.text_esc(v)?,
904        }
905        xml_out.end_elem("meta:user-defined")?;
906    }
907
908    xml_out.end_elem("office:meta")?;
909    Ok(())
910}
911
912fn write_ods_settings(book: &mut WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
913    let xmlns = book
914        .xmlns
915        .entry("settings.xml".into())
916        .or_insert_with(NamespaceMap::new);
917
918    xmlns.insert_str(
919        "xmlns:office",
920        "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
921    );
922    xmlns.insert_str("xmlns:ooo", "http://openoffice.org/2004/office");
923    xmlns.insert_str(
924        "xmlns:config",
925        "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
926    );
927
928    xml_out.dtd("UTF-8")?;
929
930    xml_out.elem("office:document-settings")?;
931    write_xmlns(xmlns, xml_out)?;
932    xml_out.attr_esc("office:version", book.version())?;
933
934    write_office_settings(book, xml_out)?;
935
936    xml_out.end_elem("office:document-settings")?;
937
938    xml_out.close()?;
939
940    Ok(())
941}
942
943fn write_office_settings(book: &WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
944    xml_out.elem("office:settings")?;
945
946    for (name, item) in book.config.iter() {
947        match item {
948            ConfigItem::Value(_) => {
949                panic!("office-settings must not contain config-item");
950            }
951            ConfigItem::Set(_) => write_config_item_set(name, item, xml_out)?,
952            ConfigItem::Vec(_) => {
953                panic!("office-settings must not contain config-item-map-index")
954            }
955            ConfigItem::Map(_) => {
956                panic!("office-settings must not contain config-item-map-named")
957            }
958            ConfigItem::Entry(_) => {
959                panic!("office-settings must not contain config-item-map-entry")
960            }
961        }
962    }
963
964    xml_out.end_elem("office:settings")?;
965    Ok(())
966}
967
968fn write_config_item_set(
969    name: &str,
970    set: &ConfigItem,
971    xml_out: &mut OdsXmlWriter<'_>,
972) -> Result<(), OdsError> {
973    xml_out.elem("config:config-item-set")?;
974    xml_out.attr_esc("config:name", name)?;
975
976    for (name, item) in set.iter() {
977        match item {
978            ConfigItem::Value(value) => write_config_item(name, value, xml_out)?,
979            ConfigItem::Set(_) => write_config_item_set(name, item, xml_out)?,
980            ConfigItem::Vec(_) => write_config_item_map_indexed(name, item, xml_out)?,
981            ConfigItem::Map(_) => write_config_item_map_named(name, item, xml_out)?,
982            ConfigItem::Entry(_) => {
983                panic!("config-item-set must not contain config-item-map-entry")
984            }
985        }
986    }
987
988    xml_out.end_elem("config:config-item-set")?;
989
990    Ok(())
991}
992
993fn write_config_item_map_indexed(
994    name: &str,
995    vec: &ConfigItem,
996    xml_out: &mut OdsXmlWriter<'_>,
997) -> Result<(), OdsError> {
998    xml_out.elem("config:config-item-map-indexed")?;
999    xml_out.attr_esc("config:name", name)?;
1000
1001    let mut index = 0;
1002    loop {
1003        let index_str = index.to_string();
1004        if let Some(item) = vec.get(&index_str) {
1005            match item {
1006                ConfigItem::Value(value) => write_config_item(name, value, xml_out)?,
1007                ConfigItem::Set(_) => {
1008                    panic!("config-item-map-index must not contain config-item-set")
1009                }
1010                ConfigItem::Vec(_) => {
1011                    panic!("config-item-map-index must not contain config-item-map-index")
1012                }
1013                ConfigItem::Map(_) => {
1014                    panic!("config-item-map-index must not contain config-item-map-named")
1015                }
1016                ConfigItem::Entry(_) => write_config_item_map_entry(None, item, xml_out)?,
1017            }
1018        } else {
1019            break;
1020        }
1021
1022        index += 1;
1023    }
1024
1025    xml_out.end_elem("config:config-item-map-indexed")?;
1026
1027    Ok(())
1028}
1029
1030fn write_config_item_map_named(
1031    name: &str,
1032    map: &ConfigItem,
1033    xml_out: &mut OdsXmlWriter<'_>,
1034) -> Result<(), OdsError> {
1035    xml_out.elem("config:config-item-map-named")?;
1036    xml_out.attr_esc("config:name", name)?;
1037
1038    for (name, item) in map.iter() {
1039        match item {
1040            ConfigItem::Value(value) => write_config_item(name, value, xml_out)?,
1041            ConfigItem::Set(_) => {
1042                panic!("config-item-map-index must not contain config-item-set")
1043            }
1044            ConfigItem::Vec(_) => {
1045                panic!("config-item-map-index must not contain config-item-map-index")
1046            }
1047            ConfigItem::Map(_) => {
1048                panic!("config-item-map-index must not contain config-item-map-named")
1049            }
1050            ConfigItem::Entry(_) => write_config_item_map_entry(Some(name), item, xml_out)?,
1051        }
1052    }
1053
1054    xml_out.end_elem("config:config-item-map-named")?;
1055
1056    Ok(())
1057}
1058
1059fn write_config_item_map_entry(
1060    name: Option<&String>,
1061    map_entry: &ConfigItem,
1062    xml_out: &mut OdsXmlWriter<'_>,
1063) -> Result<(), OdsError> {
1064    xml_out.elem("config:config-item-map-entry")?;
1065    if let Some(name) = name {
1066        xml_out.attr_esc("config:name", name)?;
1067    }
1068
1069    for (name, item) in map_entry.iter() {
1070        match item {
1071            ConfigItem::Value(value) => write_config_item(name, value, xml_out)?,
1072            ConfigItem::Set(_) => write_config_item_set(name, item, xml_out)?,
1073            ConfigItem::Vec(_) => write_config_item_map_indexed(name, item, xml_out)?,
1074            ConfigItem::Map(_) => write_config_item_map_named(name, item, xml_out)?,
1075            ConfigItem::Entry(_) => {
1076                panic!("config:config-item-map-entry must not contain config-item-map-entry")
1077            }
1078        }
1079    }
1080
1081    xml_out.end_elem("config:config-item-map-entry")?;
1082
1083    Ok(())
1084}
1085
1086fn write_config_item(
1087    name: &str,
1088    value: &ConfigValue,
1089    xml_out: &mut OdsXmlWriter<'_>,
1090) -> Result<(), OdsError> {
1091    let is_empty = match value {
1092        ConfigValue::Base64Binary(t) => t.is_empty(),
1093        ConfigValue::String(t) => t.is_empty(),
1094        _ => false,
1095    };
1096
1097    xml_out.elem_if(!is_empty, "config:config-item")?;
1098
1099    xml_out.attr_esc("config:name", name)?;
1100
1101    match value {
1102        ConfigValue::Base64Binary(v) => {
1103            xml_out.attr_str("config:type", "base64Binary")?;
1104            xml_out.text(v)?;
1105        }
1106        ConfigValue::Boolean(v) => {
1107            xml_out.attr_str("config:type", "boolean")?;
1108            xml_out.text(&v)?;
1109        }
1110        ConfigValue::DateTime(v) => {
1111            xml_out.attr_str("config:type", "datetime")?;
1112            xml_out.text(&v.format(DATETIME_FORMAT))?;
1113        }
1114        ConfigValue::Double(v) => {
1115            xml_out.attr_str("config:type", "double")?;
1116            xml_out.text(&v)?;
1117        }
1118        ConfigValue::Int(v) => {
1119            xml_out.attr_str("config:type", "int")?;
1120            xml_out.text(&v)?;
1121        }
1122        ConfigValue::Long(v) => {
1123            xml_out.attr_str("config:type", "long")?;
1124            xml_out.text(&v)?;
1125        }
1126        ConfigValue::Short(v) => {
1127            xml_out.attr_str("config:type", "short")?;
1128            xml_out.text(&v)?;
1129        }
1130        ConfigValue::String(v) => {
1131            xml_out.attr_str("config:type", "string")?;
1132            xml_out.text_esc(v)?;
1133        }
1134    }
1135
1136    xml_out.end_elem_if(!is_empty, "config:config-item")?;
1137
1138    Ok(())
1139}
1140
1141fn write_ods_styles(book: &mut WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
1142    let xmlns = book
1143        .xmlns
1144        .entry("styles.xml".into())
1145        .or_insert_with(NamespaceMap::new);
1146
1147    xmlns.insert_str(
1148        "xmlns:meta",
1149        "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
1150    );
1151    xmlns.insert_str(
1152        "xmlns:office",
1153        "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
1154    );
1155    xmlns.insert_str(
1156        "xmlns:fo",
1157        "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
1158    );
1159    xmlns.insert_str("xmlns:ooo", "http://openoffice.org/2004/office");
1160    xmlns.insert_str("xmlns:xlink", "http://www.w3.org/1999/xlink");
1161    xmlns.insert_str("xmlns:dc", "http://purl.org/dc/elements/1.1/");
1162    xmlns.insert_str(
1163        "xmlns:style",
1164        "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
1165    );
1166    xmlns.insert_str(
1167        "xmlns:text",
1168        "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
1169    );
1170    xmlns.insert_str(
1171        "xmlns:dr3d",
1172        "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
1173    );
1174    xmlns.insert_str(
1175        "xmlns:svg",
1176        "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
1177    );
1178    xmlns.insert_str(
1179        "xmlns:chart",
1180        "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
1181    );
1182    xmlns.insert_str("xmlns:rpt", "http://openoffice.org/2005/report");
1183    xmlns.insert_str(
1184        "xmlns:table",
1185        "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
1186    );
1187    xmlns.insert_str(
1188        "xmlns:number",
1189        "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
1190    );
1191    xmlns.insert_str("xmlns:ooow", "http://openoffice.org/2004/writer");
1192    xmlns.insert_str("xmlns:oooc", "http://openoffice.org/2004/calc");
1193    xmlns.insert_str("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
1194    xmlns.insert_str("xmlns:tableooo", "http://openoffice.org/2009/table");
1195    xmlns.insert_str(
1196        "xmlns:calcext",
1197        "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
1198    );
1199    xmlns.insert_str("xmlns:drawooo", "http://openoffice.org/2010/draw");
1200    xmlns.insert_str(
1201        "xmlns:draw",
1202        "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
1203    );
1204    xmlns.insert_str(
1205        "xmlns:loext",
1206        "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
1207    );
1208    xmlns.insert_str(
1209        "xmlns:field",
1210        "urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
1211    );
1212    xmlns.insert_str("xmlns:math", "http://www.w3.org/1998/Math/MathML");
1213    xmlns.insert_str(
1214        "xmlns:form",
1215        "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
1216    );
1217    xmlns.insert_str(
1218        "xmlns:script",
1219        "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
1220    );
1221    xmlns.insert_str("xmlns:dom", "http://www.w3.org/2001/xml-events");
1222    xmlns.insert_str("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
1223    xmlns.insert_str("xmlns:grddl", "http://www.w3.org/2003/g/data-view#");
1224    xmlns.insert_str("xmlns:css3t", "http://www.w3.org/TR/css3-text/");
1225    xmlns.insert_str(
1226        "xmlns:presentation",
1227        "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
1228    );
1229
1230    xml_out.dtd("UTF-8")?;
1231
1232    xml_out.elem("office:document-styles")?;
1233    write_xmlns(xmlns, xml_out)?;
1234    xml_out.attr_esc("office:version", book.version())?;
1235
1236    write_office_font_face_decls(book, StyleOrigin::Styles, xml_out)?;
1237    write_office_styles(book, StyleOrigin::Styles, xml_out)?;
1238    write_office_automatic_styles(book, StyleOrigin::Styles, xml_out)?;
1239    write_office_master_styles(book, xml_out)?;
1240
1241    xml_out.end_elem("office:document-styles")?;
1242
1243    xml_out.close()?;
1244
1245    Ok(())
1246}
1247
1248fn write_ods_content(book: &mut WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
1249    let xmlns = book
1250        .xmlns
1251        .entry("content.xml".into())
1252        .or_insert_with(NamespaceMap::new);
1253
1254    xmlns.insert_str(
1255        "xmlns:meta",
1256        "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
1257    );
1258    xmlns.insert_str(
1259        "xmlns:office",
1260        "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
1261    );
1262    xmlns.insert_str(
1263        "xmlns:fo",
1264        "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
1265    );
1266    xmlns.insert_str("xmlns:ooo", "http://openoffice.org/2004/office");
1267    xmlns.insert_str("xmlns:xlink", "http://www.w3.org/1999/xlink");
1268    xmlns.insert_str("xmlns:dc", "http://purl.org/dc/elements/1.1/");
1269    xmlns.insert_str(
1270        "xmlns:style",
1271        "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
1272    );
1273    xmlns.insert_str(
1274        "xmlns:text",
1275        "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
1276    );
1277    xmlns.insert_str(
1278        "xmlns:draw",
1279        "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
1280    );
1281    xmlns.insert_str(
1282        "xmlns:dr3d",
1283        "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
1284    );
1285    xmlns.insert_str(
1286        "xmlns:svg",
1287        "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
1288    );
1289    xmlns.insert_str(
1290        "xmlns:chart",
1291        "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
1292    );
1293    xmlns.insert_str("xmlns:rpt", "http://openoffice.org/2005/report");
1294    xmlns.insert_str(
1295        "xmlns:table",
1296        "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
1297    );
1298    xmlns.insert_str(
1299        "xmlns:number",
1300        "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
1301    );
1302    xmlns.insert_str("xmlns:ooow", "http://openoffice.org/2004/writer");
1303    xmlns.insert_str("xmlns:oooc", "http://openoffice.org/2004/calc");
1304    xmlns.insert_str("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
1305    xmlns.insert_str("xmlns:tableooo", "http://openoffice.org/2009/table");
1306    xmlns.insert_str(
1307        "xmlns:calcext",
1308        "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
1309    );
1310    xmlns.insert_str("xmlns:drawooo", "http://openoffice.org/2010/draw");
1311    xmlns.insert_str(
1312        "xmlns:loext",
1313        "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
1314    );
1315    xmlns.insert_str(
1316        "xmlns:field",
1317        "urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
1318    );
1319    xmlns.insert_str("xmlns:math", "http://www.w3.org/1998/Math/MathML");
1320    xmlns.insert_str(
1321        "xmlns:form",
1322        "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
1323    );
1324    xmlns.insert_str(
1325        "xmlns:script",
1326        "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
1327    );
1328    xmlns.insert_str("xmlns:dom", "http://www.w3.org/2001/xml-events");
1329    xmlns.insert_str("xmlns:xforms", "http://www.w3.org/2002/xforms");
1330    xmlns.insert_str("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
1331    xmlns.insert_str("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
1332    xmlns.insert_str(
1333        "xmlns:formx",
1334        "urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
1335    );
1336    xmlns.insert_str("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
1337    xmlns.insert_str("xmlns:grddl", "http://www.w3.org/2003/g/data-view#");
1338    xmlns.insert_str("xmlns:css3t", "http://www.w3.org/TR/css3-text/");
1339    xmlns.insert_str(
1340        "xmlns:presentation",
1341        "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
1342    );
1343
1344    xml_out.dtd("UTF-8")?;
1345
1346    xml_out.elem("office:document-content")?;
1347    write_xmlns(xmlns, xml_out)?;
1348    xml_out.attr_esc("office:version", book.version())?;
1349
1350    write_office_scripts(book, xml_out)?;
1351    write_office_font_face_decls(book, StyleOrigin::Content, xml_out)?;
1352    write_office_automatic_styles(book, StyleOrigin::Content, xml_out)?;
1353
1354    write_office_body(book, xml_out)?;
1355
1356    xml_out.end_elem("office:document-content")?;
1357
1358    xml_out.close()?;
1359
1360    Ok(())
1361}
1362
1363fn write_office_body(book: &WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
1364    xml_out.elem("office:body")?;
1365    xml_out.elem("office:spreadsheet")?;
1366
1367    // extra tags. pass through only
1368    for tag in &book.extra {
1369        if tag.name() == "table:calculation-settings"
1370            || tag.name() == "table:label-ranges"
1371            || tag.name() == "table:tracked-changes"
1372            || tag.name() == "text:alphabetical-index-auto-mark-file"
1373            || tag.name() == "text:dde-connection-decls"
1374            || tag.name() == "text:sequence-decls"
1375            || tag.name() == "text:user-field-decls"
1376            || tag.name() == "text:variable-decls"
1377        {
1378            write_xmltag(tag, xml_out)?;
1379        }
1380    }
1381
1382    write_content_validations(book, xml_out)?;
1383
1384    for sheet in &book.sheets {
1385        write_sheet(book, sheet, xml_out)?;
1386    }
1387
1388    // extra tags. pass through only
1389    for tag in &book.extra {
1390        if tag.name() == "table:consolidation"
1391            || tag.name() == "table:data-pilot-tables"
1392            || tag.name() == "table:database-ranges"
1393            || tag.name() == "table:dde-links"
1394            || tag.name() == "table:named-expressions"
1395            || tag.name() == "calcext:conditional-formats"
1396        {
1397            write_xmltag(tag, xml_out)?;
1398        }
1399    }
1400
1401    xml_out.end_elem("office:spreadsheet")?;
1402    xml_out.end_elem("office:body")?;
1403    Ok(())
1404}
1405
1406fn write_office_scripts(book: &WorkBook, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
1407    xml_out.elem_if(!book.scripts.is_empty(), "office:scripts")?;
1408    write_scripts(&book.scripts, xml_out)?;
1409    write_event_listeners(&book.event_listener, xml_out)?;
1410    xml_out.end_elem_if(!book.scripts.is_empty(), "office:scripts")?;
1411    Ok(())
1412}
1413
1414fn write_content_validations(
1415    book: &WorkBook,
1416    xml_out: &mut OdsXmlWriter<'_>,
1417) -> Result<(), OdsError> {
1418    if !book.validations.is_empty() {
1419        xml_out.elem("table:content-validations")?;
1420
1421        for valid in book.validations.values() {
1422            xml_out.elem("table:content-validation")?;
1423            xml_out.attr_esc("table:name", valid.name())?;
1424            xml_out.attr_esc("table:condition", &format_validation_condition(valid))?;
1425            xml_out.attr_str(
1426                "table:allow-empty-cell",
1427                if valid.allow_empty() { "true" } else { "false" },
1428            )?;
1429            xml_out.attr_str(
1430                "table:display-list",
1431                match valid.display() {
1432                    ValidationDisplay::NoDisplay => "no",
1433                    ValidationDisplay::Unsorted => "unsorted",
1434                    ValidationDisplay::SortAscending => "sort-ascending",
1435                },
1436            )?;
1437            xml_out.attr_esc("table:base-cell-address", &valid.base_cell())?;
1438
1439            if let Some(err) = valid.err() {
1440                xml_out.elem_if(err.text().is_some(), "table:error-message")?;
1441                xml_out.attr("table:display", &err.display())?;
1442                xml_out.attr("table:message-type", &err.msg_type())?;
1443                if let Some(title) = err.title() {
1444                    xml_out.attr_esc("table:title", title)?;
1445                }
1446                if let Some(text) = err.text() {
1447                    write_xmltag(text, xml_out)?;
1448                }
1449                xml_out.end_elem_if(err.text().is_some(), "table:error-message")?;
1450            }
1451            if let Some(err) = valid.help() {
1452                xml_out.elem_if(err.text().is_some(), "table:help-message")?;
1453                xml_out.attr("table:display", &err.display())?;
1454                if let Some(title) = err.title() {
1455                    xml_out.attr_esc("table:title", title)?;
1456                }
1457                if let Some(text) = err.text() {
1458                    write_xmltag(text, xml_out)?;
1459                }
1460                xml_out.end_elem_if(err.text().is_some(), "table:help-message")?;
1461            }
1462
1463            xml_out.end_elem("table:content-validation")?;
1464        }
1465        xml_out.end_elem("table:content-validations")?;
1466    }
1467
1468    Ok(())
1469}
1470
1471#[derive(Debug)]
1472struct SplitCols {
1473    col: u32,
1474    col_to: u32,
1475    hidden: bool,
1476}
1477
1478impl SplitCols {
1479    fn repeat(&self) -> u32 {
1480        self.col_to - self.col + 1
1481    }
1482}
1483
1484/// Is the cell (partially) hidden?
1485fn split_hidden(ranges: &[CellRange], row: u32, col: u32, repeat: u32, out: &mut Vec<SplitCols>) {
1486    out.clear();
1487    if repeat == 1 {
1488        if ranges.iter().any(|v| v.contains(row, col)) {
1489            let range = SplitCols {
1490                col,
1491                col_to: col,
1492                hidden: true,
1493            };
1494            out.push(range);
1495        } else {
1496            let range = SplitCols {
1497                col,
1498                col_to: col,
1499                hidden: false,
1500            };
1501            out.push(range);
1502        }
1503    } else {
1504        let ranges: Vec<_> = ranges
1505            .iter()
1506            .filter(|v| row >= v.row() && row <= v.to_row())
1507            .collect();
1508
1509        let mut range: Option<SplitCols> = None;
1510        'col_loop: for c in col..col + repeat {
1511            for r in &ranges {
1512                if c >= r.col() && c <= r.to_col() {
1513                    if let Some(range) = &mut range {
1514                        if range.hidden {
1515                            range.col_to = c;
1516                        } else {
1517                            let v = mem::replace(
1518                                range,
1519                                SplitCols {
1520                                    col: c,
1521                                    col_to: c,
1522                                    hidden: true,
1523                                },
1524                            );
1525                            out.push(v);
1526                        }
1527                    } else {
1528                        range.replace(SplitCols {
1529                            col: c,
1530                            col_to: c,
1531                            hidden: true,
1532                        });
1533                    }
1534
1535                    continue 'col_loop;
1536                }
1537            }
1538            // not hidden
1539            if let Some(range) = &mut range {
1540                if range.hidden {
1541                    let v = mem::replace(
1542                        range,
1543                        SplitCols {
1544                            col: c,
1545                            col_to: c,
1546                            hidden: false,
1547                        },
1548                    );
1549                    out.push(v);
1550                } else {
1551                    range.col_to = c;
1552                }
1553            } else {
1554                range.replace(SplitCols {
1555                    col: c,
1556                    col_to: c,
1557                    hidden: false,
1558                });
1559            }
1560        }
1561
1562        if let Some(range) = range {
1563            out.push(range);
1564        }
1565    }
1566}
1567
1568/// Removes any outlived Ranges from the vector.
1569fn remove_outlived(ranges: &mut Vec<CellRange>, row: u32, col: u32) {
1570    *ranges = ranges
1571        .drain(..)
1572        .filter(|s| !s.out_looped(row, col))
1573        .collect();
1574}
1575
1576fn write_sheet(
1577    book: &WorkBook,
1578    sheet: &Sheet,
1579    xml_out: &mut OdsXmlWriter<'_>,
1580) -> Result<(), OdsError> {
1581    xml_out.elem("table:table")?;
1582    dbg!(&sheet.name);
1583    xml_out.attr_esc("table:name", &sheet.name)?;
1584    if let Some(style) = sheet.style.as_ref() {
1585        xml_out.attr_esc("table:style-name", style.as_str())?;
1586    }
1587    if let Some(print_ranges) = &sheet.print_ranges {
1588        xml_out.attr_esc("table:print-ranges", &format_cellranges(print_ranges))?;
1589    }
1590    if !sheet.print() {
1591        xml_out.attr_str("table:print", "false")?;
1592    }
1593    if !sheet.display() {
1594        xml_out.attr_str("table:display", "false")?;
1595    }
1596
1597    for tag in &sheet.extra {
1598        if tag.name() == "table:title"
1599            || tag.name() == "table:desc"
1600            || tag.name() == "table:table-source"
1601            || tag.name() == "office:dde-source"
1602            || tag.name() == "table:scenario"
1603            || tag.name() == "office:forms"
1604            || tag.name() == "table:shapes"
1605        {
1606            write_xmltag(tag, xml_out)?;
1607        }
1608    }
1609
1610    let max_cell = sheet.used_grid_size();
1611
1612    write_table_columns(sheet, max_cell, xml_out)?;
1613
1614    // list of current spans
1615    let mut spans = Vec::<CellRange>::new();
1616    let mut split = Vec::<SplitCols>::new();
1617
1618    // table-row + table-cell
1619    let mut first_cell = true;
1620    let mut prev_row: u32 = 0;
1621    let mut prev_row_repeat: u32 = 1;
1622    let mut prev_col: u32 = 0;
1623    let mut row_group_count = 0;
1624    let mut row_header = false;
1625
1626    let mut it = CellDataIter::new(sheet.data.range(..));
1627    while let Some(((cur_row, cur_col), cell)) = it.next() {
1628        // Row repeat count.
1629        let cur_row_repeat = if let Some(row_header) = sheet.row_header.get(&cur_row) {
1630            row_header.repeat
1631        } else {
1632            1
1633        };
1634        // Cell repeat count.
1635        let cur_col_repeat = cell.repeat;
1636
1637        // There may be a lot of gaps of any kind in our data.
1638        // In the XML format there is no cell identification, every gap
1639        // must be filled with empty rows/columns. For this we need some
1640        // calculations.
1641
1642        // For the repeat-counter we need to look forward.
1643        let (next_row, next_col, is_last_cell) = //_
1644            if let Some((next_row, next_col)) = it.peek_cell()
1645        {
1646            (next_row, next_col, false)
1647        } else {
1648            (max_cell.0, max_cell.1, true)
1649        };
1650
1651        // Looking forward row-wise.
1652        let forward_delta_row = next_row - cur_row;
1653        // Column deltas are only relevant in the same row, but we need to
1654        // fill up to max used columns.
1655        let forward_delta_col = if forward_delta_row > 0 {
1656            max_cell.1 - cur_col
1657        } else {
1658            next_col - cur_col
1659        };
1660
1661        // Looking backward row-wise.
1662        let backward_delta_row = cur_row - prev_row;
1663        // When a row changes our delta is from zero to cur_col.
1664        let backward_delta_col = if backward_delta_row > 0 {
1665            cur_col
1666        } else {
1667            cur_col - prev_col
1668        };
1669
1670        // After the first cell there is always an open row tag that
1671        // needs to be closed.
1672        if backward_delta_row > 0 && !first_cell {
1673            write_end_prev_row(
1674                sheet,
1675                prev_row,
1676                prev_row_repeat,
1677                &mut row_group_count,
1678                &mut row_header,
1679                xml_out,
1680            )?;
1681        }
1682
1683        // Any empty rows before this one?
1684        if backward_delta_row > 0 {
1685            if backward_delta_row < prev_row_repeat {
1686                return Err(OdsError::Ods(format!(
1687                    "{}: row-repeat of {} for row {} overlaps with the following row.",
1688                    sheet.name, prev_row_repeat, prev_row,
1689                )));
1690            }
1691
1692            // The repeat counter for the empty rows before is reduced by the last
1693            // real repeat.
1694            let mut synth_row_repeat = backward_delta_row - prev_row_repeat;
1695            // At the very beginning last row is 0. But nothing has been written for it.
1696            // To account for this we add one repeat.
1697            if first_cell {
1698                synth_row_repeat += 1;
1699            }
1700            let synth_row = cur_row - synth_row_repeat;
1701
1702            if synth_row_repeat > 0 {
1703                write_empty_rows_before(
1704                    sheet,
1705                    synth_row,
1706                    synth_row_repeat,
1707                    max_cell,
1708                    &mut row_group_count,
1709                    &mut row_header,
1710                    xml_out,
1711                )?;
1712            }
1713        }
1714
1715        // Start a new row if there is a delta or we are at the start.
1716        // Fills in any blank cells before the current cell.
1717        if backward_delta_row > 0 || first_cell {
1718            write_start_current_row(
1719                sheet,
1720                cur_row,
1721                cur_row_repeat,
1722                backward_delta_col,
1723                &mut row_group_count,
1724                &mut row_header,
1725                xml_out,
1726            )?;
1727        }
1728
1729        // Remove no longer usefull cell-spans.
1730        remove_outlived(&mut spans, cur_row, cur_col);
1731
1732        // Split the current cell in visible/hidden ranges.
1733        split_hidden(&spans, cur_row, cur_col, cur_col_repeat, &mut split);
1734
1735        // Maybe span, only if visible. That nicely eliminates all double hides.
1736        // Only check for the start cell in case of repeat.
1737        if let Some(span) = cell.extra.as_ref().map(|v| v.span) {
1738            if !split[0].hidden && (span.row_span > 1 || span.col_span > 1) {
1739                spans.push(CellRange::origin_span(cur_row, cur_col, span.into()));
1740            }
1741        }
1742
1743        // And now to something completely different ...
1744        for s in &split {
1745            write_cell(book, cell, s.hidden, s.repeat(), xml_out)?;
1746        }
1747
1748        // There may be some blank cells until the next one.
1749        if forward_delta_row > 0 {
1750            // Write empty cells to fill up to the max used column.
1751            // If there is some overlap with repeat, it can be ignored.
1752            let synth_delta_col = forward_delta_col.saturating_sub(cur_col_repeat);
1753            if synth_delta_col > 0 {
1754                split_hidden(
1755                    &spans,
1756                    cur_row,
1757                    cur_col + cur_col_repeat,
1758                    synth_delta_col,
1759                    &mut split,
1760                );
1761                for s in &split {
1762                    write_empty_cells(s.hidden, s.repeat(), xml_out)?;
1763                }
1764            }
1765        } else if forward_delta_col > 0 {
1766            // Write empty cells unto the next cell with data.
1767            // Fail on overlap with repeat.
1768            if forward_delta_col < cur_col_repeat {
1769                return Err(OdsError::Ods(format!(
1770                    "{}: col-repeat of {} for row/col {}/{} overlaps with the following cell.",
1771                    sheet.name, cur_col_repeat, cur_row, cur_col,
1772                )));
1773            }
1774            let synth_delta_col = forward_delta_col - cur_col_repeat;
1775            if synth_delta_col > 0 {
1776                split_hidden(
1777                    &spans,
1778                    cur_row,
1779                    cur_col + cur_col_repeat,
1780                    synth_delta_col,
1781                    &mut split,
1782                );
1783                for s in &split {
1784                    write_empty_cells(s.hidden, s.repeat(), xml_out)?;
1785                }
1786            }
1787        }
1788
1789        // The last cell we will write? We can close the last row here,
1790        // where we have all the data.
1791        if is_last_cell {
1792            write_end_last_row(&mut row_group_count, &mut row_header, xml_out)?;
1793        }
1794
1795        first_cell = false;
1796        prev_row = cur_row;
1797        prev_row_repeat = cur_row_repeat;
1798        prev_col = cur_col;
1799    }
1800
1801    xml_out.end_elem("table:table")?;
1802
1803    for tag in &sheet.extra {
1804        if tag.name() == "table:named-expressions" || tag.name() == "calcext:conditional-formats" {
1805            write_xmltag(tag, xml_out)?;
1806        }
1807    }
1808
1809    Ok(())
1810}
1811
1812fn write_empty_cells(
1813    hidden: bool,
1814    repeat: u32,
1815    xml_out: &mut OdsXmlWriter<'_>,
1816) -> Result<(), OdsError> {
1817    if hidden {
1818        xml_out.empty("table:covered-table-cell")?;
1819    } else {
1820        xml_out.empty("table:table-cell")?;
1821    }
1822    if repeat > 1 {
1823        xml_out.attr("table:number-columns-repeated", &repeat)?;
1824    }
1825
1826    Ok(())
1827}
1828
1829fn write_start_current_row(
1830    sheet: &Sheet,
1831    cur_row: u32,
1832    cur_row_repeat: u32,
1833    backward_delta_col: u32,
1834    row_group_count: &mut u32,
1835    row_header: &mut bool,
1836    xml_out: &mut OdsXmlWriter<'_>,
1837) -> Result<(), OdsError> {
1838    // groups
1839    for row_group in &sheet.group_rows {
1840        if row_group.from() == cur_row {
1841            *row_group_count += 1;
1842            xml_out.elem("table:table-row-group")?;
1843            if !row_group.display() {
1844                xml_out.attr_str("table:display", "false")?;
1845            }
1846        }
1847        // extra: if the group would start within our repeat range, it's started
1848        //        at the current row instead.
1849        if row_group.from() > cur_row && row_group.from() < cur_row + cur_row_repeat {
1850            *row_group_count += 1;
1851            xml_out.elem("table:table-row-group")?;
1852            if !row_group.display() {
1853                xml_out.attr_str("table:display", "false")?;
1854            }
1855        }
1856    }
1857
1858    // print-header
1859    if let Some(header_rows) = &sheet.header_rows {
1860        if header_rows.from >= cur_row && header_rows.from < cur_row + cur_row_repeat {
1861            *row_header = true;
1862        }
1863    }
1864    if *row_header {
1865        xml_out.elem("table:table-header-rows")?;
1866    }
1867
1868    // row
1869    xml_out.elem("table:table-row")?;
1870    if let Some(row_header) = sheet.valid_row_header(cur_row) {
1871        if row_header.repeat > 1 {
1872            xml_out.attr_esc("table:number-rows-repeated", &row_header.repeat)?;
1873        }
1874        if let Some(rowstyle) = row_header.style.as_ref() {
1875            xml_out.attr_esc("table:style-name", rowstyle.as_str())?;
1876        }
1877        if let Some(cellstyle) = row_header.cellstyle.as_ref() {
1878            xml_out.attr_esc("table:default-cell-style-name", cellstyle.as_str())?;
1879        }
1880        if row_header.visible != Visibility::Visible {
1881            xml_out.attr_esc("table:visibility", &row_header.visible)?;
1882        }
1883    }
1884
1885    // Might not be the first column in this row.
1886    if backward_delta_col > 0 {
1887        xml_out.empty("table:table-cell")?;
1888        if backward_delta_col > 1 {
1889            xml_out.attr_esc("table:number-columns-repeated", &backward_delta_col)?;
1890        }
1891    }
1892
1893    Ok(())
1894}
1895
1896fn write_end_prev_row(
1897    sheet: &Sheet,
1898    last_r: u32,
1899    last_r_repeat: u32,
1900    row_group_count: &mut u32,
1901    row_header: &mut bool,
1902    xml_out: &mut OdsXmlWriter<'_>,
1903) -> Result<(), OdsError> {
1904    // row
1905    xml_out.end_elem("table:table-row")?;
1906    if *row_header {
1907        xml_out.end_elem("table:table-header-rows")?;
1908    }
1909
1910    // end of the print-header
1911    if let Some(header_rows) = &sheet.header_rows {
1912        if header_rows.to >= last_r && header_rows.to < last_r + last_r_repeat {
1913            *row_header = false;
1914        }
1915    }
1916
1917    // groups
1918    for row_group in &sheet.group_rows {
1919        if row_group.to() == last_r {
1920            *row_group_count -= 1;
1921            xml_out.end_elem("table:table-row-group")?;
1922        }
1923        // the group end is somewhere inside the repeated range.
1924        if row_group.to() > last_r && row_group.to() < last_r + last_r_repeat {
1925            *row_group_count -= 1;
1926            xml_out.end_elem("table:table-row-group")?;
1927        }
1928    }
1929
1930    Ok(())
1931}
1932
1933fn write_end_last_row(
1934    row_group_count: &mut u32,
1935    row_header: &mut bool,
1936    xml_out: &mut OdsXmlWriter<'_>,
1937) -> Result<(), OdsError> {
1938    // row
1939    xml_out.end_elem("table:table-row")?;
1940
1941    // end of the print-header.
1942    // todo: might loose some empty rows?
1943    if *row_header {
1944        *row_header = false;
1945        xml_out.end_elem("table:table-header-rows")?;
1946    }
1947
1948    // close all groups
1949    while *row_group_count > 0 {
1950        *row_group_count -= 1;
1951        xml_out.end_elem("table:table-row-group")?;
1952    }
1953
1954    Ok(())
1955}
1956
1957fn write_empty_rows_before(
1958    sheet: &Sheet,
1959    last_row: u32,
1960    last_row_repeat: u32,
1961    max_cell: (u32, u32),
1962    row_group_count: &mut u32,
1963    row_header: &mut bool,
1964    xml_out: &mut OdsXmlWriter<'_>,
1965) -> Result<(), OdsError> {
1966    // Are there any row groups? Then we don't use repeat but write everything out.
1967    if !sheet.group_rows.is_empty() || sheet.header_rows.is_some() {
1968        for r in last_row..last_row + last_row_repeat {
1969            if *row_header {
1970                xml_out.end_elem("table:table-header-rows")?;
1971            }
1972            // end of the print-header
1973            if let Some(header_rows) = &sheet.header_rows {
1974                if header_rows.to == r {
1975                    *row_header = false;
1976                }
1977            }
1978            // groups
1979            for row_group in &sheet.group_rows {
1980                if row_group.to() == r {
1981                    *row_group_count -= 1;
1982                    xml_out.end_elem("table:table-row-group")?;
1983                }
1984            }
1985            for row_group in &sheet.group_rows {
1986                if row_group.from() == r {
1987                    *row_group_count += 1;
1988                    xml_out.elem("table:table-row-group")?;
1989                    if !row_group.display() {
1990                        xml_out.attr_str("table:display", "false")?;
1991                    }
1992                }
1993            }
1994            // start of print-header
1995            if let Some(header_rows) = &sheet.header_rows {
1996                if header_rows.from == r {
1997                    *row_header = true;
1998                }
1999            }
2000            if *row_header {
2001                xml_out.elem("table:table-header-rows")?;
2002            }
2003            // row
2004            write_empty_row(sheet, r, 1, max_cell, xml_out)?;
2005        }
2006    } else {
2007        write_empty_row(sheet, last_row, last_row_repeat, max_cell, xml_out)?;
2008    }
2009
2010    Ok(())
2011}
2012
2013fn write_empty_row(
2014    sheet: &Sheet,
2015    cur_row: u32,
2016    row_repeat: u32,
2017    max_cell: (u32, u32),
2018    xml_out: &mut OdsXmlWriter<'_>,
2019) -> Result<(), OdsError> {
2020    xml_out.elem("table:table-row")?;
2021    xml_out.attr("table:number-rows-repeated", &row_repeat)?;
2022    if let Some(row_header) = sheet.valid_row_header(cur_row) {
2023        if let Some(rowstyle) = row_header.style.as_ref() {
2024            xml_out.attr_esc("table:style-name", rowstyle.as_str())?;
2025        }
2026        if let Some(cellstyle) = row_header.cellstyle.as_ref() {
2027            xml_out.attr_esc("table:default-cell-style-name", cellstyle.as_str())?;
2028        }
2029        if row_header.visible != Visibility::Visible {
2030            xml_out.attr_esc("table:visibility", &row_header.visible)?;
2031        }
2032    }
2033
2034    // We fill the empty spaces completely up to max columns.
2035    xml_out.empty("table:table-cell")?;
2036    xml_out.attr("table:number-columns-repeated", &max_cell.1)?;
2037
2038    xml_out.end_elem("table:table-row")?;
2039
2040    Ok(())
2041}
2042
2043fn write_table_columns(
2044    sheet: &Sheet,
2045    max_cell: (u32, u32),
2046    xml_out: &mut OdsXmlWriter<'_>,
2047) -> Result<(), OdsError> {
2048    // determine column count
2049    let mut max_col = max_cell.1;
2050    for grp in &sheet.group_cols {
2051        max_col = max(max_col, grp.to + 1);
2052    }
2053    if let Some(header_cols) = &sheet.header_cols {
2054        max_col = max(max_col, header_cols.to + 1);
2055    }
2056
2057    // table:table-column
2058    let mut c = 0;
2059    loop {
2060        if c >= max_col {
2061            break;
2062        }
2063
2064        for grp in &sheet.group_cols {
2065            if c == grp.from() {
2066                xml_out.elem("table:table-column-group")?;
2067                if !grp.display() {
2068                    xml_out.attr_str("table:display", "false")?;
2069                }
2070            }
2071        }
2072
2073        // print-header columns
2074        if let Some(header_cols) = &sheet.header_cols {
2075            if c >= header_cols.from && c <= header_cols.to {
2076                xml_out.elem("table:table-header-columns")?;
2077            }
2078        }
2079
2080        xml_out.empty("table:table-column")?;
2081        let span = if let Some(col_header) = sheet.col_header.get(&c) {
2082            if col_header.span > 1 {
2083                xml_out.attr_esc("table:number-columns-repeated", &col_header.span)?;
2084            }
2085            if let Some(style) = col_header.style.as_ref() {
2086                xml_out.attr_esc("table:style-name", style.as_str())?;
2087            }
2088            if let Some(style) = col_header.cellstyle.as_ref() {
2089                xml_out.attr_esc("table:default-cell-style-name", style.as_str())?;
2090            }
2091            if col_header.visible != Visibility::Visible {
2092                xml_out.attr_esc("table:visibility", &col_header.visible)?;
2093            }
2094
2095            col_header.span
2096        } else {
2097            1
2098        };
2099
2100        if let Some(header_cols) = &sheet.header_cols {
2101            if c >= header_cols.from && c <= header_cols.to {
2102                xml_out.end_elem("table:table-header-columns")?;
2103            }
2104        }
2105
2106        for col_group in &sheet.group_cols {
2107            if c == col_group.to() {
2108                xml_out.end_elem("table:table-column-group")?;
2109            }
2110        }
2111
2112        debug_assert!(span > 0);
2113        c += span;
2114    }
2115
2116    Ok(())
2117}
2118
2119#[allow(clippy::single_char_add_str)]
2120fn write_cell(
2121    book: &WorkBook,
2122    cell: &CellData,
2123    is_hidden: bool,
2124    repeat: u32,
2125    xml_out: &mut OdsXmlWriter<'_>,
2126) -> Result<(), OdsError> {
2127    let tag = if is_hidden {
2128        "table:covered-table-cell"
2129    } else {
2130        "table:table-cell"
2131    };
2132
2133    let has_subs = cell.value != Value::Empty || cell.has_annotation() || cell.has_draw_frames();
2134    xml_out.elem_if(has_subs, tag)?;
2135
2136    if let Some(formula) = &cell.formula {
2137        xml_out.attr_esc("table:formula", formula)?;
2138    }
2139
2140    if repeat > 1 {
2141        xml_out.attr_esc("table:number-columns-repeated", &repeat)?;
2142    }
2143
2144    // Direct style oder value based default style.
2145    if let Some(style) = &cell.style {
2146        xml_out.attr_esc("table:style-name", style.as_str())?;
2147    } else if let Some(style) = book.def_style(cell.value.value_type()) {
2148        xml_out.attr_esc("table:style-name", style.as_str())?;
2149    }
2150
2151    // Content validation
2152    if let Some(validation_name) = cell.extra.as_ref().and_then(|v| v.validation_name.as_ref()) {
2153        xml_out.attr_esc("table:content-validation-name", validation_name.as_str())?;
2154    }
2155
2156    // Spans
2157    if let Some(span) = cell.extra.as_ref().map(|v| v.span) {
2158        if span.row_span > 1 {
2159            xml_out.attr_esc("table:number-rows-spanned", &span.row_span)?;
2160        }
2161        if span.col_span > 1 {
2162            xml_out.attr_esc("table:number-columns-spanned", &span.col_span)?;
2163        }
2164    }
2165    if let Some(span) = cell.extra.as_ref().map(|v| v.matrix_span) {
2166        if span.row_span > 1 {
2167            xml_out.attr_esc("table:number-matrix-rows-spanned", &span.row_span)?;
2168        }
2169        if span.col_span > 1 {
2170            xml_out.attr_esc("table:number-matrix-columns-spanned", &span.col_span)?;
2171        }
2172    }
2173
2174    // This finds the correct ValueFormat, but there is no way to use it.
2175    // Falls back to: Output the same string as needed for the value-attribute
2176    // and hope for the best. Seems to work well enough.
2177    //
2178    // let valuestyle = if let Some(style_name) = cell.style {
2179    //     book.find_value_format(style_name)
2180    // } else {
2181    //     None
2182    // };
2183
2184    match &cell.value {
2185        Value::Empty => {}
2186        Value::Text(s) => {
2187            xml_out.attr_str("office:value-type", "string")?;
2188            for l in s.split('\n') {
2189                xml_out.elem_text_esc("text:p", l)?;
2190            }
2191        }
2192        Value::TextXml(t) => {
2193            xml_out.attr_str("office:value-type", "string")?;
2194            for tt in t.iter() {
2195                write_xmltag(tt, xml_out)?;
2196            }
2197        }
2198        Value::DateTime(d) => {
2199            xml_out.attr_str("office:value-type", "date")?;
2200            let value = d.format(DATETIME_FORMAT);
2201            xml_out.attr("office:date-value", &value)?;
2202            xml_out.elem("text:p")?;
2203            xml_out.text(&value)?;
2204            xml_out.end_elem("text:p")?;
2205        }
2206        Value::TimeDuration(d) => {
2207            xml_out.attr_str("office:value-type", "time")?;
2208            let value = format_duration2(*d);
2209            xml_out.attr("office:time-value", &value)?;
2210            xml_out.elem("text:p")?;
2211            xml_out.text(&value)?;
2212            xml_out.end_elem("text:p")?;
2213        }
2214        Value::Boolean(b) => {
2215            xml_out.attr_str("office:value-type", "boolean")?;
2216            xml_out.attr_str("office:boolean-value", if *b { "true" } else { "false" })?;
2217            xml_out.elem("text:p")?;
2218            xml_out.text_str(if *b { "true" } else { "false" })?;
2219            xml_out.end_elem("text:p")?;
2220        }
2221        Value::Currency(v, c) => {
2222            xml_out.attr_str("office:value-type", "currency")?;
2223            xml_out.attr_esc("office:currency", c)?;
2224            xml_out.attr("office:value", v)?;
2225            xml_out.elem("text:p")?;
2226            xml_out.text_esc(c)?;
2227            xml_out.text_str(" ")?;
2228            xml_out.text(v)?;
2229            xml_out.end_elem("text:p")?;
2230        }
2231        Value::Number(v) => {
2232            xml_out.attr_str("office:value-type", "float")?;
2233            xml_out.attr("office:value", v)?;
2234            xml_out.elem("text:p")?;
2235            xml_out.text(v)?;
2236            xml_out.end_elem("text:p")?;
2237        }
2238        Value::Percentage(v) => {
2239            xml_out.attr_str("office:value-type", "percentage")?;
2240            xml_out.attr("office:value", v)?;
2241            xml_out.elem("text:p")?;
2242            xml_out.text(v)?;
2243            xml_out.end_elem("text:p")?;
2244        }
2245    }
2246
2247    if let Some(annotation) = cell.extra.as_ref().and_then(|v| v.annotation.as_ref()) {
2248        write_annotation(annotation, xml_out)?;
2249    }
2250
2251    if let Some(draw_frames) = cell.extra.as_ref().map(|v| &v.draw_frames) {
2252        for draw_frame in draw_frames {
2253            write_draw_frame(draw_frame, xml_out)?;
2254        }
2255    }
2256
2257    xml_out.end_elem_if(has_subs, tag)?;
2258
2259    Ok(())
2260}
2261
2262fn write_draw_frame(
2263    draw_frame: &DrawFrame,
2264    xml_out: &mut OdsXmlWriter<'_>,
2265) -> Result<(), OdsError> {
2266    xml_out.elem("draw:frame")?;
2267    for (k, v) in draw_frame.attrmap().iter() {
2268        xml_out.attr_esc(k.as_ref(), v)?;
2269    }
2270
2271    for content in draw_frame.content_ref() {
2272        match content {
2273            DrawFrameContent::Image(img) => {
2274                write_draw_image(img, xml_out)?;
2275            }
2276        }
2277    }
2278
2279    if let Some(desc) = draw_frame.desc() {
2280        xml_out.elem("svg:desc")?;
2281        xml_out.text_esc(desc)?;
2282        xml_out.end_elem("svg:desc")?;
2283    }
2284    if let Some(title) = draw_frame.title() {
2285        xml_out.elem("svg:title")?;
2286        xml_out.text_esc(title)?;
2287        xml_out.end_elem("svg:title")?;
2288    }
2289
2290    xml_out.end_elem("draw:frame")?;
2291
2292    Ok(())
2293}
2294
2295fn write_draw_image(
2296    draw_image: &DrawImage,
2297    xml_out: &mut OdsXmlWriter<'_>,
2298) -> Result<(), OdsError> {
2299    xml_out.elem("draw:image")?;
2300    for (k, v) in draw_image.attrmap().iter() {
2301        xml_out.attr_esc(k.as_ref(), v)?;
2302    }
2303
2304    if let Some(bin) = draw_image.get_binary_base64() {
2305        xml_out.elem("office:binary-data")?;
2306        xml_out.text(bin)?;
2307        xml_out.end_elem("office:binary-data")?;
2308    }
2309
2310    for content in draw_image.get_text() {
2311        write_xmltag(content, xml_out)?;
2312    }
2313
2314    xml_out.end_elem("draw:image")?;
2315
2316    Ok(())
2317}
2318
2319fn write_annotation(
2320    annotation: &Annotation,
2321    xml_out: &mut OdsXmlWriter<'_>,
2322) -> Result<(), OdsError> {
2323    xml_out.elem("office:annotation")?;
2324    xml_out.attr("office:display", &annotation.display())?;
2325    xml_out.attr_esc("office:name", &annotation.name())?;
2326    for (k, v) in annotation.attrmap().iter() {
2327        xml_out.attr_esc(k.as_ref(), v)?;
2328    }
2329
2330    if let Some(creator) = annotation.creator() {
2331        xml_out.elem("dc:creator")?;
2332        xml_out.text_esc(creator.as_str())?;
2333        xml_out.end_elem("dc:creator")?;
2334    }
2335    if let Some(date) = annotation.date() {
2336        xml_out.elem("dc:date")?;
2337        xml_out.text_esc(&date.format(DATETIME_FORMAT))?;
2338        xml_out.end_elem("dc:date")?;
2339    }
2340    for v in annotation.text() {
2341        write_xmltag(v, xml_out)?;
2342    }
2343    xml_out.end_elem("office:annotation")?;
2344    Ok(())
2345}
2346
2347fn write_scripts(scripts: &Vec<Script>, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2348    for script in scripts {
2349        xml_out.elem("office:script")?;
2350        xml_out.attr_esc("script:language", &script.script_lang)?;
2351
2352        for content in &script.script {
2353            write_xmlcontent(content, xml_out)?;
2354        }
2355
2356        xml_out.end_elem("/office:script")?;
2357    }
2358
2359    Ok(())
2360}
2361
2362fn write_event_listeners(
2363    events: &HashMap<String, EventListener>,
2364    xml_out: &mut OdsXmlWriter<'_>,
2365) -> Result<(), OdsError> {
2366    for event in events.values() {
2367        xml_out.empty("script:event-listener")?;
2368        xml_out.attr_esc("script:event-name", &event.event_name)?;
2369        xml_out.attr_esc("script:language", &event.script_lang)?;
2370        xml_out.attr_esc("script:macro-name", &event.macro_name)?;
2371        xml_out.attr_esc("xlink:actuate", &event.actuate)?;
2372        xml_out.attr_esc("xlink:href", &event.href)?;
2373        xml_out.attr_esc("xlink:type", &event.link_type)?;
2374    }
2375
2376    Ok(())
2377}
2378
2379fn write_office_font_face_decls(
2380    book: &WorkBook,
2381    origin: StyleOrigin,
2382    xml_out: &mut OdsXmlWriter<'_>,
2383) -> Result<(), OdsError> {
2384    xml_out.elem_if(!book.fonts.is_empty(), "office:font-face-decls")?;
2385    write_style_font_face(&book.fonts, origin, xml_out)?;
2386    xml_out.end_elem_if(!book.fonts.is_empty(), "office:font-face-decls")?;
2387    Ok(())
2388}
2389
2390fn write_style_font_face(
2391    fonts: &HashMap<String, FontFaceDecl>,
2392    origin: StyleOrigin,
2393    xml_out: &mut OdsXmlWriter<'_>,
2394) -> Result<(), OdsError> {
2395    for font in fonts.values().filter(|s| s.origin() == origin) {
2396        xml_out.empty("style:font-face")?;
2397        xml_out.attr_esc("style:name", font.name())?;
2398        for (a, v) in font.attrmap().iter() {
2399            xml_out.attr_esc(a.as_ref(), v)?;
2400        }
2401    }
2402    Ok(())
2403}
2404
2405fn write_office_styles(
2406    book: &WorkBook,
2407    origin: StyleOrigin,
2408    xml_out: &mut OdsXmlWriter<'_>,
2409) -> Result<(), OdsError> {
2410    xml_out.elem("office:styles")?;
2411    write_styles(book, origin, StyleUse::Default, xml_out)?;
2412    write_styles(book, origin, StyleUse::Named, xml_out)?;
2413    write_valuestyles(book, origin, StyleUse::Named, xml_out)?;
2414    write_valuestyles(book, origin, StyleUse::Default, xml_out)?;
2415    xml_out.end_elem("office:styles")?;
2416    Ok(())
2417}
2418
2419fn write_office_automatic_styles(
2420    book: &WorkBook,
2421    origin: StyleOrigin,
2422    xml_out: &mut OdsXmlWriter<'_>,
2423) -> Result<(), OdsError> {
2424    xml_out.elem("office:automatic-styles")?;
2425    write_pagestyles(&book.pagestyles, xml_out)?;
2426    write_styles(book, origin, StyleUse::Automatic, xml_out)?;
2427    write_valuestyles(book, origin, StyleUse::Automatic, xml_out)?;
2428    xml_out.end_elem("office:automatic-styles")?;
2429    Ok(())
2430}
2431
2432fn write_office_master_styles(
2433    book: &WorkBook,
2434    xml_out: &mut OdsXmlWriter<'_>,
2435) -> Result<(), OdsError> {
2436    xml_out.elem("office:master-styles")?;
2437    write_masterpage(&book.masterpages, xml_out)?;
2438    xml_out.end_elem("office:master-styles")?;
2439    Ok(())
2440}
2441
2442fn write_styles(
2443    book: &WorkBook,
2444    origin: StyleOrigin,
2445    styleuse: StyleUse,
2446    xml_out: &mut OdsXmlWriter<'_>,
2447) -> Result<(), OdsError> {
2448    for style in book.colstyles.values() {
2449        if style.origin() == origin && style.styleuse() == styleuse {
2450            write_colstyle(style, xml_out)?;
2451        }
2452    }
2453    for style in book.rowstyles.values() {
2454        if style.origin() == origin && style.styleuse() == styleuse {
2455            write_rowstyle(style, xml_out)?;
2456        }
2457    }
2458    for style in book.tablestyles.values() {
2459        if style.origin() == origin && style.styleuse() == styleuse {
2460            write_tablestyle(style, xml_out)?;
2461        }
2462    }
2463    for style in book.cellstyles.values() {
2464        if style.origin() == origin && style.styleuse() == styleuse {
2465            write_cellstyle(style, xml_out)?;
2466        }
2467    }
2468    for style in book.paragraphstyles.values() {
2469        if style.origin() == origin && style.styleuse() == styleuse {
2470            write_paragraphstyle(style, xml_out)?;
2471        }
2472    }
2473    for style in book.textstyles.values() {
2474        if style.origin() == origin && style.styleuse() == styleuse {
2475            write_textstyle(style, xml_out)?;
2476        }
2477    }
2478    for style in book.rubystyles.values() {
2479        if style.origin() == origin && style.styleuse() == styleuse {
2480            write_rubystyle(style, xml_out)?;
2481        }
2482    }
2483    for style in book.graphicstyles.values() {
2484        if style.origin() == origin && style.styleuse() == styleuse {
2485            write_graphicstyle(style, xml_out)?;
2486        }
2487    }
2488
2489    // if let Some(stylemaps) = style.stylemaps() {
2490    //     for sm in stylemaps {
2491    //         xml_out.empty("style:map")?;
2492    //         xml_out.attr_esc("style:condition", sm.condition())?;
2493    //         xml_out.attr_esc("style:apply-style-name", sm.applied_style())?;
2494    //         xml_out.attr_esc("style:base-cell-address", &sm.base_cell().to_string())?;
2495    //     }
2496    // }
2497
2498    Ok(())
2499}
2500
2501fn write_tablestyle(style: &TableStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2502    let is_empty = style.tablestyle().is_empty();
2503
2504    if style.styleuse() == StyleUse::Default {
2505        xml_out.elem_if(!is_empty, "style:default-style")?;
2506    } else {
2507        xml_out.elem_if(!is_empty, "style:style")?;
2508        xml_out.attr_esc("style:name", style.name())?;
2509    }
2510    xml_out.attr_str("style:family", "table")?;
2511    for (a, v) in style.attrmap().iter() {
2512        match a.as_ref() {
2513            "style:name" => {}
2514            "style:family" => {}
2515            _ => {
2516                xml_out.attr_esc(a.as_ref(), v)?;
2517            }
2518        }
2519    }
2520
2521    if !style.tablestyle().is_empty() {
2522        xml_out.empty("style:table-properties")?;
2523        for (a, v) in style.tablestyle().iter() {
2524            xml_out.attr_esc(a.as_ref(), v)?;
2525        }
2526    }
2527    if style.styleuse() == StyleUse::Default {
2528        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2529    } else {
2530        xml_out.end_elem_if(!is_empty, "style:style")?;
2531    }
2532
2533    Ok(())
2534}
2535
2536fn write_rowstyle(style: &RowStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2537    let is_empty = style.rowstyle().is_empty();
2538
2539    if style.styleuse() == StyleUse::Default {
2540        xml_out.elem_if(!is_empty, "style:default-style")?;
2541    } else {
2542        xml_out.elem_if(!is_empty, "style:style")?;
2543        xml_out.attr_esc("style:name", style.name())?;
2544    }
2545    xml_out.attr_str("style:family", "table-row")?;
2546    for (a, v) in style.attrmap().iter() {
2547        match a.as_ref() {
2548            "style:name" => {}
2549            "style:family" => {}
2550            _ => {
2551                xml_out.attr_esc(a.as_ref(), v)?;
2552            }
2553        }
2554    }
2555
2556    if !style.rowstyle().is_empty() {
2557        xml_out.empty("style:table-row-properties")?;
2558        for (a, v) in style.rowstyle().iter() {
2559            xml_out.attr_esc(a.as_ref(), v)?;
2560        }
2561    }
2562    if style.styleuse() == StyleUse::Default {
2563        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2564    } else {
2565        xml_out.end_elem_if(!is_empty, "style:style")?;
2566    }
2567
2568    Ok(())
2569}
2570
2571fn write_colstyle(style: &ColStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2572    let is_empty = style.colstyle().is_empty();
2573
2574    if style.styleuse() == StyleUse::Default {
2575        xml_out.elem_if(!is_empty, "style:default-style")?;
2576    } else {
2577        xml_out.elem_if(!is_empty, "style:style")?;
2578        xml_out.attr_esc("style:name", style.name())?;
2579    }
2580    xml_out.attr_str("style:family", "table-column")?;
2581    for (a, v) in style.attrmap().iter() {
2582        match a.as_ref() {
2583            "style:name" => {}
2584            "style:family" => {}
2585            _ => {
2586                xml_out.attr_esc(a.as_ref(), v)?;
2587            }
2588        }
2589    }
2590
2591    if !style.colstyle().is_empty() {
2592        xml_out.empty("style:table-column-properties")?;
2593        for (a, v) in style.colstyle().iter() {
2594            xml_out.attr_esc(a.as_ref(), v)?;
2595        }
2596    }
2597    if style.styleuse() == StyleUse::Default {
2598        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2599    } else {
2600        xml_out.end_elem_if(!is_empty, "style:style")?;
2601    }
2602
2603    Ok(())
2604}
2605
2606fn write_cellstyle(style: &CellStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2607    let is_empty = style.cellstyle().is_empty()
2608        && style.paragraphstyle().is_empty()
2609        && style.textstyle().is_empty()
2610        && style.stylemaps().is_none();
2611
2612    if style.styleuse() == StyleUse::Default {
2613        xml_out.elem_if(!is_empty, "style:default-style")?;
2614    } else {
2615        xml_out.elem_if(!is_empty, "style:style")?;
2616        xml_out.attr_esc("style:name", style.name())?;
2617    }
2618    xml_out.attr_str("style:family", "table-cell")?;
2619    for (a, v) in style.attrmap().iter() {
2620        match a.as_ref() {
2621            "style:name" => {}
2622            "style:family" => {}
2623            _ => {
2624                xml_out.attr_esc(a.as_ref(), v)?;
2625            }
2626        }
2627    }
2628
2629    if !style.cellstyle().is_empty() {
2630        xml_out.empty("style:table-cell-properties")?;
2631        for (a, v) in style.cellstyle().iter() {
2632            xml_out.attr_esc(a.as_ref(), v)?;
2633        }
2634    }
2635    if !style.paragraphstyle().is_empty() {
2636        xml_out.empty("style:paragraph-properties")?;
2637        for (a, v) in style.paragraphstyle().iter() {
2638            xml_out.attr_esc(a.as_ref(), v)?;
2639        }
2640    }
2641    if !style.textstyle().is_empty() {
2642        xml_out.empty("style:text-properties")?;
2643        for (a, v) in style.textstyle().iter() {
2644            xml_out.attr_esc(a.as_ref(), v)?;
2645        }
2646    }
2647    if let Some(stylemaps) = style.stylemaps() {
2648        for sm in stylemaps {
2649            xml_out.empty("style:map")?;
2650            xml_out.attr_esc("style:condition", sm.condition())?;
2651            xml_out.attr_esc("style:apply-style-name", sm.applied_style().as_str())?;
2652            if let Some(r) = sm.base_cell() {
2653                xml_out.attr_esc("style:base-cell-address", r)?;
2654            }
2655        }
2656    }
2657    if style.styleuse() == StyleUse::Default {
2658        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2659    } else {
2660        xml_out.end_elem_if(!is_empty, "style:style")?;
2661    }
2662
2663    Ok(())
2664}
2665
2666fn write_paragraphstyle(
2667    style: &ParagraphStyle,
2668    xml_out: &mut OdsXmlWriter<'_>,
2669) -> Result<(), OdsError> {
2670    let is_empty = style.paragraphstyle().is_empty() && style.textstyle().is_empty();
2671
2672    if style.styleuse() == StyleUse::Default {
2673        xml_out.elem_if(!is_empty, "style:default-style")?;
2674    } else {
2675        xml_out.elem_if(!is_empty, "style:style")?;
2676        xml_out.attr_esc("style:name", style.name())?;
2677    }
2678    xml_out.attr_str("style:family", "paragraph")?;
2679    for (a, v) in style.attrmap().iter() {
2680        match a.as_ref() {
2681            "style:name" => {}
2682            "style:family" => {}
2683            _ => {
2684                xml_out.attr_esc(a.as_ref(), v)?;
2685            }
2686        }
2687    }
2688
2689    if !style.paragraphstyle().is_empty() {
2690        if style.tabstops().is_none() {
2691            xml_out.empty("style:paragraph-properties")?;
2692            for (a, v) in style.paragraphstyle().iter() {
2693                xml_out.attr_esc(a.as_ref(), v)?;
2694            }
2695        } else {
2696            xml_out.elem("style:paragraph-properties")?;
2697            for (a, v) in style.paragraphstyle().iter() {
2698                xml_out.attr_esc(a.as_ref(), v)?;
2699            }
2700            xml_out.elem("style:tab-stops")?;
2701            if let Some(tabstops) = style.tabstops() {
2702                for ts in tabstops {
2703                    xml_out.empty("style:tab-stop")?;
2704                    for (a, v) in ts.attrmap().iter() {
2705                        xml_out.attr_esc(a.as_ref(), v)?;
2706                    }
2707                }
2708            }
2709            xml_out.end_elem("style:tab-stops")?;
2710            xml_out.end_elem("style:paragraph-properties")?;
2711        }
2712    }
2713    if !style.textstyle().is_empty() {
2714        xml_out.empty("style:text-properties")?;
2715        for (a, v) in style.textstyle().iter() {
2716            xml_out.attr_esc(a.as_ref(), v)?;
2717        }
2718    }
2719    if style.styleuse() == StyleUse::Default {
2720        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2721    } else {
2722        xml_out.end_elem_if(!is_empty, "style:style")?;
2723    }
2724
2725    Ok(())
2726}
2727
2728fn write_textstyle(style: &TextStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2729    let is_empty = style.textstyle().is_empty();
2730
2731    if style.styleuse() == StyleUse::Default {
2732        xml_out.elem_if(!is_empty, "style:default-style")?;
2733    } else {
2734        xml_out.elem_if(!is_empty, "style:style")?;
2735        xml_out.attr_esc("style:name", style.name())?;
2736    }
2737    xml_out.attr_str("style:family", "text")?;
2738    for (a, v) in style.attrmap().iter() {
2739        match a.as_ref() {
2740            "style:name" => {}
2741            "style:family" => {}
2742            _ => {
2743                xml_out.attr_esc(a.as_ref(), v)?;
2744            }
2745        }
2746    }
2747
2748    if !style.textstyle().is_empty() {
2749        xml_out.empty("style:text-properties")?;
2750        for (a, v) in style.textstyle().iter() {
2751            xml_out.attr_esc(a.as_ref(), v)?;
2752        }
2753    }
2754    if style.styleuse() == StyleUse::Default {
2755        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2756    } else {
2757        xml_out.end_elem_if(!is_empty, "style:style")?;
2758    }
2759
2760    Ok(())
2761}
2762
2763fn write_rubystyle(style: &RubyStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2764    let is_empty = style.rubystyle().is_empty();
2765
2766    if style.styleuse() == StyleUse::Default {
2767        xml_out.elem_if(!is_empty, "style:default-style")?;
2768    } else {
2769        xml_out.elem_if(!is_empty, "style:style")?;
2770        xml_out.attr_esc("style:name", style.name())?;
2771    }
2772    xml_out.attr_str("style:family", "ruby")?;
2773    for (a, v) in style.attrmap().iter() {
2774        match a.as_ref() {
2775            "style:name" => {}
2776            "style:family" => {}
2777            _ => {
2778                xml_out.attr_esc(a.as_ref(), v)?;
2779            }
2780        }
2781    }
2782
2783    if !style.rubystyle().is_empty() {
2784        xml_out.empty("style:ruby-properties")?;
2785        for (a, v) in style.rubystyle().iter() {
2786            xml_out.attr_esc(a.as_ref(), v)?;
2787        }
2788    }
2789    if style.styleuse() == StyleUse::Default {
2790        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2791    } else {
2792        xml_out.end_elem_if(!is_empty, "style:style")?;
2793    }
2794
2795    Ok(())
2796}
2797
2798fn write_graphicstyle(
2799    style: &GraphicStyle,
2800    xml_out: &mut OdsXmlWriter<'_>,
2801) -> Result<(), OdsError> {
2802    let is_empty = style.graphicstyle().is_empty()
2803        && style.paragraphstyle().is_empty()
2804        && style.textstyle().is_empty();
2805
2806    if style.styleuse() == StyleUse::Default {
2807        xml_out.elem_if(!is_empty, "style:default-style")?;
2808    } else {
2809        xml_out.elem_if(!is_empty, "style:style")?;
2810        xml_out.attr_esc("style:name", style.name())?;
2811    }
2812    xml_out.attr_str("style:family", "graphic")?;
2813    for (a, v) in style.attrmap().iter() {
2814        match a.as_ref() {
2815            "style:name" => {}
2816            "style:family" => {}
2817            _ => {
2818                xml_out.attr_esc(a.as_ref(), v)?;
2819            }
2820        }
2821    }
2822
2823    if !style.graphicstyle().is_empty() {
2824        xml_out.empty("style:graphic-properties")?;
2825        for (a, v) in style.graphicstyle().iter() {
2826            xml_out.attr_esc(a.as_ref(), v)?;
2827        }
2828    }
2829    if !style.paragraphstyle().is_empty() {
2830        xml_out.empty("style:paragraph-properties")?;
2831        for (a, v) in style.paragraphstyle().iter() {
2832            xml_out.attr_esc(a.as_ref(), v)?;
2833        }
2834    }
2835    if !style.textstyle().is_empty() {
2836        xml_out.empty("style:text-properties")?;
2837        for (a, v) in style.textstyle().iter() {
2838            xml_out.attr_esc(a.as_ref(), v)?;
2839        }
2840    }
2841
2842    if style.styleuse() == StyleUse::Default {
2843        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2844    } else {
2845        xml_out.end_elem_if(!is_empty, "style:style")?;
2846    }
2847
2848    Ok(())
2849}
2850
2851fn write_valuestyles(
2852    book: &WorkBook,
2853    origin: StyleOrigin,
2854    style_use: StyleUse,
2855    xml_out: &mut OdsXmlWriter<'_>,
2856) -> Result<(), OdsError> {
2857    write_valuestyle(&book.formats_boolean, origin, style_use, xml_out)?;
2858    write_valuestyle(&book.formats_currency, origin, style_use, xml_out)?;
2859    write_valuestyle(&book.formats_datetime, origin, style_use, xml_out)?;
2860    write_valuestyle(&book.formats_number, origin, style_use, xml_out)?;
2861    write_valuestyle(&book.formats_percentage, origin, style_use, xml_out)?;
2862    write_valuestyle(&book.formats_text, origin, style_use, xml_out)?;
2863    write_valuestyle(&book.formats_timeduration, origin, style_use, xml_out)?;
2864    Ok(())
2865}
2866
2867fn write_valuestyle<T: ValueFormatTrait>(
2868    value_formats: &HashMap<String, T>,
2869    origin: StyleOrigin,
2870    styleuse: StyleUse,
2871    xml_out: &mut OdsXmlWriter<'_>,
2872) -> Result<(), OdsError> {
2873    for value_format in value_formats
2874        .values()
2875        .filter(|s| s.origin() == origin && s.styleuse() == styleuse)
2876    {
2877        let tag = match value_format.value_type() {
2878            ValueType::Empty => unreachable!(),
2879            ValueType::Boolean => "number:boolean-style",
2880            ValueType::Number => "number:number-style",
2881            ValueType::Text => "number:text-style",
2882            ValueType::TextXml => "number:text-style",
2883            ValueType::TimeDuration => "number:time-style",
2884            ValueType::Percentage => "number:percentage-style",
2885            ValueType::Currency => "number:currency-style",
2886            ValueType::DateTime => "number:date-style",
2887        };
2888
2889        xml_out.elem(tag)?;
2890        xml_out.attr_esc("style:name", value_format.name())?;
2891        for (a, v) in value_format.attrmap().iter() {
2892            xml_out.attr_esc(a.as_ref(), v)?;
2893        }
2894
2895        if !value_format.textstyle().is_empty() {
2896            xml_out.empty("style:text-properties")?;
2897            for (a, v) in value_format.textstyle().iter() {
2898                xml_out.attr_esc(a.as_ref(), v)?;
2899            }
2900        }
2901
2902        for part in value_format.parts() {
2903            let part_tag = match part.part_type() {
2904                FormatPartType::Boolean => "number:boolean",
2905                FormatPartType::Number => "number:number",
2906                FormatPartType::ScientificNumber => "number:scientific-number",
2907                FormatPartType::CurrencySymbol => "number:currency-symbol",
2908                FormatPartType::Day => "number:day",
2909                FormatPartType::Month => "number:month",
2910                FormatPartType::Year => "number:year",
2911                FormatPartType::Era => "number:era",
2912                FormatPartType::DayOfWeek => "number:day-of-week",
2913                FormatPartType::WeekOfYear => "number:week-of-year",
2914                FormatPartType::Quarter => "number:quarter",
2915                FormatPartType::Hours => "number:hours",
2916                FormatPartType::Minutes => "number:minutes",
2917                FormatPartType::Seconds => "number:seconds",
2918                FormatPartType::Fraction => "number:fraction",
2919                FormatPartType::AmPm => "number:am-pm",
2920                FormatPartType::Text => "number:text",
2921                FormatPartType::TextContent => "number:text-content",
2922                FormatPartType::FillCharacter => "number:fill-character",
2923            };
2924
2925            if part.part_type() == FormatPartType::Text
2926                || part.part_type() == FormatPartType::CurrencySymbol
2927                || part.part_type() == FormatPartType::FillCharacter
2928            {
2929                let content = part.content().filter(|v| !v.is_empty());
2930                xml_out.elem_if(content.is_some(), part_tag)?;
2931                for (a, v) in part.attrmap().iter() {
2932                    xml_out.attr_esc(a.as_ref(), v)?;
2933                }
2934                if let Some(content) = content {
2935                    xml_out.text_esc(content)?;
2936                }
2937                xml_out.end_elem_if(content.is_some(), part_tag)?;
2938            } else if part.part_type() == FormatPartType::Number {
2939                if let Some(position) = part.position() {
2940                    xml_out.elem(part_tag)?;
2941                    for (a, v) in part.attrmap().iter() {
2942                        xml_out.attr_esc(a.as_ref(), v)?;
2943                    }
2944
2945                    // embedded text
2946                    if let Some(content) = part.content() {
2947                        xml_out.elem("number:embedded-text")?;
2948                        xml_out.attr_esc("number:position", &position)?;
2949                        xml_out.text_esc(content)?;
2950                        xml_out.end_elem("number:embedded-text")?;
2951                    } else {
2952                        xml_out.empty("number:embedded-text")?;
2953                        xml_out.attr_esc("number:position", &position)?;
2954                    }
2955
2956                    xml_out.end_elem(part_tag)?;
2957                } else {
2958                    xml_out.empty(part_tag)?;
2959                    for (a, v) in part.attrmap().iter() {
2960                        xml_out.attr_esc(a.as_ref(), v)?;
2961                    }
2962                }
2963            } else {
2964                xml_out.empty(part_tag)?;
2965                for (a, v) in part.attrmap().iter() {
2966                    xml_out.attr_esc(a.as_ref(), v)?;
2967                }
2968            }
2969        }
2970
2971        if let Some(stylemaps) = value_format.stylemaps() {
2972            for sm in stylemaps {
2973                xml_out.empty("style:map")?;
2974                xml_out.attr_esc("style:condition", sm.condition())?;
2975                xml_out.attr_esc("style:apply-style-name", sm.applied_style())?;
2976            }
2977        }
2978
2979        xml_out.end_elem(tag)?;
2980    }
2981
2982    Ok(())
2983}
2984
2985fn write_pagestyles(
2986    styles: &HashMap<PageStyleRef, PageStyle>,
2987    xml_out: &mut OdsXmlWriter<'_>,
2988) -> Result<(), OdsError> {
2989    for style in styles.values() {
2990        xml_out.elem("style:page-layout")?;
2991        xml_out.attr_esc("style:name", style.name())?;
2992        if let Some(master_page_usage) = &style.master_page_usage {
2993            xml_out.attr_esc("style:page-usage", master_page_usage)?;
2994        }
2995
2996        if !style.style().is_empty() {
2997            xml_out.empty("style:page-layout-properties")?;
2998            for (k, v) in style.style().iter() {
2999                xml_out.attr_esc(k.as_ref(), v)?;
3000            }
3001        }
3002
3003        xml_out.elem("style:header-style")?;
3004        xml_out.empty("style:header-footer-properties")?;
3005        if !style.headerstyle().style().is_empty() {
3006            for (k, v) in style.headerstyle().style().iter() {
3007                xml_out.attr_esc(k.as_ref(), v)?;
3008            }
3009        }
3010        xml_out.end_elem("style:header-style")?;
3011
3012        xml_out.elem("style:footer-style")?;
3013        xml_out.empty("style:header-footer-properties")?;
3014        if !style.footerstyle().style().is_empty() {
3015            for (k, v) in style.footerstyle().style().iter() {
3016                xml_out.attr_esc(k.as_ref(), v)?;
3017            }
3018        }
3019        xml_out.end_elem("style:footer-style")?;
3020
3021        xml_out.end_elem("style:page-layout")?;
3022    }
3023
3024    Ok(())
3025}
3026
3027fn write_masterpage(
3028    masterpages: &HashMap<MasterPageRef, MasterPage>,
3029    xml_out: &mut OdsXmlWriter<'_>,
3030) -> Result<(), OdsError> {
3031    for masterpage in masterpages.values() {
3032        xml_out.elem("style:master-page")?;
3033        xml_out.attr_esc("style:name", masterpage.name())?;
3034        if !masterpage.display_name().is_empty() {
3035            xml_out.attr_esc("style:display-name", masterpage.display_name())?;
3036        }
3037        if let Some(style) = masterpage.pagestyle() {
3038            xml_out.attr_esc("style:page-layout-name", style.as_str())?;
3039        }
3040        if let Some(next) = masterpage.next_masterpage() {
3041            xml_out.attr_esc("style:next-style-name", next.as_str())?;
3042        }
3043
3044        // header
3045        xml_out.elem_if(!masterpage.header().is_empty(), "style:header")?;
3046        if !masterpage.header().display() {
3047            xml_out.attr_str("style:display", "false")?;
3048        }
3049        write_regions(masterpage.header(), xml_out)?;
3050        xml_out.end_elem_if(!masterpage.header().is_empty(), "style:header")?;
3051
3052        xml_out.elem_if(!masterpage.header_first().is_empty(), "style:header-first")?;
3053        if !masterpage.header_first().display() || masterpage.header_first().is_empty() {
3054            xml_out.attr_str("style:display", "false")?;
3055        }
3056        write_regions(masterpage.header_first(), xml_out)?;
3057        xml_out.end_elem_if(!masterpage.header_first().is_empty(), "style:header-first")?;
3058
3059        xml_out.elem_if(!masterpage.header_left().is_empty(), "style:header-left")?;
3060        if !masterpage.header_left().display() || masterpage.header_left().is_empty() {
3061            xml_out.attr_str("style:display", "false")?;
3062        }
3063        write_regions(masterpage.header_left(), xml_out)?;
3064        xml_out.end_elem_if(!masterpage.header_left().is_empty(), "style:header-left")?;
3065
3066        // footer
3067        xml_out.elem_if(!masterpage.footer().is_empty(), "style:footer")?;
3068        if !masterpage.footer().display() {
3069            xml_out.attr_str("style:display", "false")?;
3070        }
3071        write_regions(masterpage.footer(), xml_out)?;
3072        xml_out.end_elem_if(!masterpage.footer().is_empty(), "style:footer")?;
3073
3074        xml_out.elem_if(!masterpage.footer_first().is_empty(), "style:footer-first")?;
3075        if !masterpage.footer_first().display() || masterpage.footer_first().is_empty() {
3076            xml_out.attr_str("style:display", "false")?;
3077        }
3078        write_regions(masterpage.footer_first(), xml_out)?;
3079        xml_out.end_elem_if(!masterpage.footer_first().is_empty(), "style:footer-first")?;
3080
3081        xml_out.elem_if(!masterpage.footer_left().is_empty(), "style:footer-left")?;
3082        if !masterpage.footer_left().display() || masterpage.footer_left().is_empty() {
3083            xml_out.attr_str("style:display", "false")?;
3084        }
3085        write_regions(masterpage.footer_left(), xml_out)?;
3086        xml_out.end_elem_if(!masterpage.footer_left().is_empty(), "style:footer-left")?;
3087
3088        xml_out.end_elem("style:master-page")?;
3089    }
3090
3091    Ok(())
3092}
3093
3094fn write_regions(hf: &HeaderFooter, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
3095    if !hf.left().is_empty() {
3096        xml_out.elem("style:region-left")?;
3097        for v in hf.left() {
3098            write_xmltag(v, xml_out)?;
3099        }
3100        xml_out.end_elem("style:region-left")?;
3101    }
3102    if !hf.center().is_empty() {
3103        xml_out.elem("style:region-center")?;
3104        for v in hf.center() {
3105            write_xmltag(v, xml_out)?;
3106        }
3107        xml_out.end_elem("style:region-center")?;
3108    }
3109    if !hf.right().is_empty() {
3110        xml_out.elem("style:region-right")?;
3111        for v in hf.right() {
3112            write_xmltag(v, xml_out)?;
3113        }
3114        xml_out.end_elem("style:region-right")?;
3115    }
3116    for content in hf.content() {
3117        write_xmltag(content, xml_out)?;
3118    }
3119
3120    Ok(())
3121}
3122
3123fn write_xmlcontent(x: &XmlContent, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
3124    match x {
3125        XmlContent::Text(t) => {
3126            xml_out.text_esc(t)?;
3127        }
3128        XmlContent::Tag(t) => {
3129            write_xmltag(t, xml_out)?;
3130        }
3131    }
3132    Ok(())
3133}
3134
3135fn write_xmltag(x: &XmlTag, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
3136    xml_out.elem_if(!x.is_empty(), x.name())?;
3137
3138    for (k, v) in x.attrmap().iter() {
3139        xml_out.attr_esc(k.as_ref(), v)?;
3140    }
3141
3142    for c in x.content() {
3143        match c {
3144            XmlContent::Text(t) => {
3145                xml_out.text_esc(t)?;
3146            }
3147            XmlContent::Tag(t) => {
3148                write_xmltag(t, xml_out)?;
3149            }
3150        }
3151    }
3152
3153    xml_out.end_elem_if(!x.is_empty(), x.name())?;
3154
3155    Ok(())
3156}
3157
3158// All extra entries from the manifest.
3159fn write_ods_extra<W: Write + Seek>(
3160    cfg: &OdsWriteOptions,
3161    zip_writer: &mut ZipWriter<W>,
3162    book: &WorkBook,
3163) -> Result<(), OdsError> {
3164    for manifest in book.manifest.values() {
3165        if !matches!(
3166            manifest.full_path.as_str(),
3167            "/" | "settings.xml" | "styles.xml" | "content.xml" | "meta.xml"
3168        ) {
3169            if manifest.is_dir() {
3170                zip_writer.add_directory(&manifest.full_path, FileOptions::<()>::default())?;
3171            } else {
3172                zip_writer.start_file(
3173                    manifest.full_path.as_str(),
3174                    FileOptions::<()>::default()
3175                        .compression_method(cfg.method)
3176                        .compression_level(cfg.level),
3177                )?;
3178                if let Some(buf) = &manifest.buffer {
3179                    zip_writer.write_all(buf.as_slice())?;
3180                }
3181            }
3182        }
3183    }
3184
3185    Ok(())
3186}