Add fldchar and instrtext (#381)

* feat: Add fieldCharater

* feat: Add instrText

* feat: Add toc inst

* wip

* feat: impl minimum toc write with rust
main
bokuweb 2021-12-06 00:28:31 +09:00 committed by GitHub
parent caa61e9947
commit 1850ac2018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 317 additions and 0 deletions

View File

@ -0,0 +1,18 @@
use docx_rs::*;
pub fn main() -> Result<(), DocxError> {
let path = std::path::Path::new("./output/dirty_toc.docx");
let file = std::fs::File::create(&path).unwrap();
let p = Paragraph::new()
.add_run(Run::new().add_text("Hello"))
.style("Heading1")
.page_break_before(true);
let style = Style::new("Heading1", StyleType::Paragraph).name("Heading 1");
Docx::new()
.add_style(style)
.add_table_of_contents(TableOfContents::new().heading_styles_range(1, 3))
.add_paragraph(p)
.build()
.pack(file)?;
Ok(())
}

View File

@ -22,6 +22,7 @@ pub enum DocumentChild {
CommentStart(Box<CommentRangeStart>), CommentStart(Box<CommentRangeStart>),
CommentEnd(CommentRangeEnd), CommentEnd(CommentRangeEnd),
StructuredDataTag(StructuredDataTag), StructuredDataTag(StructuredDataTag),
TableOfContents(TableOfContents),
} }
impl Serialize for DocumentChild { impl Serialize for DocumentChild {
@ -72,6 +73,12 @@ impl Serialize for DocumentChild {
t.serialize_field("data", r)?; t.serialize_field("data", r)?;
t.end() t.end()
} }
DocumentChild::TableOfContents(ref r) => {
let mut t = serializer.serialize_struct("TableOfContents", 2)?;
t.serialize_field("type", "tableOfContents")?;
t.serialize_field("data", r)?;
t.end()
}
} }
} }
} }
@ -194,6 +201,11 @@ impl Document {
self.children.push(DocumentChild::StructuredDataTag(t)); self.children.push(DocumentChild::StructuredDataTag(t));
self self
} }
pub fn add_table_of_contents(mut self, t: TableOfContents) -> Self {
self.children.push(DocumentChild::TableOfContents(t));
self
}
} }
impl BuildXML for DocumentChild { impl BuildXML for DocumentChild {
@ -206,6 +218,7 @@ impl BuildXML for DocumentChild {
DocumentChild::CommentStart(v) => v.build(), DocumentChild::CommentStart(v) => v.build(),
DocumentChild::CommentEnd(v) => v.build(), DocumentChild::CommentEnd(v) => v.build(),
DocumentChild::StructuredDataTag(v) => v.build(), DocumentChild::StructuredDataTag(v) => v.build(),
DocumentChild::TableOfContents(v) => v.build(),
} }
} }
} }
@ -243,6 +256,22 @@ mod tests {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?> r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w14 wp14"> <w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w14 wp14">
<w:body><w:p w14:paraId="12345678"><w:pPr><w:rPr /></w:pPr><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r></w:p><w:sectPr><w:pgSz w:w="11906" w:h="16838" /><w:pgMar w:top="1985" w:right="1701" w:bottom="1701" w:left="1701" w:header="851" w:footer="992" w:gutter="0" /><w:cols w:space="425" /><w:docGrid w:type="lines" w:linePitch="360" /></w:sectPr></w:body> <w:body><w:p w14:paraId="12345678"><w:pPr><w:rPr /></w:pPr><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r></w:p><w:sectPr><w:pgSz w:w="11906" w:h="16838" /><w:pgMar w:top="1985" w:right="1701" w:bottom="1701" w:left="1701" w:header="851" w:footer="992" w:gutter="0" /><w:cols w:space="425" /><w:docGrid w:type="lines" w:linePitch="360" /></w:sectPr></w:body>
</w:document>"#
);
}
#[test]
fn test_document_with_toc() {
let toc = TableOfContents::new().heading_styles_range(1, 3);
let b = Document::new().add_table_of_contents(toc).build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w14 wp14">
<w:body><w:sdt>
<w:sdtPr />
<w:sdtContent><w:p w14:paraId="12345678"><w:pPr><w:rPr /></w:pPr><w:r><w:rPr /><w:fldChar w:fldCharType="begin" w:dirty="true" /><w:instrText>TOC \o &quot;1-3&quot;</w:instrText><w:fldChar w:fldCharType="separate" w:dirty="false" /></w:r></w:p><w:p w14:paraId="12345678"><w:pPr><w:rPr /></w:pPr><w:r><w:rPr /><w:fldChar w:fldCharType="end" w:dirty="false" /></w:r></w:p></w:sdtContent>
</w:sdt><w:sectPr><w:pgSz w:w="11906" w:h="16838" /><w:pgMar w:top="1985" w:right="1701" w:bottom="1701" w:left="1701" w:header="851" w:footer="992" w:gutter="0" /><w:cols w:space="425" /><w:docGrid w:type="lines" w:linePitch="360" /></w:sectPr></w:body>
</w:document>"# </w:document>"#
); );
} }

View File

@ -0,0 +1,54 @@
use serde::Serialize;
use crate::documents::*;
use crate::types::*;
use crate::xml_builder::*;
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct FieldChar {
pub field_char_type: FieldCharType,
pub dirty: bool,
}
impl FieldChar {
pub fn new(t: FieldCharType) -> Self {
Self {
field_char_type: t,
dirty: false,
}
}
pub fn dirty(mut self) -> Self {
self.dirty = true;
self
}
}
impl BuildXML for FieldChar {
fn build(&self) -> Vec<u8> {
XMLBuilder::new()
.field_character(
&format!("{}", self.field_char_type),
&format!("{}", &self.dirty),
)
.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::str;
#[test]
fn test_field_character() {
let b = FieldChar::new(FieldCharType::Begin).dirty().build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:fldChar w:fldCharType="begin" w:dirty="true" />"#
);
}
}

View File

@ -0,0 +1,43 @@
use serde::Serialize;
use crate::documents::*;
use crate::xml_builder::*;
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct InstrText {
pub val: String,
}
impl InstrText {
pub fn new(i: impl Into<String>) -> Self {
Self { val: i.into() }
}
}
impl BuildXML for InstrText {
fn build(&self) -> Vec<u8> {
XMLBuilder::new()
.open_instr_text()
.plain_text(&self.val)
.close()
.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::str;
#[test]
fn test_toc_instr() {
let b = InstrText::new(r#"ToC \o "1-3""#).build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:instrText>ToC \o "1-3"</w:instrText>"#
);
}
}

View File

@ -22,6 +22,7 @@ mod doc_grid;
mod doc_id; mod doc_id;
mod doc_var; mod doc_var;
mod drawing; mod drawing;
mod fld_char;
mod font; mod font;
mod footer_reference; mod footer_reference;
mod grid_span; mod grid_span;
@ -30,6 +31,7 @@ mod highlight;
mod indent; mod indent;
mod indent_level; mod indent_level;
mod insert; mod insert;
mod instr_text;
mod italic; mod italic;
mod italic_cs; mod italic_cs;
mod justification; mod justification;
@ -77,6 +79,7 @@ mod table_cell_width;
mod table_grid; mod table_grid;
mod table_indent; mod table_indent;
mod table_layout; mod table_layout;
mod table_of_contents;
mod table_property; mod table_property;
mod table_row; mod table_row;
mod table_row_property; mod table_row_property;
@ -120,6 +123,7 @@ pub use doc_grid::*;
pub use doc_id::*; pub use doc_id::*;
pub use doc_var::*; pub use doc_var::*;
pub use drawing::*; pub use drawing::*;
pub use fld_char::*;
pub use font::*; pub use font::*;
pub use footer_reference::*; pub use footer_reference::*;
pub use grid_span::*; pub use grid_span::*;
@ -128,6 +132,7 @@ pub use highlight::*;
pub use indent::*; pub use indent::*;
pub use indent_level::*; pub use indent_level::*;
pub use insert::*; pub use insert::*;
pub use instr_text::*;
pub use italic::*; pub use italic::*;
pub use italic_cs::*; pub use italic_cs::*;
pub use justification::*; pub use justification::*;
@ -175,6 +180,7 @@ pub use table_cell_width::*;
pub use table_grid::*; pub use table_grid::*;
pub use table_indent::*; pub use table_indent::*;
pub use table_layout::*; pub use table_layout::*;
pub use table_of_contents::*;
pub use table_property::*; pub use table_property::*;
pub use table_row::*; pub use table_row::*;
pub use table_row_property::*; pub use table_row_property::*;

View File

@ -32,6 +32,8 @@ pub enum RunChild {
Drawing(Box<Drawing>), Drawing(Box<Drawing>),
CommentStart(Box<CommentRangeStart>), CommentStart(Box<CommentRangeStart>),
CommentEnd(CommentRangeEnd), CommentEnd(CommentRangeEnd),
FieldChar(FieldChar),
InstrText(InstrText),
} }
impl Serialize for RunChild { impl Serialize for RunChild {
@ -81,6 +83,18 @@ impl Serialize for RunChild {
t.serialize_field("data", r)?; t.serialize_field("data", r)?;
t.end() t.end()
} }
RunChild::FieldChar(ref f) => {
let mut t = serializer.serialize_struct("FieldChar", 2)?;
t.serialize_field("type", "fieldChar")?;
t.serialize_field("data", f)?;
t.end()
}
RunChild::InstrText(ref i) => {
let mut t = serializer.serialize_struct("InstrText", 2)?;
t.serialize_field("type", "instrText")?;
t.serialize_field("data", i)?;
t.end()
}
} }
} }
} }
@ -103,6 +117,20 @@ impl Run {
self self
} }
pub fn add_field_char(mut self, t: crate::types::FieldCharType, dirty: bool) -> Run {
let mut f = FieldChar::new(t);
if dirty {
f = f.dirty();
};
self.children.push(RunChild::FieldChar(f));
self
}
pub fn add_instr_text(mut self, i: impl Into<String>) -> Run {
self.children.push(RunChild::InstrText(InstrText::new(i)));
self
}
pub fn add_tab(mut self) -> Run { pub fn add_tab(mut self) -> Run {
self.children.push(RunChild::Tab(Tab::new())); self.children.push(RunChild::Tab(Tab::new()));
self self
@ -216,6 +244,8 @@ impl BuildXML for Run {
RunChild::Drawing(t) => b = b.add_child(t), RunChild::Drawing(t) => b = b.add_child(t),
RunChild::CommentStart(c) => b = b.add_child(c), RunChild::CommentStart(c) => b = b.add_child(c),
RunChild::CommentEnd(c) => b = b.add_child(c), RunChild::CommentEnd(c) => b = b.add_child(c),
RunChild::FieldChar(c) => b = b.add_child(c),
RunChild::InstrText(c) => b = b.add_child(c),
} }
} }
b.close().build() b.close().build()

View File

@ -0,0 +1,79 @@
use serde::Serialize;
use crate::documents::*;
use crate::types::*;
use crate::xml_builder::*;
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TOCTOC_topic_ID0ELZO1.html
#[derive(Serialize, Debug, Clone, PartialEq, Default)]
pub struct TableOfContents {
// If no heading range is specified, all heading levels used in the document are listed.
heading_styles_range: Option<(usize, usize)>,
}
impl TableOfContents {
pub fn new() -> Self {
Self::default()
}
pub fn heading_styles_range(mut self, start: usize, end: usize) -> Self {
self.heading_styles_range = Some((start, end));
self
}
fn build_instr_text(&self) -> String {
let mut instr = "TOC".to_string();
if let Some(heading_styles_range) = self.heading_styles_range {
instr = format!(
"{} \\o &quot;{}-{}&quot;",
instr, heading_styles_range.0, heading_styles_range.1
);
}
instr
}
}
impl BuildXML for TableOfContents {
fn build(&self) -> Vec<u8> {
let p1 = Paragraph::new().add_run(
Run::new()
.add_field_char(FieldCharType::Begin, true)
.add_instr_text(self.build_instr_text())
.add_field_char(FieldCharType::Separate, false),
);
let p2 = Paragraph::new().add_run(Run::new().add_field_char(FieldCharType::End, false));
XMLBuilder::new()
.open_structured_tag()
.open_structured_tag_property()
.close()
.open_structured_tag_content()
.add_child(&p1)
.add_child(&p2)
.close()
.close()
.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::str;
#[test]
fn test_toc() {
let b = TableOfContents::new().heading_styles_range(1, 3).build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:sdt>
<w:sdtPr />
<w:sdtContent><w:p w14:paraId="12345678"><w:pPr><w:rPr /></w:pPr><w:r><w:rPr /><w:fldChar w:fldCharType="begin" w:dirty="true" /><w:instrText>TOC \o &quot;1-3&quot;</w:instrText><w:fldChar w:fldCharType="separate" w:dirty="false" /></w:r></w:p><w:p w14:paraId="12345678"><w:pPr><w:rPr /></w:pPr><w:r><w:rPr /><w:fldChar w:fldCharType="end" w:dirty="false" /></w:r></w:p></w:sdtContent>
</w:sdt>"#
);
}
}

View File

@ -155,6 +155,11 @@ impl Docx {
self self
} }
pub fn add_style(mut self, s: Style) -> Self {
self.styles = self.styles.add_style(s);
self
}
pub fn numberings(mut self, n: Numberings) -> Self { pub fn numberings(mut self, n: Numberings) -> Self {
self.numberings = n; self.numberings = n;
self self
@ -201,6 +206,11 @@ impl Docx {
self self
} }
pub fn add_table_of_contents(mut self, t: TableOfContents) -> Docx {
self.document = self.document.add_table_of_contents(t);
self
}
pub fn add_bookmark_start(mut self, id: usize, name: impl Into<String>) -> Docx { pub fn add_bookmark_start(mut self, id: usize, name: impl Into<String>) -> Docx {
self.document = self.document.add_bookmark_start(id, name); self.document = self.document.add_bookmark_start(id, name);
self self

View File

@ -0,0 +1,42 @@
use serde::{Deserialize, Serialize};
//
// Please see https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_ST_FldCharType_topic_ID0E6TU2.html#topic_ID0E6TU2
//
use std::fmt;
use std::str::FromStr;
use wasm_bindgen::prelude::*;
use super::errors;
#[wasm_bindgen]
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum FieldCharType {
Begin,
Separate,
End,
Unsupported,
}
impl fmt::Display for FieldCharType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
FieldCharType::Begin => write!(f, "begin"),
FieldCharType::Separate => write!(f, "separate"),
FieldCharType::End => write!(f, "end"),
FieldCharType::Unsupported => write!(f, "unsupported"),
}
}
}
impl FromStr for FieldCharType {
type Err = errors::TypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"begin" => Ok(FieldCharType::Begin),
"separate" => Ok(FieldCharType::Separate),
"end" => Ok(FieldCharType::End),
_ => Ok(FieldCharType::Unsupported),
}
}
}

View File

@ -5,6 +5,7 @@ pub mod break_type;
pub mod doc_grid_type; pub mod doc_grid_type;
pub mod emu; pub mod emu;
pub mod errors; pub mod errors;
pub mod field_char_type;
pub mod font_pitch_type; pub mod font_pitch_type;
pub mod height_rule; pub mod height_rule;
pub mod level_suffix_type; pub mod level_suffix_type;
@ -30,6 +31,7 @@ pub use break_type::*;
pub use doc_grid_type::*; pub use doc_grid_type::*;
pub use emu::*; pub use emu::*;
pub use errors::*; pub use errors::*;
pub use field_char_type::*;
pub use font_pitch_type::*; pub use font_pitch_type::*;
pub use height_rule::*; pub use height_rule::*;
pub use level_suffix_type::*; pub use level_suffix_type::*;

View File

@ -92,6 +92,10 @@ impl XMLBuilder {
// i.e. <w:szCs ... > // i.e. <w:szCs ... >
closed_with_usize!(sz_cs, "w:szCs"); closed_with_usize!(sz_cs, "w:szCs");
closed!(field_character, "w:fldChar", "w:fldCharType", "w:dirty");
open!(open_instr_text, "w:instrText");
closed_with_str!(text_direction, "w:textDirection"); closed_with_str!(text_direction, "w:textDirection");
closed!(b, "w:b"); closed!(b, "w:b");