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(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    xml_out.attr_esc("table:name", &sheet.name)?;
1583    if let Some(style) = sheet.style.as_ref() {
1584        xml_out.attr_esc("table:style-name", style.as_str())?;
1585    }
1586    if let Some(print_ranges) = &sheet.print_ranges {
1587        xml_out.attr_esc("table:print-ranges", &format_cellranges(print_ranges))?;
1588    }
1589    if !sheet.print() {
1590        xml_out.attr_str("table:print", "false")?;
1591    }
1592    if !sheet.display() {
1593        xml_out.attr_str("table:display", "false")?;
1594    }
1595
1596    for tag in &sheet.extra {
1597        if tag.name() == "table:title"
1598            || tag.name() == "table:desc"
1599            || tag.name() == "table:table-source"
1600            || tag.name() == "office:dde-source"
1601            || tag.name() == "table:scenario"
1602            || tag.name() == "office:forms"
1603            || tag.name() == "table:shapes"
1604        {
1605            write_xmltag(tag, xml_out)?;
1606        }
1607    }
1608
1609    let max_cell = sheet.used_grid_size();
1610
1611    write_table_columns(sheet, max_cell, xml_out)?;
1612
1613    // list of current spans
1614    let mut spans = Vec::<CellRange>::new();
1615    let mut split = Vec::<SplitCols>::new();
1616
1617    // table-row + table-cell
1618    let mut first_cell = true;
1619    let mut prev_row: u32 = 0;
1620    let mut prev_row_repeat: u32 = 1;
1621    let mut prev_col: u32 = 0;
1622    let mut row_group_count = 0;
1623    let mut row_header = false;
1624
1625    let mut it = CellDataIter::new(sheet.data.range(..));
1626    while let Some(((cur_row, cur_col), cell)) = it.next() {
1627        // Row repeat count.
1628        let cur_row_repeat = if let Some(row_header) = sheet.row_header.get(&cur_row) {
1629            row_header.repeat
1630        } else {
1631            1
1632        };
1633        // Cell repeat count.
1634        let cur_col_repeat = cell.repeat;
1635
1636        // There may be a lot of gaps of any kind in our data.
1637        // In the XML format there is no cell identification, every gap
1638        // must be filled with empty rows/columns. For this we need some
1639        // calculations.
1640
1641        // For the repeat-counter we need to look forward.
1642        let (next_row, next_col, is_last_cell) = //_
1643            if let Some((next_row, next_col)) = it.peek_cell()
1644        {
1645            (next_row, next_col, false)
1646        } else {
1647            (max_cell.0, max_cell.1, true)
1648        };
1649
1650        // Looking forward row-wise.
1651        let forward_delta_row = next_row - cur_row;
1652        // Column deltas are only relevant in the same row, but we need to
1653        // fill up to max used columns.
1654        let forward_delta_col = if forward_delta_row > 0 {
1655            max_cell.1 - cur_col
1656        } else {
1657            next_col - cur_col
1658        };
1659
1660        // Looking backward row-wise.
1661        let backward_delta_row = cur_row - prev_row;
1662        // When a row changes our delta is from zero to cur_col.
1663        let backward_delta_col = if backward_delta_row > 0 {
1664            cur_col
1665        } else {
1666            cur_col - prev_col
1667        };
1668
1669        // After the first cell there is always an open row tag that
1670        // needs to be closed.
1671        if backward_delta_row > 0 && !first_cell {
1672            write_end_prev_row(
1673                sheet,
1674                prev_row,
1675                prev_row_repeat,
1676                &mut row_group_count,
1677                &mut row_header,
1678                xml_out,
1679            )?;
1680        }
1681
1682        // Any empty rows before this one?
1683        if backward_delta_row > 0 {
1684            if backward_delta_row < prev_row_repeat {
1685                return Err(OdsError::Ods(format!(
1686                    "{}: row-repeat of {} for row {} overlaps with the following row.",
1687                    sheet.name, prev_row_repeat, prev_row,
1688                )));
1689            }
1690
1691            // The repeat counter for the empty rows before is reduced by the last
1692            // real repeat.
1693            let mut synth_row_repeat = backward_delta_row - prev_row_repeat;
1694            // At the very beginning last row is 0. But nothing has been written for it.
1695            // To account for this we add one repeat.
1696            if first_cell {
1697                synth_row_repeat += 1;
1698            }
1699            let synth_row = cur_row - synth_row_repeat;
1700
1701            if synth_row_repeat > 0 {
1702                write_empty_rows_before(
1703                    sheet,
1704                    synth_row,
1705                    synth_row_repeat,
1706                    max_cell,
1707                    &mut row_group_count,
1708                    &mut row_header,
1709                    xml_out,
1710                )?;
1711            }
1712        }
1713
1714        // Start a new row if there is a delta or we are at the start.
1715        // Fills in any blank cells before the current cell.
1716        if backward_delta_row > 0 || first_cell {
1717            write_start_current_row(
1718                sheet,
1719                cur_row,
1720                cur_row_repeat,
1721                backward_delta_col,
1722                &mut row_group_count,
1723                &mut row_header,
1724                xml_out,
1725            )?;
1726        }
1727
1728        // Remove no longer usefull cell-spans.
1729        remove_outlived(&mut spans, cur_row, cur_col);
1730
1731        // Split the current cell in visible/hidden ranges.
1732        split_hidden(&spans, cur_row, cur_col, cur_col_repeat, &mut split);
1733
1734        // Maybe span, only if visible. That nicely eliminates all double hides.
1735        // Only check for the start cell in case of repeat.
1736        if let Some(span) = cell.extra.as_ref().map(|v| v.span) {
1737            if !split[0].hidden && (span.row_span > 1 || span.col_span > 1) {
1738                spans.push(CellRange::origin_span(cur_row, cur_col, span.into()));
1739            }
1740        }
1741
1742        // And now to something completely different ...
1743        for s in &split {
1744            write_cell(book, cell, s.hidden, s.repeat(), xml_out)?;
1745        }
1746
1747        // There may be some blank cells until the next one.
1748        if forward_delta_row > 0 {
1749            // Write empty cells to fill up to the max used column.
1750            // If there is some overlap with repeat, it can be ignored.
1751            let synth_delta_col = forward_delta_col.saturating_sub(cur_col_repeat);
1752            if synth_delta_col > 0 {
1753                split_hidden(
1754                    &spans,
1755                    cur_row,
1756                    cur_col + cur_col_repeat,
1757                    synth_delta_col,
1758                    &mut split,
1759                );
1760                for s in &split {
1761                    write_empty_cells(s.hidden, s.repeat(), xml_out)?;
1762                }
1763            }
1764        } else if forward_delta_col > 0 {
1765            // Write empty cells unto the next cell with data.
1766            // Fail on overlap with repeat.
1767            if forward_delta_col < cur_col_repeat {
1768                return Err(OdsError::Ods(format!(
1769                    "{}: col-repeat of {} for row/col {}/{} overlaps with the following cell.",
1770                    sheet.name, cur_col_repeat, cur_row, cur_col,
1771                )));
1772            }
1773            let synth_delta_col = forward_delta_col - cur_col_repeat;
1774            if synth_delta_col > 0 {
1775                split_hidden(
1776                    &spans,
1777                    cur_row,
1778                    cur_col + cur_col_repeat,
1779                    synth_delta_col,
1780                    &mut split,
1781                );
1782                for s in &split {
1783                    write_empty_cells(s.hidden, s.repeat(), xml_out)?;
1784                }
1785            }
1786        }
1787
1788        // The last cell we will write? We can close the last row here,
1789        // where we have all the data.
1790        if is_last_cell {
1791            write_end_last_row(&mut row_group_count, &mut row_header, xml_out)?;
1792        }
1793
1794        first_cell = false;
1795        prev_row = cur_row;
1796        prev_row_repeat = cur_row_repeat;
1797        prev_col = cur_col;
1798    }
1799
1800    xml_out.end_elem("table:table")?;
1801
1802    for tag in &sheet.extra {
1803        if tag.name() == "table:named-expressions" || tag.name() == "calcext:conditional-formats" {
1804            write_xmltag(tag, xml_out)?;
1805        }
1806    }
1807
1808    Ok(())
1809}
1810
1811fn write_empty_cells(
1812    hidden: bool,
1813    repeat: u32,
1814    xml_out: &mut OdsXmlWriter<'_>,
1815) -> Result<(), OdsError> {
1816    if hidden {
1817        xml_out.empty("table:covered-table-cell")?;
1818    } else {
1819        xml_out.empty("table:table-cell")?;
1820    }
1821    if repeat > 1 {
1822        xml_out.attr("table:number-columns-repeated", &repeat)?;
1823    }
1824
1825    Ok(())
1826}
1827
1828fn write_start_current_row(
1829    sheet: &Sheet,
1830    cur_row: u32,
1831    cur_row_repeat: u32,
1832    backward_delta_col: u32,
1833    row_group_count: &mut u32,
1834    row_header: &mut bool,
1835    xml_out: &mut OdsXmlWriter<'_>,
1836) -> Result<(), OdsError> {
1837    // groups
1838    for row_group in &sheet.group_rows {
1839        if row_group.from() == cur_row {
1840            *row_group_count += 1;
1841            xml_out.elem("table:table-row-group")?;
1842            if !row_group.display() {
1843                xml_out.attr_str("table:display", "false")?;
1844            }
1845        }
1846        // extra: if the group would start within our repeat range, it's started
1847        //        at the current row instead.
1848        if row_group.from() > cur_row && row_group.from() < cur_row + cur_row_repeat {
1849            *row_group_count += 1;
1850            xml_out.elem("table:table-row-group")?;
1851            if !row_group.display() {
1852                xml_out.attr_str("table:display", "false")?;
1853            }
1854        }
1855    }
1856
1857    // print-header
1858    if let Some(header_rows) = &sheet.header_rows {
1859        if header_rows.from >= cur_row && header_rows.from < cur_row + cur_row_repeat {
1860            *row_header = true;
1861        }
1862    }
1863    if *row_header {
1864        xml_out.elem("table:table-header-rows")?;
1865    }
1866
1867    // row
1868    xml_out.elem("table:table-row")?;
1869    if let Some(row_header) = sheet.valid_row_header(cur_row) {
1870        if row_header.repeat > 1 {
1871            xml_out.attr_esc("table:number-rows-repeated", &row_header.repeat)?;
1872        }
1873        if let Some(rowstyle) = row_header.style.as_ref() {
1874            xml_out.attr_esc("table:style-name", rowstyle.as_str())?;
1875        }
1876        if let Some(cellstyle) = row_header.cellstyle.as_ref() {
1877            xml_out.attr_esc("table:default-cell-style-name", cellstyle.as_str())?;
1878        }
1879        if row_header.visible != Visibility::Visible {
1880            xml_out.attr_esc("table:visibility", &row_header.visible)?;
1881        }
1882    }
1883
1884    // Might not be the first column in this row.
1885    if backward_delta_col > 0 {
1886        xml_out.empty("table:table-cell")?;
1887        if backward_delta_col > 1 {
1888            xml_out.attr_esc("table:number-columns-repeated", &backward_delta_col)?;
1889        }
1890    }
1891
1892    Ok(())
1893}
1894
1895fn write_end_prev_row(
1896    sheet: &Sheet,
1897    last_r: u32,
1898    last_r_repeat: u32,
1899    row_group_count: &mut u32,
1900    row_header: &mut bool,
1901    xml_out: &mut OdsXmlWriter<'_>,
1902) -> Result<(), OdsError> {
1903    // row
1904    xml_out.end_elem("table:table-row")?;
1905    if *row_header {
1906        xml_out.end_elem("table:table-header-rows")?;
1907    }
1908
1909    // end of the print-header
1910    if let Some(header_rows) = &sheet.header_rows {
1911        if header_rows.to >= last_r && header_rows.to < last_r + last_r_repeat {
1912            *row_header = false;
1913        }
1914    }
1915
1916    // groups
1917    for row_group in &sheet.group_rows {
1918        if row_group.to() == last_r {
1919            *row_group_count -= 1;
1920            xml_out.end_elem("table:table-row-group")?;
1921        }
1922        // the group end is somewhere inside the repeated range.
1923        if row_group.to() > last_r && row_group.to() < last_r + last_r_repeat {
1924            *row_group_count -= 1;
1925            xml_out.end_elem("table:table-row-group")?;
1926        }
1927    }
1928
1929    Ok(())
1930}
1931
1932fn write_end_last_row(
1933    row_group_count: &mut u32,
1934    row_header: &mut bool,
1935    xml_out: &mut OdsXmlWriter<'_>,
1936) -> Result<(), OdsError> {
1937    // row
1938    xml_out.end_elem("table:table-row")?;
1939
1940    // end of the print-header.
1941    // todo: might loose some empty rows?
1942    if *row_header {
1943        *row_header = false;
1944        xml_out.end_elem("table:table-header-rows")?;
1945    }
1946
1947    // close all groups
1948    while *row_group_count > 0 {
1949        *row_group_count -= 1;
1950        xml_out.end_elem("table:table-row-group")?;
1951    }
1952
1953    Ok(())
1954}
1955
1956fn write_empty_rows_before(
1957    sheet: &Sheet,
1958    last_row: u32,
1959    last_row_repeat: u32,
1960    max_cell: (u32, u32),
1961    row_group_count: &mut u32,
1962    row_header: &mut bool,
1963    xml_out: &mut OdsXmlWriter<'_>,
1964) -> Result<(), OdsError> {
1965    // Are there any row groups? Then we don't use repeat but write everything out.
1966    if !sheet.group_rows.is_empty() || sheet.header_rows.is_some() {
1967        for r in last_row..last_row + last_row_repeat {
1968            if *row_header {
1969                xml_out.end_elem("table:table-header-rows")?;
1970            }
1971            // end of the print-header
1972            if let Some(header_rows) = &sheet.header_rows {
1973                if header_rows.to == r {
1974                    *row_header = false;
1975                }
1976            }
1977            // groups
1978            for row_group in &sheet.group_rows {
1979                if row_group.to() == r {
1980                    *row_group_count -= 1;
1981                    xml_out.end_elem("table:table-row-group")?;
1982                }
1983            }
1984            for row_group in &sheet.group_rows {
1985                if row_group.from() == r {
1986                    *row_group_count += 1;
1987                    xml_out.elem("table:table-row-group")?;
1988                    if !row_group.display() {
1989                        xml_out.attr_str("table:display", "false")?;
1990                    }
1991                }
1992            }
1993            // start of print-header
1994            if let Some(header_rows) = &sheet.header_rows {
1995                if header_rows.from == r {
1996                    *row_header = true;
1997                }
1998            }
1999            if *row_header {
2000                xml_out.elem("table:table-header-rows")?;
2001            }
2002            // row
2003            write_empty_row(sheet, r, 1, max_cell, xml_out)?;
2004        }
2005    } else {
2006        write_empty_row(sheet, last_row, last_row_repeat, max_cell, xml_out)?;
2007    }
2008
2009    Ok(())
2010}
2011
2012fn write_empty_row(
2013    sheet: &Sheet,
2014    cur_row: u32,
2015    row_repeat: u32,
2016    max_cell: (u32, u32),
2017    xml_out: &mut OdsXmlWriter<'_>,
2018) -> Result<(), OdsError> {
2019    xml_out.elem("table:table-row")?;
2020    xml_out.attr("table:number-rows-repeated", &row_repeat)?;
2021    if let Some(row_header) = sheet.valid_row_header(cur_row) {
2022        if let Some(rowstyle) = row_header.style.as_ref() {
2023            xml_out.attr_esc("table:style-name", rowstyle.as_str())?;
2024        }
2025        if let Some(cellstyle) = row_header.cellstyle.as_ref() {
2026            xml_out.attr_esc("table:default-cell-style-name", cellstyle.as_str())?;
2027        }
2028        if row_header.visible != Visibility::Visible {
2029            xml_out.attr_esc("table:visibility", &row_header.visible)?;
2030        }
2031    }
2032
2033    // We fill the empty spaces completely up to max columns.
2034    xml_out.empty("table:table-cell")?;
2035    xml_out.attr("table:number-columns-repeated", &max_cell.1)?;
2036
2037    xml_out.end_elem("table:table-row")?;
2038
2039    Ok(())
2040}
2041
2042fn write_table_columns(
2043    sheet: &Sheet,
2044    max_cell: (u32, u32),
2045    xml_out: &mut OdsXmlWriter<'_>,
2046) -> Result<(), OdsError> {
2047    // determine column count
2048    let mut max_col = max_cell.1;
2049    for grp in &sheet.group_cols {
2050        max_col = max(max_col, grp.to + 1);
2051    }
2052    if let Some(header_cols) = &sheet.header_cols {
2053        max_col = max(max_col, header_cols.to + 1);
2054    }
2055
2056    // table:table-column
2057    let mut c = 0;
2058    loop {
2059        if c >= max_col {
2060            break;
2061        }
2062
2063        for grp in &sheet.group_cols {
2064            if c == grp.from() {
2065                xml_out.elem("table:table-column-group")?;
2066                if !grp.display() {
2067                    xml_out.attr_str("table:display", "false")?;
2068                }
2069            }
2070        }
2071
2072        // print-header columns
2073        if let Some(header_cols) = &sheet.header_cols {
2074            if c >= header_cols.from && c <= header_cols.to {
2075                xml_out.elem("table:table-header-columns")?;
2076            }
2077        }
2078
2079        xml_out.empty("table:table-column")?;
2080        let span = if let Some(col_header) = sheet.col_header.get(&c) {
2081            if col_header.span > 1 {
2082                xml_out.attr_esc("table:number-columns-repeated", &col_header.span)?;
2083            }
2084            if let Some(style) = col_header.style.as_ref() {
2085                xml_out.attr_esc("table:style-name", style.as_str())?;
2086            }
2087            if let Some(style) = col_header.cellstyle.as_ref() {
2088                xml_out.attr_esc("table:default-cell-style-name", style.as_str())?;
2089            }
2090            if col_header.visible != Visibility::Visible {
2091                xml_out.attr_esc("table:visibility", &col_header.visible)?;
2092            }
2093
2094            col_header.span
2095        } else {
2096            1
2097        };
2098
2099        if let Some(header_cols) = &sheet.header_cols {
2100            if c >= header_cols.from && c <= header_cols.to {
2101                xml_out.end_elem("table:table-header-columns")?;
2102            }
2103        }
2104
2105        for col_group in &sheet.group_cols {
2106            if c == col_group.to() {
2107                xml_out.end_elem("table:table-column-group")?;
2108            }
2109        }
2110
2111        debug_assert!(span > 0);
2112        c += span;
2113    }
2114
2115    Ok(())
2116}
2117
2118#[allow(clippy::single_char_add_str)]
2119fn write_cell(
2120    book: &WorkBook,
2121    cell: &CellData,
2122    is_hidden: bool,
2123    repeat: u32,
2124    xml_out: &mut OdsXmlWriter<'_>,
2125) -> Result<(), OdsError> {
2126    let tag = if is_hidden {
2127        "table:covered-table-cell"
2128    } else {
2129        "table:table-cell"
2130    };
2131
2132    let has_subs = cell.value != Value::Empty || cell.has_annotation() || cell.has_draw_frames();
2133    xml_out.elem_if(has_subs, tag)?;
2134
2135    if let Some(formula) = &cell.formula {
2136        xml_out.attr_esc("table:formula", formula)?;
2137    }
2138
2139    if repeat > 1 {
2140        xml_out.attr_esc("table:number-columns-repeated", &repeat)?;
2141    }
2142
2143    // Direct style oder value based default style.
2144    if let Some(style) = &cell.style {
2145        xml_out.attr_esc("table:style-name", style.as_str())?;
2146    } else if let Some(style) = book.def_style(cell.value.value_type()) {
2147        xml_out.attr_esc("table:style-name", style.as_str())?;
2148    }
2149
2150    // Content validation
2151    if let Some(validation_name) = cell.extra.as_ref().and_then(|v| v.validation_name.as_ref()) {
2152        xml_out.attr_esc("table:content-validation-name", validation_name.as_str())?;
2153    }
2154
2155    // Spans
2156    if let Some(span) = cell.extra.as_ref().map(|v| v.span) {
2157        if span.row_span > 1 {
2158            xml_out.attr_esc("table:number-rows-spanned", &span.row_span)?;
2159        }
2160        if span.col_span > 1 {
2161            xml_out.attr_esc("table:number-columns-spanned", &span.col_span)?;
2162        }
2163    }
2164    if let Some(span) = cell.extra.as_ref().map(|v| v.matrix_span) {
2165        if span.row_span > 1 {
2166            xml_out.attr_esc("table:number-matrix-rows-spanned", &span.row_span)?;
2167        }
2168        if span.col_span > 1 {
2169            xml_out.attr_esc("table:number-matrix-columns-spanned", &span.col_span)?;
2170        }
2171    }
2172
2173    // This finds the correct ValueFormat, but there is no way to use it.
2174    // Falls back to: Output the same string as needed for the value-attribute
2175    // and hope for the best. Seems to work well enough.
2176    //
2177    // let valuestyle = if let Some(style_name) = cell.style {
2178    //     book.find_value_format(style_name)
2179    // } else {
2180    //     None
2181    // };
2182
2183    match &cell.value {
2184        Value::Empty => {}
2185        Value::Text(s) => {
2186            xml_out.attr_str("office:value-type", "string")?;
2187            for l in s.split('\n') {
2188                xml_out.elem_text_esc("text:p", l)?;
2189            }
2190        }
2191        Value::TextXml(t) => {
2192            xml_out.attr_str("office:value-type", "string")?;
2193            for tt in t.iter() {
2194                write_xmltag(tt, xml_out)?;
2195            }
2196        }
2197        Value::DateTime(d) => {
2198            xml_out.attr_str("office:value-type", "date")?;
2199            let value = d.format(DATETIME_FORMAT);
2200            xml_out.attr("office:date-value", &value)?;
2201            xml_out.elem("text:p")?;
2202            xml_out.text(&value)?;
2203            xml_out.end_elem("text:p")?;
2204        }
2205        Value::TimeDuration(d) => {
2206            xml_out.attr_str("office:value-type", "time")?;
2207            let value = format_duration2(*d);
2208            xml_out.attr("office:time-value", &value)?;
2209            xml_out.elem("text:p")?;
2210            xml_out.text(&value)?;
2211            xml_out.end_elem("text:p")?;
2212        }
2213        Value::Boolean(b) => {
2214            xml_out.attr_str("office:value-type", "boolean")?;
2215            xml_out.attr_str("office:boolean-value", if *b { "true" } else { "false" })?;
2216            xml_out.elem("text:p")?;
2217            xml_out.text_str(if *b { "true" } else { "false" })?;
2218            xml_out.end_elem("text:p")?;
2219        }
2220        Value::Currency(v, c) => {
2221            xml_out.attr_str("office:value-type", "currency")?;
2222            xml_out.attr_esc("office:currency", c)?;
2223            xml_out.attr("office:value", v)?;
2224            xml_out.elem("text:p")?;
2225            xml_out.text_esc(c)?;
2226            xml_out.text_str(" ")?;
2227            xml_out.text(v)?;
2228            xml_out.end_elem("text:p")?;
2229        }
2230        Value::Number(v) => {
2231            xml_out.attr_str("office:value-type", "float")?;
2232            xml_out.attr("office:value", v)?;
2233            xml_out.elem("text:p")?;
2234            xml_out.text(v)?;
2235            xml_out.end_elem("text:p")?;
2236        }
2237        Value::Percentage(v) => {
2238            xml_out.attr_str("office:value-type", "percentage")?;
2239            xml_out.attr("office:value", v)?;
2240            xml_out.elem("text:p")?;
2241            xml_out.text(v)?;
2242            xml_out.end_elem("text:p")?;
2243        }
2244    }
2245
2246    if let Some(annotation) = cell.extra.as_ref().and_then(|v| v.annotation.as_ref()) {
2247        write_annotation(annotation, xml_out)?;
2248    }
2249
2250    if let Some(draw_frames) = cell.extra.as_ref().map(|v| &v.draw_frames) {
2251        for draw_frame in draw_frames {
2252            write_draw_frame(draw_frame, xml_out)?;
2253        }
2254    }
2255
2256    xml_out.end_elem_if(has_subs, tag)?;
2257
2258    Ok(())
2259}
2260
2261fn write_draw_frame(
2262    draw_frame: &DrawFrame,
2263    xml_out: &mut OdsXmlWriter<'_>,
2264) -> Result<(), OdsError> {
2265    xml_out.elem("draw:frame")?;
2266    for (k, v) in draw_frame.attrmap().iter() {
2267        xml_out.attr_esc(k.as_ref(), v)?;
2268    }
2269
2270    for content in draw_frame.content_ref() {
2271        match content {
2272            DrawFrameContent::Image(img) => {
2273                write_draw_image(img, xml_out)?;
2274            }
2275        }
2276    }
2277
2278    if let Some(desc) = draw_frame.desc() {
2279        xml_out.elem("svg:desc")?;
2280        xml_out.text_esc(desc)?;
2281        xml_out.end_elem("svg:desc")?;
2282    }
2283    if let Some(title) = draw_frame.title() {
2284        xml_out.elem("svg:title")?;
2285        xml_out.text_esc(title)?;
2286        xml_out.end_elem("svg:title")?;
2287    }
2288
2289    xml_out.end_elem("draw:frame")?;
2290
2291    Ok(())
2292}
2293
2294fn write_draw_image(
2295    draw_image: &DrawImage,
2296    xml_out: &mut OdsXmlWriter<'_>,
2297) -> Result<(), OdsError> {
2298    xml_out.elem("draw:image")?;
2299    for (k, v) in draw_image.attrmap().iter() {
2300        xml_out.attr_esc(k.as_ref(), v)?;
2301    }
2302
2303    if let Some(bin) = draw_image.get_binary_base64() {
2304        xml_out.elem("office:binary-data")?;
2305        xml_out.text(bin)?;
2306        xml_out.end_elem("office:binary-data")?;
2307    }
2308
2309    for content in draw_image.get_text() {
2310        write_xmltag(content, xml_out)?;
2311    }
2312
2313    xml_out.end_elem("draw:image")?;
2314
2315    Ok(())
2316}
2317
2318fn write_annotation(
2319    annotation: &Annotation,
2320    xml_out: &mut OdsXmlWriter<'_>,
2321) -> Result<(), OdsError> {
2322    xml_out.elem("office:annotation")?;
2323    xml_out.attr("office:display", &annotation.display())?;
2324    xml_out.attr_esc("office:name", &annotation.name())?;
2325    for (k, v) in annotation.attrmap().iter() {
2326        xml_out.attr_esc(k.as_ref(), v)?;
2327    }
2328
2329    if let Some(creator) = annotation.creator() {
2330        xml_out.elem("dc:creator")?;
2331        xml_out.text_esc(creator.as_str())?;
2332        xml_out.end_elem("dc:creator")?;
2333    }
2334    if let Some(date) = annotation.date() {
2335        xml_out.elem("dc:date")?;
2336        xml_out.text_esc(&date.format(DATETIME_FORMAT))?;
2337        xml_out.end_elem("dc:date")?;
2338    }
2339    for v in annotation.text() {
2340        write_xmltag(v, xml_out)?;
2341    }
2342    xml_out.end_elem("office:annotation")?;
2343    Ok(())
2344}
2345
2346fn write_scripts(scripts: &Vec<Script>, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2347    for script in scripts {
2348        xml_out.elem("office:script")?;
2349        xml_out.attr_esc("script:language", &script.script_lang)?;
2350
2351        for content in &script.script {
2352            write_xmlcontent(content, xml_out)?;
2353        }
2354
2355        xml_out.end_elem("/office:script")?;
2356    }
2357
2358    Ok(())
2359}
2360
2361fn write_event_listeners(
2362    events: &HashMap<String, EventListener>,
2363    xml_out: &mut OdsXmlWriter<'_>,
2364) -> Result<(), OdsError> {
2365    for event in events.values() {
2366        xml_out.empty("script:event-listener")?;
2367        xml_out.attr_esc("script:event-name", &event.event_name)?;
2368        xml_out.attr_esc("script:language", &event.script_lang)?;
2369        xml_out.attr_esc("script:macro-name", &event.macro_name)?;
2370        xml_out.attr_esc("xlink:actuate", &event.actuate)?;
2371        xml_out.attr_esc("xlink:href", &event.href)?;
2372        xml_out.attr_esc("xlink:type", &event.link_type)?;
2373    }
2374
2375    Ok(())
2376}
2377
2378fn write_office_font_face_decls(
2379    book: &WorkBook,
2380    origin: StyleOrigin,
2381    xml_out: &mut OdsXmlWriter<'_>,
2382) -> Result<(), OdsError> {
2383    xml_out.elem_if(!book.fonts.is_empty(), "office:font-face-decls")?;
2384    write_style_font_face(&book.fonts, origin, xml_out)?;
2385    xml_out.end_elem_if(!book.fonts.is_empty(), "office:font-face-decls")?;
2386    Ok(())
2387}
2388
2389fn write_style_font_face(
2390    fonts: &HashMap<String, FontFaceDecl>,
2391    origin: StyleOrigin,
2392    xml_out: &mut OdsXmlWriter<'_>,
2393) -> Result<(), OdsError> {
2394    for font in fonts.values().filter(|s| s.origin() == origin) {
2395        xml_out.empty("style:font-face")?;
2396        xml_out.attr_esc("style:name", font.name())?;
2397        for (a, v) in font.attrmap().iter() {
2398            xml_out.attr_esc(a.as_ref(), v)?;
2399        }
2400    }
2401    Ok(())
2402}
2403
2404fn write_office_styles(
2405    book: &WorkBook,
2406    origin: StyleOrigin,
2407    xml_out: &mut OdsXmlWriter<'_>,
2408) -> Result<(), OdsError> {
2409    xml_out.elem("office:styles")?;
2410    write_styles(book, origin, StyleUse::Default, xml_out)?;
2411    write_styles(book, origin, StyleUse::Named, xml_out)?;
2412    write_valuestyles(book, origin, StyleUse::Named, xml_out)?;
2413    write_valuestyles(book, origin, StyleUse::Default, xml_out)?;
2414    xml_out.end_elem("office:styles")?;
2415    Ok(())
2416}
2417
2418fn write_office_automatic_styles(
2419    book: &WorkBook,
2420    origin: StyleOrigin,
2421    xml_out: &mut OdsXmlWriter<'_>,
2422) -> Result<(), OdsError> {
2423    xml_out.elem("office:automatic-styles")?;
2424    write_pagestyles(&book.pagestyles, xml_out)?;
2425    write_styles(book, origin, StyleUse::Automatic, xml_out)?;
2426    write_valuestyles(book, origin, StyleUse::Automatic, xml_out)?;
2427    xml_out.end_elem("office:automatic-styles")?;
2428    Ok(())
2429}
2430
2431fn write_office_master_styles(
2432    book: &WorkBook,
2433    xml_out: &mut OdsXmlWriter<'_>,
2434) -> Result<(), OdsError> {
2435    xml_out.elem("office:master-styles")?;
2436    write_masterpage(&book.masterpages, xml_out)?;
2437    xml_out.end_elem("office:master-styles")?;
2438    Ok(())
2439}
2440
2441fn write_styles(
2442    book: &WorkBook,
2443    origin: StyleOrigin,
2444    styleuse: StyleUse,
2445    xml_out: &mut OdsXmlWriter<'_>,
2446) -> Result<(), OdsError> {
2447    for style in book.colstyles.values() {
2448        if style.origin() == origin && style.styleuse() == styleuse {
2449            write_colstyle(style, xml_out)?;
2450        }
2451    }
2452    for style in book.rowstyles.values() {
2453        if style.origin() == origin && style.styleuse() == styleuse {
2454            write_rowstyle(style, xml_out)?;
2455        }
2456    }
2457    for style in book.tablestyles.values() {
2458        if style.origin() == origin && style.styleuse() == styleuse {
2459            write_tablestyle(style, xml_out)?;
2460        }
2461    }
2462    for style in book.cellstyles.values() {
2463        if style.origin() == origin && style.styleuse() == styleuse {
2464            write_cellstyle(style, xml_out)?;
2465        }
2466    }
2467    for style in book.paragraphstyles.values() {
2468        if style.origin() == origin && style.styleuse() == styleuse {
2469            write_paragraphstyle(style, xml_out)?;
2470        }
2471    }
2472    for style in book.textstyles.values() {
2473        if style.origin() == origin && style.styleuse() == styleuse {
2474            write_textstyle(style, xml_out)?;
2475        }
2476    }
2477    for style in book.rubystyles.values() {
2478        if style.origin() == origin && style.styleuse() == styleuse {
2479            write_rubystyle(style, xml_out)?;
2480        }
2481    }
2482    for style in book.graphicstyles.values() {
2483        if style.origin() == origin && style.styleuse() == styleuse {
2484            write_graphicstyle(style, xml_out)?;
2485        }
2486    }
2487
2488    // if let Some(stylemaps) = style.stylemaps() {
2489    //     for sm in stylemaps {
2490    //         xml_out.empty("style:map")?;
2491    //         xml_out.attr_esc("style:condition", sm.condition())?;
2492    //         xml_out.attr_esc("style:apply-style-name", sm.applied_style())?;
2493    //         xml_out.attr_esc("style:base-cell-address", &sm.base_cell().to_string())?;
2494    //     }
2495    // }
2496
2497    Ok(())
2498}
2499
2500fn write_tablestyle(style: &TableStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2501    let is_empty = style.tablestyle().is_empty();
2502
2503    if style.styleuse() == StyleUse::Default {
2504        xml_out.elem_if(!is_empty, "style:default-style")?;
2505    } else {
2506        xml_out.elem_if(!is_empty, "style:style")?;
2507        xml_out.attr_esc("style:name", style.name())?;
2508    }
2509    xml_out.attr_str("style:family", "table")?;
2510    for (a, v) in style.attrmap().iter() {
2511        match a.as_ref() {
2512            "style:name" => {}
2513            "style:family" => {}
2514            _ => {
2515                xml_out.attr_esc(a.as_ref(), v)?;
2516            }
2517        }
2518    }
2519
2520    if !style.tablestyle().is_empty() {
2521        xml_out.empty("style:table-properties")?;
2522        for (a, v) in style.tablestyle().iter() {
2523            xml_out.attr_esc(a.as_ref(), v)?;
2524        }
2525    }
2526    if style.styleuse() == StyleUse::Default {
2527        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2528    } else {
2529        xml_out.end_elem_if(!is_empty, "style:style")?;
2530    }
2531
2532    Ok(())
2533}
2534
2535fn write_rowstyle(style: &RowStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2536    let is_empty = style.rowstyle().is_empty();
2537
2538    if style.styleuse() == StyleUse::Default {
2539        xml_out.elem_if(!is_empty, "style:default-style")?;
2540    } else {
2541        xml_out.elem_if(!is_empty, "style:style")?;
2542        xml_out.attr_esc("style:name", style.name())?;
2543    }
2544    xml_out.attr_str("style:family", "table-row")?;
2545    for (a, v) in style.attrmap().iter() {
2546        match a.as_ref() {
2547            "style:name" => {}
2548            "style:family" => {}
2549            _ => {
2550                xml_out.attr_esc(a.as_ref(), v)?;
2551            }
2552        }
2553    }
2554
2555    if !style.rowstyle().is_empty() {
2556        xml_out.empty("style:table-row-properties")?;
2557        for (a, v) in style.rowstyle().iter() {
2558            xml_out.attr_esc(a.as_ref(), v)?;
2559        }
2560    }
2561    if style.styleuse() == StyleUse::Default {
2562        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2563    } else {
2564        xml_out.end_elem_if(!is_empty, "style:style")?;
2565    }
2566
2567    Ok(())
2568}
2569
2570fn write_colstyle(style: &ColStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2571    let is_empty = style.colstyle().is_empty();
2572
2573    if style.styleuse() == StyleUse::Default {
2574        xml_out.elem_if(!is_empty, "style:default-style")?;
2575    } else {
2576        xml_out.elem_if(!is_empty, "style:style")?;
2577        xml_out.attr_esc("style:name", style.name())?;
2578    }
2579    xml_out.attr_str("style:family", "table-column")?;
2580    for (a, v) in style.attrmap().iter() {
2581        match a.as_ref() {
2582            "style:name" => {}
2583            "style:family" => {}
2584            _ => {
2585                xml_out.attr_esc(a.as_ref(), v)?;
2586            }
2587        }
2588    }
2589
2590    if !style.colstyle().is_empty() {
2591        xml_out.empty("style:table-column-properties")?;
2592        for (a, v) in style.colstyle().iter() {
2593            xml_out.attr_esc(a.as_ref(), v)?;
2594        }
2595    }
2596    if style.styleuse() == StyleUse::Default {
2597        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2598    } else {
2599        xml_out.end_elem_if(!is_empty, "style:style")?;
2600    }
2601
2602    Ok(())
2603}
2604
2605fn write_cellstyle(style: &CellStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2606    let is_empty = style.cellstyle().is_empty()
2607        && style.paragraphstyle().is_empty()
2608        && style.textstyle().is_empty()
2609        && style.stylemaps().is_none();
2610
2611    if style.styleuse() == StyleUse::Default {
2612        xml_out.elem_if(!is_empty, "style:default-style")?;
2613    } else {
2614        xml_out.elem_if(!is_empty, "style:style")?;
2615        xml_out.attr_esc("style:name", style.name())?;
2616    }
2617    xml_out.attr_str("style:family", "table-cell")?;
2618    for (a, v) in style.attrmap().iter() {
2619        match a.as_ref() {
2620            "style:name" => {}
2621            "style:family" => {}
2622            _ => {
2623                xml_out.attr_esc(a.as_ref(), v)?;
2624            }
2625        }
2626    }
2627
2628    if !style.cellstyle().is_empty() {
2629        xml_out.empty("style:table-cell-properties")?;
2630        for (a, v) in style.cellstyle().iter() {
2631            xml_out.attr_esc(a.as_ref(), v)?;
2632        }
2633    }
2634    if !style.paragraphstyle().is_empty() {
2635        xml_out.empty("style:paragraph-properties")?;
2636        for (a, v) in style.paragraphstyle().iter() {
2637            xml_out.attr_esc(a.as_ref(), v)?;
2638        }
2639    }
2640    if !style.textstyle().is_empty() {
2641        xml_out.empty("style:text-properties")?;
2642        for (a, v) in style.textstyle().iter() {
2643            xml_out.attr_esc(a.as_ref(), v)?;
2644        }
2645    }
2646    if let Some(stylemaps) = style.stylemaps() {
2647        for sm in stylemaps {
2648            xml_out.empty("style:map")?;
2649            xml_out.attr_esc("style:condition", sm.condition())?;
2650            xml_out.attr_esc("style:apply-style-name", sm.applied_style().as_str())?;
2651            if let Some(r) = sm.base_cell() {
2652                xml_out.attr_esc("style:base-cell-address", r)?;
2653            }
2654        }
2655    }
2656    if style.styleuse() == StyleUse::Default {
2657        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2658    } else {
2659        xml_out.end_elem_if(!is_empty, "style:style")?;
2660    }
2661
2662    Ok(())
2663}
2664
2665fn write_paragraphstyle(
2666    style: &ParagraphStyle,
2667    xml_out: &mut OdsXmlWriter<'_>,
2668) -> Result<(), OdsError> {
2669    let is_empty = style.paragraphstyle().is_empty() && style.textstyle().is_empty();
2670
2671    if style.styleuse() == StyleUse::Default {
2672        xml_out.elem_if(!is_empty, "style:default-style")?;
2673    } else {
2674        xml_out.elem_if(!is_empty, "style:style")?;
2675        xml_out.attr_esc("style:name", style.name())?;
2676    }
2677    xml_out.attr_str("style:family", "paragraph")?;
2678    for (a, v) in style.attrmap().iter() {
2679        match a.as_ref() {
2680            "style:name" => {}
2681            "style:family" => {}
2682            _ => {
2683                xml_out.attr_esc(a.as_ref(), v)?;
2684            }
2685        }
2686    }
2687
2688    if !style.paragraphstyle().is_empty() {
2689        if style.tabstops().is_none() {
2690            xml_out.empty("style:paragraph-properties")?;
2691            for (a, v) in style.paragraphstyle().iter() {
2692                xml_out.attr_esc(a.as_ref(), v)?;
2693            }
2694        } else {
2695            xml_out.elem("style:paragraph-properties")?;
2696            for (a, v) in style.paragraphstyle().iter() {
2697                xml_out.attr_esc(a.as_ref(), v)?;
2698            }
2699            xml_out.elem("style:tab-stops")?;
2700            if let Some(tabstops) = style.tabstops() {
2701                for ts in tabstops {
2702                    xml_out.empty("style:tab-stop")?;
2703                    for (a, v) in ts.attrmap().iter() {
2704                        xml_out.attr_esc(a.as_ref(), v)?;
2705                    }
2706                }
2707            }
2708            xml_out.end_elem("style:tab-stops")?;
2709            xml_out.end_elem("style:paragraph-properties")?;
2710        }
2711    }
2712    if !style.textstyle().is_empty() {
2713        xml_out.empty("style:text-properties")?;
2714        for (a, v) in style.textstyle().iter() {
2715            xml_out.attr_esc(a.as_ref(), v)?;
2716        }
2717    }
2718    if style.styleuse() == StyleUse::Default {
2719        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2720    } else {
2721        xml_out.end_elem_if(!is_empty, "style:style")?;
2722    }
2723
2724    Ok(())
2725}
2726
2727fn write_textstyle(style: &TextStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2728    let is_empty = style.textstyle().is_empty();
2729
2730    if style.styleuse() == StyleUse::Default {
2731        xml_out.elem_if(!is_empty, "style:default-style")?;
2732    } else {
2733        xml_out.elem_if(!is_empty, "style:style")?;
2734        xml_out.attr_esc("style:name", style.name())?;
2735    }
2736    xml_out.attr_str("style:family", "text")?;
2737    for (a, v) in style.attrmap().iter() {
2738        match a.as_ref() {
2739            "style:name" => {}
2740            "style:family" => {}
2741            _ => {
2742                xml_out.attr_esc(a.as_ref(), v)?;
2743            }
2744        }
2745    }
2746
2747    if !style.textstyle().is_empty() {
2748        xml_out.empty("style:text-properties")?;
2749        for (a, v) in style.textstyle().iter() {
2750            xml_out.attr_esc(a.as_ref(), v)?;
2751        }
2752    }
2753    if style.styleuse() == StyleUse::Default {
2754        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2755    } else {
2756        xml_out.end_elem_if(!is_empty, "style:style")?;
2757    }
2758
2759    Ok(())
2760}
2761
2762fn write_rubystyle(style: &RubyStyle, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
2763    let is_empty = style.rubystyle().is_empty();
2764
2765    if style.styleuse() == StyleUse::Default {
2766        xml_out.elem_if(!is_empty, "style:default-style")?;
2767    } else {
2768        xml_out.elem_if(!is_empty, "style:style")?;
2769        xml_out.attr_esc("style:name", style.name())?;
2770    }
2771    xml_out.attr_str("style:family", "ruby")?;
2772    for (a, v) in style.attrmap().iter() {
2773        match a.as_ref() {
2774            "style:name" => {}
2775            "style:family" => {}
2776            _ => {
2777                xml_out.attr_esc(a.as_ref(), v)?;
2778            }
2779        }
2780    }
2781
2782    if !style.rubystyle().is_empty() {
2783        xml_out.empty("style:ruby-properties")?;
2784        for (a, v) in style.rubystyle().iter() {
2785            xml_out.attr_esc(a.as_ref(), v)?;
2786        }
2787    }
2788    if style.styleuse() == StyleUse::Default {
2789        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2790    } else {
2791        xml_out.end_elem_if(!is_empty, "style:style")?;
2792    }
2793
2794    Ok(())
2795}
2796
2797fn write_graphicstyle(
2798    style: &GraphicStyle,
2799    xml_out: &mut OdsXmlWriter<'_>,
2800) -> Result<(), OdsError> {
2801    let is_empty = style.graphicstyle().is_empty()
2802        && style.paragraphstyle().is_empty()
2803        && style.textstyle().is_empty();
2804
2805    if style.styleuse() == StyleUse::Default {
2806        xml_out.elem_if(!is_empty, "style:default-style")?;
2807    } else {
2808        xml_out.elem_if(!is_empty, "style:style")?;
2809        xml_out.attr_esc("style:name", style.name())?;
2810    }
2811    xml_out.attr_str("style:family", "graphic")?;
2812    for (a, v) in style.attrmap().iter() {
2813        match a.as_ref() {
2814            "style:name" => {}
2815            "style:family" => {}
2816            _ => {
2817                xml_out.attr_esc(a.as_ref(), v)?;
2818            }
2819        }
2820    }
2821
2822    if !style.graphicstyle().is_empty() {
2823        xml_out.empty("style:graphic-properties")?;
2824        for (a, v) in style.graphicstyle().iter() {
2825            xml_out.attr_esc(a.as_ref(), v)?;
2826        }
2827    }
2828    if !style.paragraphstyle().is_empty() {
2829        xml_out.empty("style:paragraph-properties")?;
2830        for (a, v) in style.paragraphstyle().iter() {
2831            xml_out.attr_esc(a.as_ref(), v)?;
2832        }
2833    }
2834    if !style.textstyle().is_empty() {
2835        xml_out.empty("style:text-properties")?;
2836        for (a, v) in style.textstyle().iter() {
2837            xml_out.attr_esc(a.as_ref(), v)?;
2838        }
2839    }
2840
2841    if style.styleuse() == StyleUse::Default {
2842        xml_out.end_elem_if(!is_empty, "style:default-style")?;
2843    } else {
2844        xml_out.end_elem_if(!is_empty, "style:style")?;
2845    }
2846
2847    Ok(())
2848}
2849
2850fn write_valuestyles(
2851    book: &WorkBook,
2852    origin: StyleOrigin,
2853    style_use: StyleUse,
2854    xml_out: &mut OdsXmlWriter<'_>,
2855) -> Result<(), OdsError> {
2856    write_valuestyle(&book.formats_boolean, origin, style_use, xml_out)?;
2857    write_valuestyle(&book.formats_currency, origin, style_use, xml_out)?;
2858    write_valuestyle(&book.formats_datetime, origin, style_use, xml_out)?;
2859    write_valuestyle(&book.formats_number, origin, style_use, xml_out)?;
2860    write_valuestyle(&book.formats_percentage, origin, style_use, xml_out)?;
2861    write_valuestyle(&book.formats_text, origin, style_use, xml_out)?;
2862    write_valuestyle(&book.formats_timeduration, origin, style_use, xml_out)?;
2863    Ok(())
2864}
2865
2866fn write_valuestyle<T: ValueFormatTrait>(
2867    value_formats: &HashMap<String, T>,
2868    origin: StyleOrigin,
2869    styleuse: StyleUse,
2870    xml_out: &mut OdsXmlWriter<'_>,
2871) -> Result<(), OdsError> {
2872    for value_format in value_formats
2873        .values()
2874        .filter(|s| s.origin() == origin && s.styleuse() == styleuse)
2875    {
2876        let tag = match value_format.value_type() {
2877            ValueType::Empty => unreachable!(),
2878            ValueType::Boolean => "number:boolean-style",
2879            ValueType::Number => "number:number-style",
2880            ValueType::Text => "number:text-style",
2881            ValueType::TextXml => "number:text-style",
2882            ValueType::TimeDuration => "number:time-style",
2883            ValueType::Percentage => "number:percentage-style",
2884            ValueType::Currency => "number:currency-style",
2885            ValueType::DateTime => "number:date-style",
2886        };
2887
2888        xml_out.elem(tag)?;
2889        xml_out.attr_esc("style:name", value_format.name())?;
2890        for (a, v) in value_format.attrmap().iter() {
2891            xml_out.attr_esc(a.as_ref(), v)?;
2892        }
2893
2894        if !value_format.textstyle().is_empty() {
2895            xml_out.empty("style:text-properties")?;
2896            for (a, v) in value_format.textstyle().iter() {
2897                xml_out.attr_esc(a.as_ref(), v)?;
2898            }
2899        }
2900
2901        for part in value_format.parts() {
2902            let part_tag = match part.part_type() {
2903                FormatPartType::Boolean => "number:boolean",
2904                FormatPartType::Number => "number:number",
2905                FormatPartType::ScientificNumber => "number:scientific-number",
2906                FormatPartType::CurrencySymbol => "number:currency-symbol",
2907                FormatPartType::Day => "number:day",
2908                FormatPartType::Month => "number:month",
2909                FormatPartType::Year => "number:year",
2910                FormatPartType::Era => "number:era",
2911                FormatPartType::DayOfWeek => "number:day-of-week",
2912                FormatPartType::WeekOfYear => "number:week-of-year",
2913                FormatPartType::Quarter => "number:quarter",
2914                FormatPartType::Hours => "number:hours",
2915                FormatPartType::Minutes => "number:minutes",
2916                FormatPartType::Seconds => "number:seconds",
2917                FormatPartType::Fraction => "number:fraction",
2918                FormatPartType::AmPm => "number:am-pm",
2919                FormatPartType::Text => "number:text",
2920                FormatPartType::TextContent => "number:text-content",
2921                FormatPartType::FillCharacter => "number:fill-character",
2922            };
2923
2924            if part.part_type() == FormatPartType::Text
2925                || part.part_type() == FormatPartType::CurrencySymbol
2926                || part.part_type() == FormatPartType::FillCharacter
2927            {
2928                let content = part.content().filter(|v| !v.is_empty());
2929                xml_out.elem_if(content.is_some(), part_tag)?;
2930                for (a, v) in part.attrmap().iter() {
2931                    xml_out.attr_esc(a.as_ref(), v)?;
2932                }
2933                if let Some(content) = content {
2934                    xml_out.text_esc(content)?;
2935                }
2936                xml_out.end_elem_if(content.is_some(), part_tag)?;
2937            } else if part.part_type() == FormatPartType::Number {
2938                if let Some(position) = part.position() {
2939                    xml_out.elem(part_tag)?;
2940                    for (a, v) in part.attrmap().iter() {
2941                        xml_out.attr_esc(a.as_ref(), v)?;
2942                    }
2943
2944                    // embedded text
2945                    if let Some(content) = part.content() {
2946                        xml_out.elem("number:embedded-text")?;
2947                        xml_out.attr_esc("number:position", &position)?;
2948                        xml_out.text_esc(content)?;
2949                        xml_out.end_elem("number:embedded-text")?;
2950                    } else {
2951                        xml_out.empty("number:embedded-text")?;
2952                        xml_out.attr_esc("number:position", &position)?;
2953                    }
2954
2955                    xml_out.end_elem(part_tag)?;
2956                } else {
2957                    xml_out.empty(part_tag)?;
2958                    for (a, v) in part.attrmap().iter() {
2959                        xml_out.attr_esc(a.as_ref(), v)?;
2960                    }
2961                }
2962            } else {
2963                xml_out.empty(part_tag)?;
2964                for (a, v) in part.attrmap().iter() {
2965                    xml_out.attr_esc(a.as_ref(), v)?;
2966                }
2967            }
2968        }
2969
2970        if let Some(stylemaps) = value_format.stylemaps() {
2971            for sm in stylemaps {
2972                xml_out.empty("style:map")?;
2973                xml_out.attr_esc("style:condition", sm.condition())?;
2974                xml_out.attr_esc("style:apply-style-name", sm.applied_style())?;
2975            }
2976        }
2977
2978        xml_out.end_elem(tag)?;
2979    }
2980
2981    Ok(())
2982}
2983
2984fn write_pagestyles(
2985    styles: &HashMap<PageStyleRef, PageStyle>,
2986    xml_out: &mut OdsXmlWriter<'_>,
2987) -> Result<(), OdsError> {
2988    for style in styles.values() {
2989        xml_out.elem("style:page-layout")?;
2990        xml_out.attr_esc("style:name", style.name())?;
2991        if let Some(master_page_usage) = &style.master_page_usage {
2992            xml_out.attr_esc("style:page-usage", master_page_usage)?;
2993        }
2994
2995        if !style.style().is_empty() {
2996            xml_out.empty("style:page-layout-properties")?;
2997            for (k, v) in style.style().iter() {
2998                xml_out.attr_esc(k.as_ref(), v)?;
2999            }
3000        }
3001
3002        xml_out.elem("style:header-style")?;
3003        xml_out.empty("style:header-footer-properties")?;
3004        if !style.headerstyle().style().is_empty() {
3005            for (k, v) in style.headerstyle().style().iter() {
3006                xml_out.attr_esc(k.as_ref(), v)?;
3007            }
3008        }
3009        xml_out.end_elem("style:header-style")?;
3010
3011        xml_out.elem("style:footer-style")?;
3012        xml_out.empty("style:header-footer-properties")?;
3013        if !style.footerstyle().style().is_empty() {
3014            for (k, v) in style.footerstyle().style().iter() {
3015                xml_out.attr_esc(k.as_ref(), v)?;
3016            }
3017        }
3018        xml_out.end_elem("style:footer-style")?;
3019
3020        xml_out.end_elem("style:page-layout")?;
3021    }
3022
3023    Ok(())
3024}
3025
3026fn write_masterpage(
3027    masterpages: &HashMap<MasterPageRef, MasterPage>,
3028    xml_out: &mut OdsXmlWriter<'_>,
3029) -> Result<(), OdsError> {
3030    for masterpage in masterpages.values() {
3031        xml_out.elem("style:master-page")?;
3032        xml_out.attr_esc("style:name", masterpage.name())?;
3033        if !masterpage.display_name().is_empty() {
3034            xml_out.attr_esc("style:display-name", masterpage.display_name())?;
3035        }
3036        if let Some(style) = masterpage.pagestyle() {
3037            xml_out.attr_esc("style:page-layout-name", style.as_str())?;
3038        }
3039        if let Some(next) = masterpage.next_masterpage() {
3040            xml_out.attr_esc("style:next-style-name", next.as_str())?;
3041        }
3042
3043        // header
3044        xml_out.elem_if(!masterpage.header().is_empty(), "style:header")?;
3045        if !masterpage.header().display() {
3046            xml_out.attr_str("style:display", "false")?;
3047        }
3048        write_regions(masterpage.header(), xml_out)?;
3049        xml_out.end_elem_if(!masterpage.header().is_empty(), "style:header")?;
3050
3051        xml_out.elem_if(!masterpage.header_first().is_empty(), "style:header-first")?;
3052        if !masterpage.header_first().display() || masterpage.header_first().is_empty() {
3053            xml_out.attr_str("style:display", "false")?;
3054        }
3055        write_regions(masterpage.header_first(), xml_out)?;
3056        xml_out.end_elem_if(!masterpage.header_first().is_empty(), "style:header-first")?;
3057
3058        xml_out.elem_if(!masterpage.header_left().is_empty(), "style:header-left")?;
3059        if !masterpage.header_left().display() || masterpage.header_left().is_empty() {
3060            xml_out.attr_str("style:display", "false")?;
3061        }
3062        write_regions(masterpage.header_left(), xml_out)?;
3063        xml_out.end_elem_if(!masterpage.header_left().is_empty(), "style:header-left")?;
3064
3065        // footer
3066        xml_out.elem_if(!masterpage.footer().is_empty(), "style:footer")?;
3067        if !masterpage.footer().display() {
3068            xml_out.attr_str("style:display", "false")?;
3069        }
3070        write_regions(masterpage.footer(), xml_out)?;
3071        xml_out.end_elem_if(!masterpage.footer().is_empty(), "style:footer")?;
3072
3073        xml_out.elem_if(!masterpage.footer_first().is_empty(), "style:footer-first")?;
3074        if !masterpage.footer_first().display() || masterpage.footer_first().is_empty() {
3075            xml_out.attr_str("style:display", "false")?;
3076        }
3077        write_regions(masterpage.footer_first(), xml_out)?;
3078        xml_out.end_elem_if(!masterpage.footer_first().is_empty(), "style:footer-first")?;
3079
3080        xml_out.elem_if(!masterpage.footer_left().is_empty(), "style:footer-left")?;
3081        if !masterpage.footer_left().display() || masterpage.footer_left().is_empty() {
3082            xml_out.attr_str("style:display", "false")?;
3083        }
3084        write_regions(masterpage.footer_left(), xml_out)?;
3085        xml_out.end_elem_if(!masterpage.footer_left().is_empty(), "style:footer-left")?;
3086
3087        xml_out.end_elem("style:master-page")?;
3088    }
3089
3090    Ok(())
3091}
3092
3093fn write_regions(hf: &HeaderFooter, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
3094    if !hf.left().is_empty() {
3095        xml_out.elem("style:region-left")?;
3096        for v in hf.left() {
3097            write_xmltag(v, xml_out)?;
3098        }
3099        xml_out.end_elem("style:region-left")?;
3100    }
3101    if !hf.center().is_empty() {
3102        xml_out.elem("style:region-center")?;
3103        for v in hf.center() {
3104            write_xmltag(v, xml_out)?;
3105        }
3106        xml_out.end_elem("style:region-center")?;
3107    }
3108    if !hf.right().is_empty() {
3109        xml_out.elem("style:region-right")?;
3110        for v in hf.right() {
3111            write_xmltag(v, xml_out)?;
3112        }
3113        xml_out.end_elem("style:region-right")?;
3114    }
3115    for content in hf.content() {
3116        write_xmltag(content, xml_out)?;
3117    }
3118
3119    Ok(())
3120}
3121
3122fn write_xmlcontent(x: &XmlContent, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
3123    match x {
3124        XmlContent::Text(t) => {
3125            xml_out.text_esc(t)?;
3126        }
3127        XmlContent::Tag(t) => {
3128            write_xmltag(t, xml_out)?;
3129        }
3130    }
3131    Ok(())
3132}
3133
3134fn write_xmltag(x: &XmlTag, xml_out: &mut OdsXmlWriter<'_>) -> Result<(), OdsError> {
3135    xml_out.elem_if(!x.is_empty(), x.name())?;
3136
3137    for (k, v) in x.attrmap().iter() {
3138        xml_out.attr_esc(k.as_ref(), v)?;
3139    }
3140
3141    for c in x.content() {
3142        match c {
3143            XmlContent::Text(t) => {
3144                xml_out.text_esc(t)?;
3145            }
3146            XmlContent::Tag(t) => {
3147                write_xmltag(t, xml_out)?;
3148            }
3149        }
3150    }
3151
3152    xml_out.end_elem_if(!x.is_empty(), x.name())?;
3153
3154    Ok(())
3155}
3156
3157// All extra entries from the manifest.
3158fn write_ods_extra<W: Write + Seek>(
3159    cfg: &OdsWriteOptions,
3160    zip_writer: &mut ZipWriter<W>,
3161    book: &WorkBook,
3162) -> Result<(), OdsError> {
3163    for manifest in book.manifest.values() {
3164        if !matches!(
3165            manifest.full_path.as_str(),
3166            "/" | "settings.xml" | "styles.xml" | "content.xml" | "meta.xml"
3167        ) {
3168            if manifest.is_dir() {
3169                zip_writer.add_directory(&manifest.full_path, FileOptions::<()>::default())?;
3170            } else {
3171                zip_writer.start_file(
3172                    manifest.full_path.as_str(),
3173                    FileOptions::<()>::default()
3174                        .compression_method(cfg.method)
3175                        .compression_level(cfg.level),
3176                )?;
3177                if let Some(buf) = &manifest.buffer {
3178                    zip_writer.write_all(buf.as_slice())?;
3179                }
3180            }
3181        }
3182    }
3183
3184    Ok(())
3185}