Add reader (#33)
* feat: Add contenttypes and rel deserializer * feat: Add reader fundation * feat: Add run reader * feat: Add run reader * feat: Add paragraph reader foundation * fix:test * feat: Add book mark * fix:bookmark id * feat: Add bookmark reader * feat: Add paragraph json fondation * fix:fix reader * feat: Add table reader foundation * feat: Add table reader foundation * feat: Add table cell prop serializer * chore: rename content to children * feat: Add row serializer * feat: Add table property serializer * fix: lint error * feat: Add table reader * feat: readermain
parent
6c7dad56e5
commit
5fe4e4d646
|
@ -1,5 +1,10 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
|
@ -79,10 +84,12 @@ name = "docx-rs"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasm-bindgen 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zip 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -114,6 +121,17 @@ dependencies = [
|
|||
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.29"
|
||||
|
@ -127,6 +145,11 @@ dependencies = [
|
|||
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.30"
|
||||
|
@ -158,6 +181,14 @@ name = "memchr"
|
|||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
|
@ -228,11 +259,44 @@ name = "rustc-demangle"
|
|||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sourcefile"
|
||||
version = "0.1.4"
|
||||
|
@ -442,14 +506,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
|
||||
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
|
@ -462,13 +528,16 @@ dependencies = [
|
|||
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
|
||||
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
|
||||
"checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f"
|
||||
"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
|
||||
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
|
||||
"checksum js-sys 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "a60f6ca5eb7ae3014e3ab34e3189a1560267245216e19f76a021a4c669817e62"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
|
||||
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
||||
"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
|
||||
"checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd"
|
||||
|
@ -478,7 +547,11 @@ dependencies = [
|
|||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
|
||||
"checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b"
|
||||
"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
|
||||
"checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c"
|
||||
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
|
@ -503,4 +576,4 @@ dependencies = [
|
|||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
"checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5"
|
||||
"checksum zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3c21bb410afa2bd823a047f5bda3adb62f51074ac7e06263b2c97ecdd47e9fc6"
|
||||
"checksum zip 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e41ff37ba788e2169b19fa70253b70cb53d9f2db9fb9aea9bcfc5047e02c3bae"
|
||||
|
|
|
@ -23,8 +23,9 @@ path = "src/lib.rs"
|
|||
xml-rs = "0.8.0"
|
||||
wasm-bindgen = "0.2.50"
|
||||
thiserror = "1.0"
|
||||
zip = { version = "0.5", default-features = false }
|
||||
zip = { version = "0.5.4", default-features = false, features = ["deflate"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6.1"
|
||||
|
||||
|
|
|
@ -9,20 +9,20 @@ pub fn main() -> Result<(), DocxError> {
|
|||
.add_paragraph(
|
||||
Paragraph::new()
|
||||
.add_run(Run::new().add_text(DUMMY))
|
||||
.indent(840, None),
|
||||
.indent(840, None, None),
|
||||
)
|
||||
.add_paragraph(Paragraph::new())
|
||||
.add_paragraph(
|
||||
Paragraph::new()
|
||||
.add_run(Run::new().add_text(DUMMY))
|
||||
.indent(840, Some(SpecialIndentType::FirstLine(720))),
|
||||
)
|
||||
.add_paragraph(Paragraph::new().add_run(Run::new().add_text(DUMMY)).indent(
|
||||
840,
|
||||
Some(SpecialIndentType::FirstLine(720)),
|
||||
None,
|
||||
))
|
||||
.add_paragraph(Paragraph::new())
|
||||
.add_paragraph(
|
||||
Paragraph::new()
|
||||
.add_run(Run::new().add_text(DUMMY))
|
||||
.indent(1560, Some(SpecialIndentType::Hanging(720))),
|
||||
)
|
||||
.add_paragraph(Paragraph::new().add_run(Run::new().add_text(DUMMY)).indent(
|
||||
1560,
|
||||
Some(SpecialIndentType::Hanging(720)),
|
||||
None,
|
||||
))
|
||||
.build()
|
||||
.pack(file)?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use docx_rs::*;
|
||||
|
||||
|
||||
pub fn main() -> Result<(), DocxError> {
|
||||
let path = std::path::Path::new("./numbering.docx");
|
||||
let file = std::fs::File::create(&path).unwrap();
|
||||
|
@ -19,7 +18,7 @@ pub fn main() -> Result<(), DocxError> {
|
|||
LevelText::new("Section %1."),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(1620, Some(SpecialIndentType::Hanging(320))),
|
||||
.indent(1620, Some(SpecialIndentType::Hanging(320)), None),
|
||||
),
|
||||
)
|
||||
.build()
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use docx_rs::*;
|
||||
use std::fs::*;
|
||||
use std::io::Read;
|
||||
|
||||
pub fn main() {
|
||||
let mut file = File::open("./fixtures/paragraph/paragraph.docx").unwrap();
|
||||
let mut buf = vec![];
|
||||
file.read_to_end(&mut buf).unwrap();
|
||||
dbg!(read_docx(&buf).unwrap().json());
|
||||
}
|
|
@ -2,7 +2,10 @@ use super::Comment;
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Comments {
|
||||
comments: Vec<Comment>,
|
||||
}
|
||||
|
|
|
@ -1,67 +1,142 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::reader::{FromXML, ReaderError};
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContentTypes {}
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct ContentTypes {
|
||||
types: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ContentTypes {
|
||||
pub fn new() -> ContentTypes {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn add_content(mut self, namespace: impl Into<String>, path: impl Into<String>) -> Self {
|
||||
self.types.insert(namespace.into(), path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_default(mut self) -> ContentTypes {
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-package.relationships+xml".to_owned(),
|
||||
"/_rels/.rels".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.extended-properties+xml".to_owned(),
|
||||
"/docProps/app.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-package.core-properties+xml".to_owned(),
|
||||
"/docProps/core.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-package.relationships+xml".to_owned(),
|
||||
"/word/_rels/document.xml.rels".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"
|
||||
.to_owned(),
|
||||
"/word/settings.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"
|
||||
.to_owned(),
|
||||
"/word/fontTable.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
|
||||
.to_owned(),
|
||||
"/word/document.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml".to_owned(),
|
||||
"/word/styles.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"
|
||||
.to_owned(),
|
||||
"/word/comments.xml".to_owned(),
|
||||
);
|
||||
self.types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"
|
||||
.to_owned(),
|
||||
"/word/numbering.xml".to_owned(),
|
||||
);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContentTypes {
|
||||
fn default() -> Self {
|
||||
ContentTypes {}
|
||||
ContentTypes {
|
||||
types: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for ContentTypes {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
b.declaration(None)
|
||||
.open_types("http://schemas.openxmlformats.org/package/2006/content-types")
|
||||
.add_override(
|
||||
"/_rels/.rels",
|
||||
"application/vnd.openxmlformats-package.relationships+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/docProps/app.xml",
|
||||
"application/vnd.openxmlformats-officedocument.extended-properties+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/docProps/core.xml",
|
||||
"application/vnd.openxmlformats-package.core-properties+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/_rels/document.xml.rels",
|
||||
"application/vnd.openxmlformats-package.relationships+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/settings.xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/fontTable.xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/document.xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/styles.xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/comments.xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
|
||||
)
|
||||
.add_override(
|
||||
"/word/numbering.xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
|
||||
)
|
||||
.close()
|
||||
.build()
|
||||
let mut b = b
|
||||
.declaration(None)
|
||||
.open_types("http://schemas.openxmlformats.org/package/2006/content-types");
|
||||
for (k, v) in self.types.iter() {
|
||||
b = b.add_override(k, v);
|
||||
}
|
||||
b.close().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromXML for ContentTypes {
|
||||
fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
|
||||
let parser = EventReader::new(reader);
|
||||
let mut s = Self::default();
|
||||
let mut depth = 0;
|
||||
for e in parser {
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement { attributes, .. }) => {
|
||||
if depth == 1 {
|
||||
let namespace = attributes[0].value.clone();
|
||||
let path = attributes[1].value.clone();
|
||||
s = s.add_content(namespace, path);
|
||||
}
|
||||
depth += 1;
|
||||
}
|
||||
Ok(XmlEvent::EndElement { .. }) => {
|
||||
depth -= 1;
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_xml() {
|
||||
let xml = r#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml"></Override></Types>"#;
|
||||
let c = ContentTypes::from_xml(xml.as_bytes()).unwrap();
|
||||
let mut types = HashMap::new();
|
||||
types.insert(
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
|
||||
.to_owned(),
|
||||
"/word/document.xml".to_owned(),
|
||||
);
|
||||
assert_eq!(ContentTypes { types }, c);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppProps {}
|
||||
|
||||
impl AppProps {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CoreProps {
|
||||
config: CorePropsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CorePropsConfig {
|
||||
created: Option<String>,
|
||||
creator: Option<String>,
|
||||
|
|
|
@ -3,12 +3,16 @@ mod core;
|
|||
|
||||
pub use self::app::*;
|
||||
pub use self::core::*;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DocProps {
|
||||
app: AppProps,
|
||||
core: CoreProps,
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocProps {
|
||||
pub app: AppProps,
|
||||
pub core: CoreProps,
|
||||
}
|
||||
|
||||
impl DocProps {
|
||||
|
|
|
@ -1,20 +1,56 @@
|
|||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{Paragraph, SectionProperty, Table};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Document {
|
||||
pub(crate) children: Vec<DocumentChild>,
|
||||
pub children: Vec<DocumentChild>,
|
||||
pub section_property: SectionProperty,
|
||||
pub has_numbering: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DocumentChild {
|
||||
Paragraph(Paragraph),
|
||||
Table(Table),
|
||||
}
|
||||
|
||||
impl Serialize for DocumentChild {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
DocumentChild::Paragraph(ref p) => {
|
||||
let mut t = serializer.serialize_struct("Paragraph", 2)?;
|
||||
t.serialize_field("type", "paragraph")?;
|
||||
t.serialize_field("data", p)?;
|
||||
t.end()
|
||||
}
|
||||
DocumentChild::Table(ref c) => {
|
||||
let mut t = serializer.serialize_struct("Table", 2)?;
|
||||
t.serialize_field("type", "table")?;
|
||||
t.serialize_field("data", c)?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Document {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
children: Vec::new(),
|
||||
section_property: SectionProperty::new(),
|
||||
has_numbering: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn new() -> Document {
|
||||
Default::default()
|
||||
|
@ -37,16 +73,6 @@ impl Document {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Document {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
children: Vec::new(),
|
||||
section_property: SectionProperty::new(),
|
||||
has_numbering: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for Document {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let mut b = XMLBuilder::new()
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentRels {
|
||||
pub(crate) has_comments: bool,
|
||||
pub(crate) has_numberings: bool,
|
||||
pub has_comments: bool,
|
||||
pub has_numberings: bool,
|
||||
}
|
||||
|
||||
impl DocumentRels {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Bold {}
|
||||
|
||||
impl Bold {
|
||||
|
@ -16,6 +18,15 @@ impl Default for Bold {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Bold {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for Bold {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct BoldCs {}
|
||||
|
||||
impl BoldCs {
|
||||
|
@ -16,6 +18,15 @@ impl Default for BoldCs {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for BoldCs {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for BoldCs {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct BookmarkEnd {
|
||||
id: String,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl BookmarkEnd {
|
||||
pub fn new(id: impl Into<String>) -> BookmarkEnd {
|
||||
BookmarkEnd { id: id.into() }
|
||||
pub fn new(id: usize) -> BookmarkEnd {
|
||||
BookmarkEnd { id }
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for BookmarkEnd {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
b.bookmark_end(&self.id).build()
|
||||
b.bookmark_end(&format!("{}", self.id)).build()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,11 +31,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_bookmark_end() {
|
||||
let c = BookmarkEnd::new("mockid");
|
||||
let c = BookmarkEnd::new(0);
|
||||
let b = c.build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:bookmarkEnd w:id="mockid" />"#
|
||||
);
|
||||
assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:bookmarkEnd w:id="0" />"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct BookmarkStart {
|
||||
id: String,
|
||||
id: usize,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl BookmarkStart {
|
||||
pub fn new(id: impl Into<String>, name: impl Into<String>) -> BookmarkStart {
|
||||
pub fn new(id: usize, name: impl Into<String>) -> BookmarkStart {
|
||||
BookmarkStart {
|
||||
id: id.into(),
|
||||
id,
|
||||
name: name.into(),
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +21,8 @@ impl BookmarkStart {
|
|||
impl BuildXML for BookmarkStart {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
b.bookmark_start(&self.id, &self.name).build()
|
||||
b.bookmark_start(&format!("{}", self.id), &self.name)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,11 +36,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_bookmark_start() {
|
||||
let c = BookmarkStart::new("mockid", "mockname");
|
||||
let c = BookmarkStart::new(0, "mockname");
|
||||
let b = c.build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:bookmarkStart w:id="mockid" w:name="mockname" />"#
|
||||
r#"<w:bookmarkStart w:id="0" w:name="mockname" />"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Break {
|
||||
break_type: BreakType,
|
||||
}
|
||||
|
@ -19,3 +22,14 @@ impl BuildXML for Break {
|
|||
b.br(&self.break_type.to_string()).build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Break {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut t = serializer.serialize_struct("Break", 1)?;
|
||||
t.serialize_field("breakType", &format!("{}", &self.break_type))?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Color {
|
||||
val: String,
|
||||
}
|
||||
|
@ -18,6 +20,15 @@ impl BuildXML for Color {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Color {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::{BuildXML, Paragraph};
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct Comment {
|
||||
pub id: usize,
|
||||
pub author: String,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct CommentRangeEnd {
|
||||
id: usize,
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::Comment;
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct CommentRangeStart {
|
||||
id: usize,
|
||||
comment: Comment,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DefaultTabStop {
|
||||
val: usize,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for DefaultTabStop {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for DefaultTabStop {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u64(self.val as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::{BuildXML, HistoryId, Run};
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct Delete {
|
||||
pub author: String,
|
||||
pub date: String,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeleteText {
|
||||
text: String,
|
||||
preserve_space: bool,
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
use super::run_property_default::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocDefaults {
|
||||
run_property_default: RunPropertyDefault,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct GridSpan {
|
||||
val: usize,
|
||||
}
|
||||
|
@ -18,6 +20,15 @@ impl BuildXML for GridSpan {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for GridSpan {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u32(self.val as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Highlight {
|
||||
val: String,
|
||||
}
|
||||
|
@ -18,6 +20,15 @@ impl BuildXML for Highlight {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Highlight {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -35,4 +46,11 @@ mod tests {
|
|||
r#"<w:highlight w:val="FFFFFF" />"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_json() {
|
||||
let c = Highlight::new("FFFFFF");
|
||||
|
||||
assert_eq!(serde_json::to_string(&c).unwrap(), r#""FFFFFF""#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,61 @@
|
|||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Indent {
|
||||
left: usize,
|
||||
start: usize,
|
||||
end: Option<usize>,
|
||||
special_indent: Option<SpecialIndentType>,
|
||||
}
|
||||
|
||||
impl Indent {
|
||||
pub fn new(left: usize, special_indent: Option<SpecialIndentType>) -> Indent {
|
||||
pub fn new(
|
||||
start: usize,
|
||||
special_indent: Option<SpecialIndentType>,
|
||||
end: Option<usize>,
|
||||
) -> Indent {
|
||||
Indent {
|
||||
left,
|
||||
start,
|
||||
end,
|
||||
special_indent,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(mut self, end: usize) -> Self {
|
||||
self.end = Some(end);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for Indent {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
XMLBuilder::new()
|
||||
.indent(self.left, self.special_indent)
|
||||
.indent(
|
||||
self.start,
|
||||
self.special_indent,
|
||||
self.end.unwrap_or_default(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Indent {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut t = serializer.serialize_struct("Indent", 3)?;
|
||||
t.serialize_field("start", &self.start)?;
|
||||
t.serialize_field("end", &self.end)?;
|
||||
t.serialize_field("specialIndent", &self.special_indent)?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -35,25 +66,28 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_left() {
|
||||
let b = Indent::new(20, None).build();
|
||||
assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:ind w:left="20" />"#);
|
||||
let b = Indent::new(20, None, None).build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:ind w:left="20" w:right="0" />"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_line() {
|
||||
let b = Indent::new(20, Some(SpecialIndentType::FirstLine(40))).build();
|
||||
let b = Indent::new(20, Some(SpecialIndentType::FirstLine(40)), None).build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:ind w:left="20" w:firstLine="40" />"#
|
||||
r#"<w:ind w:left="20" w:right="0" w:firstLine="40" />"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hanging() {
|
||||
let b = Indent::new(20, Some(SpecialIndentType::Hanging(50))).build();
|
||||
let b = Indent::new(20, Some(SpecialIndentType::Hanging(50)), None).build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:ind w:left="20" w:hanging="50" />"#
|
||||
r#"<w:ind w:left="20" w:right="0" w:hanging="50" />"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct IndentLevel {
|
||||
val: usize,
|
||||
pub val: usize,
|
||||
}
|
||||
|
||||
impl IndentLevel {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::{BuildXML, HistoryId, Run};
|
||||
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct Insert {
|
||||
pub run: Run,
|
||||
pub author: String,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Italic {}
|
||||
|
||||
impl Italic {
|
||||
|
@ -16,6 +18,15 @@ impl Default for Italic {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Italic {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for Italic {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct ItalicCs {}
|
||||
|
||||
impl ItalicCs {
|
||||
|
@ -16,6 +18,15 @@ impl Default for ItalicCs {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for ItalicCs {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for ItalicCs {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
|
@ -9,9 +11,9 @@ use crate::xml_builder::*;
|
|||
// present without the val attribute, the default of the val attribute is centerGroup . This means that the instances
|
||||
// of mathematical text can be aligned with respect to each other, but the entire group of mathematical text is
|
||||
// centered as a whole.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Justification {
|
||||
val: String,
|
||||
pub val: String,
|
||||
}
|
||||
|
||||
impl Justification {
|
||||
|
@ -27,6 +29,15 @@ impl BuildXML for Justification {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Justification {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -2,13 +2,16 @@ use crate::documents::{BuildXML, LevelJc, LevelText, NumberFormat, ParagraphProp
|
|||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Level {
|
||||
level: usize,
|
||||
start: Start,
|
||||
format: NumberFormat,
|
||||
text: LevelText,
|
||||
jc: LevelJc,
|
||||
pub level: usize,
|
||||
pub start: Start,
|
||||
pub format: NumberFormat,
|
||||
pub text: LevelText,
|
||||
pub jc: LevelJc,
|
||||
pub paragraph_property: ParagraphProperty,
|
||||
}
|
||||
|
||||
|
@ -30,8 +33,13 @@ impl Level {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn indent(mut self, left: usize, special_indent: Option<SpecialIndentType>) -> Self {
|
||||
self.paragraph_property = self.paragraph_property.indent(left, special_indent);
|
||||
pub fn indent(
|
||||
mut self,
|
||||
left: usize,
|
||||
special_indent: Option<SpecialIndentType>,
|
||||
end: Option<usize>,
|
||||
) -> Self {
|
||||
self.paragraph_property = self.paragraph_property.indent(left, special_indent, end);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -83,11 +91,11 @@ mod tests {
|
|||
LevelText::new("%4."),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(320, Some(SpecialIndentType::Hanging(200)))
|
||||
.indent(320, Some(SpecialIndentType::Hanging(200)), None)
|
||||
.build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:lvl w:ilvl="1"><w:start w:val="1" /><w:numFmt w:val="decimal" /><w:lvlText w:val="%4." /><w:lvlJc w:val="left" /><w:pPr><w:pStyle w:val="Normal" /><w:rPr /><w:ind w:left="320" w:hanging="200" /></w:pPr></w:lvl>"#
|
||||
r#"<w:lvl w:ilvl="1"><w:start w:val="1" /><w:numFmt w:val="decimal" /><w:lvlText w:val="%4." /><w:lvlJc w:val="left" /><w:pPr><w:pStyle w:val="Normal" /><w:rPr /><w:ind w:left="320" w:right="0" w:hanging="200" /></w:pPr></w:lvl>"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LevelJc {
|
||||
val: String,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for LevelJc {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for LevelJc {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LevelText {
|
||||
val: String,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for LevelText {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for LevelText {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Name {
|
||||
name: String,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for Name {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NumberFormat {
|
||||
val: String,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for NumberFormat {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for NumberFormat {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::documents::{BuildXML, Level};
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Numbering {
|
||||
id: usize,
|
||||
levels: Vec<Level>,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NumberingId {
|
||||
id: usize,
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
impl NumberingId {
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{IndentLevel, NumberingId};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NumberingProperty {
|
||||
id: NumberingId,
|
||||
level: IndentLevel,
|
||||
pub id: NumberingId,
|
||||
pub level: IndentLevel,
|
||||
}
|
||||
|
||||
impl NumberingProperty {
|
||||
|
@ -25,6 +28,18 @@ impl BuildXML for NumberingProperty {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for NumberingProperty {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut t = serializer.serialize_struct("NumberProperty", 2)?;
|
||||
t.serialize_field("id", &self.id.id)?;
|
||||
t.serialize_field("level", &self.level.val)?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageMargin {
|
||||
top: usize,
|
||||
left: usize,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageSize {
|
||||
w: usize,
|
||||
h: usize,
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::*;
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Paragraph {
|
||||
pub children: Vec<ParagraphChild>,
|
||||
pub property: ParagraphProperty,
|
||||
pub has_numbering: bool,
|
||||
attrs: Vec<(String, String)>,
|
||||
pub attrs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl Default for Paragraph {
|
||||
|
@ -22,7 +26,7 @@ impl Default for Paragraph {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ParagraphChild {
|
||||
Run(Run),
|
||||
Insert(Insert),
|
||||
|
@ -47,6 +51,33 @@ impl BuildXML for ParagraphChild {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for ParagraphChild {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
ParagraphChild::Run(ref r) => {
|
||||
let mut t = serializer.serialize_struct("Run", 2)?;
|
||||
t.serialize_field("type", "run")?;
|
||||
t.serialize_field("data", r)?;
|
||||
t.end()
|
||||
}
|
||||
ParagraphChild::Insert(ref r) => {
|
||||
let mut t = serializer.serialize_struct("Insert", 2)?;
|
||||
t.serialize_field("type", "insert")?;
|
||||
t.serialize_field("data", r)?;
|
||||
t.end()
|
||||
}
|
||||
_ => {
|
||||
let mut t = serializer.serialize_struct("Unsupported", 2)?;
|
||||
t.serialize_field("type", "unsupported")?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Paragraph {
|
||||
pub fn new() -> Paragraph {
|
||||
Default::default()
|
||||
|
@ -76,17 +107,13 @@ impl Paragraph {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn add_bookmark_start(
|
||||
mut self,
|
||||
id: impl Into<String>,
|
||||
name: impl Into<String>,
|
||||
) -> Paragraph {
|
||||
pub fn add_bookmark_start(mut self, id: usize, name: impl Into<String>) -> Paragraph {
|
||||
self.children
|
||||
.push(ParagraphChild::BookmarkStart(BookmarkStart::new(id, name)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_bookmark_end(mut self, id: impl Into<String>) -> Paragraph {
|
||||
pub fn add_bookmark_end(mut self, id: usize) -> Paragraph {
|
||||
self.children
|
||||
.push(ParagraphChild::BookmarkEnd(BookmarkEnd::new(id)));
|
||||
self
|
||||
|
@ -115,8 +142,13 @@ impl Paragraph {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn indent(mut self, left: usize, special_indent: Option<SpecialIndentType>) -> Paragraph {
|
||||
self.property = self.property.indent(left, special_indent);
|
||||
pub fn indent(
|
||||
mut self,
|
||||
left: usize,
|
||||
special_indent: Option<SpecialIndentType>,
|
||||
end: Option<usize>,
|
||||
) -> Paragraph {
|
||||
self.property = self.property.indent(left, special_indent, end);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -172,13 +204,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_bookmark() {
|
||||
let b = Paragraph::new()
|
||||
.add_bookmark_start("1234-5678", "article")
|
||||
.add_bookmark_start(0, "article")
|
||||
.add_run(Run::new().add_text("Hello"))
|
||||
.add_bookmark_end("1234-5678")
|
||||
.add_bookmark_end(0)
|
||||
.build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:p><w:pPr><w:pStyle w:val="Normal" /><w:rPr /></w:pPr><w:bookmarkStart w:id="1234-5678" w:name="article" /><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r><w:bookmarkEnd w:id="1234-5678" /></w:p>"#
|
||||
r#"<w:p><w:pPr><w:pStyle w:val="Normal" /><w:rPr /></w:pPr><w:bookmarkStart w:id="0" w:name="article" /><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r><w:bookmarkEnd w:id="0" /></w:p>"#
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -212,4 +244,25 @@ mod tests {
|
|||
r#"<w:p><w:pPr><w:pStyle w:val="Normal" /><w:rPr /><w:numPr><w:numId w:val="0" /><w:ilvl w:val="1" /></w:numPr></w:pPr><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r></w:p>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_paragraph_run_json() {
|
||||
let run = Run::new().add_text("Hello");
|
||||
let p = Paragraph::new().add_run(run);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&p).unwrap(),
|
||||
r#"{"children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"style":"Normal","numberingProperty":null,"alignment":null,"indent":null},"hasNumbering":false,"attrs":[]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_paragraph_insert_json() {
|
||||
let run = Run::new().add_text("Hello");
|
||||
let ins = Insert::new(run);
|
||||
let p = Paragraph::new().add_insert(ins);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&p).unwrap(),
|
||||
r#"{"children":[{"type":"insert","data":{"run":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]},"author":"unnamed","date":"1970-01-01T00:00:00Z"}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"style":"Normal","numberingProperty":null,"alignment":null,"indent":null},"hasNumbering":false,"attrs":[]}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::*;
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::{AlignmentType, SpecialIndentType};
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParagraphProperty {
|
||||
run_property: RunProperty,
|
||||
style: ParagraphStyle,
|
||||
numbering_property: Option<NumberingProperty>,
|
||||
alignment: Option<Justification>,
|
||||
indent: Option<Indent>,
|
||||
pub run_property: RunProperty,
|
||||
pub style: ParagraphStyle,
|
||||
pub numbering_property: Option<NumberingProperty>,
|
||||
pub alignment: Option<Justification>,
|
||||
pub indent: Option<Indent>,
|
||||
}
|
||||
|
||||
impl Default for ParagraphProperty {
|
||||
|
@ -45,8 +48,13 @@ impl ParagraphProperty {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn indent(mut self, left: usize, special_indent: Option<SpecialIndentType>) -> Self {
|
||||
self.indent = Some(Indent::new(left, special_indent));
|
||||
pub fn indent(
|
||||
mut self,
|
||||
left: usize,
|
||||
special_indent: Option<SpecialIndentType>,
|
||||
end: Option<usize>,
|
||||
) -> Self {
|
||||
self.indent = Some(Indent::new(left, special_indent, end));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -101,10 +109,20 @@ mod tests {
|
|||
#[test]
|
||||
fn test_indent() {
|
||||
let c = ParagraphProperty::new();
|
||||
let b = c.indent(20, None).build();
|
||||
let b = c.indent(20, None, None).build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
r#"<w:pPr><w:pStyle w:val="Normal" /><w:rPr /><w:ind w:left="20" /></w:pPr>"#
|
||||
r#"<w:pPr><w:pStyle w:val="Normal" /><w:rPr /><w:ind w:left="20" w:right="0" /></w:pPr>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indent_json() {
|
||||
let c = ParagraphProperty::new();
|
||||
let b = c.indent(20, Some(SpecialIndentType::FirstLine(10)), None);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&b).unwrap(),
|
||||
r#"{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"style":"Normal","numberingProperty":null,"alignment":null,"indent":{"start":20,"end":null,"specialIndent":{"type":"firstLine","val":10}}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ParagraphStyle {
|
||||
val: String,
|
||||
}
|
||||
|
@ -36,6 +38,15 @@ impl BuildXML for ParagraphStyle {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for ParagraphStyle {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use super::{Break, DeleteText, RunProperty, Tab, Text};
|
||||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::BreakType;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Run {
|
||||
pub run_property: RunProperty,
|
||||
pub children: Vec<RunChild>,
|
||||
|
@ -19,7 +23,7 @@ impl Default for Run {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub enum RunChild {
|
||||
Text(Text),
|
||||
DeleteText(DeleteText),
|
||||
|
@ -27,6 +31,39 @@ pub enum RunChild {
|
|||
Break(Break),
|
||||
}
|
||||
|
||||
impl Serialize for RunChild {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
RunChild::Text(ref s) => {
|
||||
let mut t = serializer.serialize_struct("Text", 2)?;
|
||||
t.serialize_field("type", "text")?;
|
||||
t.serialize_field("data", s)?;
|
||||
t.end()
|
||||
}
|
||||
RunChild::DeleteText(ref s) => {
|
||||
let mut t = serializer.serialize_struct("DeleteText", 2)?;
|
||||
t.serialize_field("type", "deleteText")?;
|
||||
t.serialize_field("data", s)?;
|
||||
t.end()
|
||||
}
|
||||
RunChild::Tab(_) => {
|
||||
let mut t = serializer.serialize_struct("Tab", 1)?;
|
||||
t.serialize_field("type", "tab")?;
|
||||
t.end()
|
||||
}
|
||||
RunChild::Break(ref s) => {
|
||||
let mut t = serializer.serialize_struct("Break", 2)?;
|
||||
t.serialize_field("type", "break")?;
|
||||
t.serialize_field("data", s)?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Run {
|
||||
pub fn new() -> Run {
|
||||
Run {
|
||||
|
@ -34,12 +71,12 @@ impl Run {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_text(mut self, text: &str) -> Run {
|
||||
pub fn add_text(mut self, text: impl Into<String>) -> Run {
|
||||
self.children.push(RunChild::Text(Text::new(text)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_delete_text(mut self, text: &str) -> Run {
|
||||
pub fn add_delete_text(mut self, text: impl Into<String>) -> Run {
|
||||
self.children
|
||||
.push(RunChild::DeleteText(DeleteText::new(text)));
|
||||
self
|
||||
|
@ -110,6 +147,7 @@ impl BuildXML for Run {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::super::*;
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
@ -132,4 +170,41 @@ mod tests {
|
|||
r#"<w:r><w:rPr><w:u w:val="single" /></w:rPr><w:t xml:space="preserve">Hello</w:t></w:r>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_child_json() {
|
||||
let c = RunChild::Text(Text::new("Hello"));
|
||||
assert_eq!(
|
||||
serde_json::to_string(&c).unwrap(),
|
||||
r#"{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_json() {
|
||||
let run = Run {
|
||||
children: vec![
|
||||
RunChild::Tab(Tab::new()),
|
||||
RunChild::Text(Text::new("Hello")),
|
||||
RunChild::Break(Break::new(BreakType::Page)),
|
||||
RunChild::DeleteText(DeleteText::new("deleted")),
|
||||
],
|
||||
run_property: RunProperty {
|
||||
sz: Some(Sz::new(30)),
|
||||
sz_cs: Some(SzCs::new(30)),
|
||||
color: Some(Color::new("C9211E")),
|
||||
highlight: Some(Highlight::new("yellow")),
|
||||
underline: Some(Underline::new("single")),
|
||||
bold: Some(Bold::new()),
|
||||
bold_cs: Some(BoldCs::new()),
|
||||
italic: Some(Italic::new()),
|
||||
italic_cs: Some(ItalicCs::new()),
|
||||
vanish: Some(Vanish::new()),
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_string(&run).unwrap(),
|
||||
r#"{"runProperty":{"sz":30,"szCs":30,"color":"C9211E","highlight":"yellow","underline":"single","bold":true,"boldCs":true,"italic":true,"italicCs":true,"vanish":true},"children":[{"type":"tab"},{"type":"text","data":{"preserveSpace":true,"text":"Hello"}},{"type":"break","data":{"breakType":"page"}},{"type":"deleteText","data":{"text":"deleted","preserveSpace":true}}]}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Bold, BoldCs, Color, Highlight, Italic, ItalicCs, Sz, SzCs, Underline, Vanish};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RunProperty {
|
||||
sz: Option<Sz>,
|
||||
sz_cs: Option<SzCs>,
|
||||
color: Option<Color>,
|
||||
highlight: Option<Highlight>,
|
||||
underline: Option<Underline>,
|
||||
bold: Option<Bold>,
|
||||
bold_cs: Option<BoldCs>,
|
||||
italic: Option<Italic>,
|
||||
italic_cs: Option<ItalicCs>,
|
||||
vanish: Option<Vanish>,
|
||||
pub sz: Option<Sz>,
|
||||
pub sz_cs: Option<SzCs>,
|
||||
pub color: Option<Color>,
|
||||
pub highlight: Option<Highlight>,
|
||||
pub underline: Option<Underline>,
|
||||
pub bold: Option<Bold>,
|
||||
pub bold_cs: Option<BoldCs>,
|
||||
pub italic: Option<Italic>,
|
||||
pub italic_cs: Option<ItalicCs>,
|
||||
pub vanish: Option<Vanish>,
|
||||
}
|
||||
|
||||
impl RunProperty {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::RunProperty;
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RunPropertyDefault {
|
||||
run_property: RunProperty,
|
||||
}
|
||||
|
|
|
@ -2,7 +2,10 @@ use super::*;
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SectionProperty {
|
||||
page_size: PageSize,
|
||||
page_margin: PageMargin,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Start {
|
||||
val: usize,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for Start {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Start {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u32(self.val as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
use crate::StyleType;
|
||||
|
||||
use super::{BasedOn, Name, Next, ParagraphProperty, QFormat, RunProperty};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Style {
|
||||
style_id: String,
|
||||
name: Name,
|
||||
style_type: StyleType,
|
||||
run_property: RunProperty,
|
||||
paragraph_property: ParagraphProperty,
|
||||
pub style_id: String,
|
||||
pub name: Name,
|
||||
pub style_type: StyleType,
|
||||
pub run_property: RunProperty,
|
||||
pub paragraph_property: ParagraphProperty,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Sz {
|
||||
val: usize,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for Sz {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Sz {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u32(self.val as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct SzCs {
|
||||
val: usize,
|
||||
}
|
||||
|
@ -18,6 +19,15 @@ impl BuildXML for SzCs {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for SzCs {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u32(self.val as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Tab {}
|
||||
|
||||
impl Tab {
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::{TableGrid, TableProperty, TableRow};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Table {
|
||||
pub rows: Vec<TableRow>,
|
||||
pub grid: Vec<usize>,
|
||||
pub(crate) has_numbering: bool,
|
||||
property: TableProperty,
|
||||
pub has_numbering: bool,
|
||||
pub property: TableProperty,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
|
@ -24,6 +27,11 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_row(mut self, row: TableRow) -> Table {
|
||||
self.rows.push(row);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_grid(mut self, grid: Vec<usize>) -> Table {
|
||||
self.grid = grid;
|
||||
self
|
||||
|
@ -39,8 +47,8 @@ impl Table {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, w: usize) -> Table {
|
||||
self.property = self.property.width(w, WidthType::DXA);
|
||||
pub fn width(mut self, w: usize, t: WidthType) -> Table {
|
||||
self.property = self.property.width(w, t);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -97,4 +105,13 @@ mod tests {
|
|||
</w:tblGrid><w:tr><w:trPr /></w:tr></w:tbl>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_json() {
|
||||
let t = Table::new(vec![]).set_grid(vec![100, 200, 300]);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&t).unwrap(),
|
||||
r#"{"rows":[],"grid":[100,200,300],"hasNumbering":false,"property":{"width":{"width":0,"widthType":"Auto"},"justification":"left","borders":{"top":{"position":"top","borderType":"single","size":2,"space":0,"color":"000000"},"left":{"position":"left","borderType":"single","size":2,"space":0,"color":"000000"},"bottom":{"position":"bottom","borderType":"single","size":2,"space":0,"color":"000000"},"right":{"position":"right","borderType":"single","size":2,"space":0,"color":"000000"},"insideH":{"position":"insideH","borderType":"single","size":2,"space":0,"color":"000000"},"insideV":{"position":"insideV","borderType":"single","size":2,"space":0,"color":"000000"}},"margins":{"top":55,"left":54,"bottom":55,"right":55},"indent":null}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
@ -15,7 +17,8 @@ use crate::xml_builder::*;
|
|||
tr2bl – diagonal border from top right corner to bottom left corner
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableBorder {
|
||||
position: BorderPosition,
|
||||
border_type: BorderType,
|
||||
|
@ -57,10 +60,10 @@ impl BuildXML for TableBorder {
|
|||
BorderPosition::Right => {
|
||||
base.border_right(self.border_type, self.size, self.space, &self.color)
|
||||
}
|
||||
BorderPosition::IndideH => {
|
||||
BorderPosition::InsideH => {
|
||||
base.border_inside_h(self.border_type, self.size, self.space, &self.color)
|
||||
}
|
||||
BorderPosition::IndideV => {
|
||||
BorderPosition::InsideV => {
|
||||
base.border_inside_v(self.border_type, self.size, self.space, &self.color)
|
||||
}
|
||||
};
|
||||
|
@ -80,7 +83,8 @@ impl BuildXML for TableBorder {
|
|||
// If there is no cell border, then the table-level exception border shall be displayed
|
||||
// If this element is omitted, then this table shall have the borders specified by the associated table level borders
|
||||
// (§17.4.38).
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableBorders {
|
||||
top: Option<TableBorder>,
|
||||
left: Option<TableBorder>,
|
||||
|
@ -97,8 +101,8 @@ impl Default for TableBorders {
|
|||
left: Some(TableBorder::new(BorderPosition::Left)),
|
||||
bottom: Some(TableBorder::new(BorderPosition::Bottom)),
|
||||
right: Some(TableBorder::new(BorderPosition::Right)),
|
||||
inside_h: Some(TableBorder::new(BorderPosition::IndideH)),
|
||||
inside_v: Some(TableBorder::new(BorderPosition::IndideV)),
|
||||
inside_h: Some(TableBorder::new(BorderPosition::InsideH)),
|
||||
inside_v: Some(TableBorder::new(BorderPosition::InsideV)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,8 +118,8 @@ impl TableBorders {
|
|||
BorderPosition::Left => self.left = Some(border),
|
||||
BorderPosition::Bottom => self.bottom = Some(border),
|
||||
BorderPosition::Right => self.right = Some(border),
|
||||
BorderPosition::IndideH => self.inside_h = Some(border),
|
||||
BorderPosition::IndideV => self.inside_v = Some(border),
|
||||
BorderPosition::InsideH => self.inside_h = Some(border),
|
||||
BorderPosition::InsideV => self.inside_v = Some(border),
|
||||
};
|
||||
self
|
||||
}
|
||||
|
@ -126,8 +130,8 @@ impl TableBorders {
|
|||
BorderPosition::Left => self.left = None,
|
||||
BorderPosition::Bottom => self.bottom = None,
|
||||
BorderPosition::Right => self.right = None,
|
||||
BorderPosition::IndideH => self.inside_h = None,
|
||||
BorderPosition::IndideV => self.inside_v = None,
|
||||
BorderPosition::InsideH => self.inside_h = None,
|
||||
BorderPosition::InsideV => self.inside_v = None,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
|
|
@ -1,20 +1,40 @@
|
|||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{Paragraph, TableCellProperty};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableCell {
|
||||
pub contents: Vec<TableCellContent>,
|
||||
pub children: Vec<TableCellContent>,
|
||||
pub property: TableCellProperty,
|
||||
pub has_numbering: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TableCellContent {
|
||||
Paragraph(Paragraph),
|
||||
}
|
||||
|
||||
impl Serialize for TableCellContent {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
TableCellContent::Paragraph(ref s) => {
|
||||
let mut t = serializer.serialize_struct("Paragraph", 2)?;
|
||||
t.serialize_field("type", "paragraph")?;
|
||||
t.serialize_field("data", s)?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableCell {
|
||||
pub fn new() -> TableCell {
|
||||
Default::default()
|
||||
|
@ -24,7 +44,7 @@ impl TableCell {
|
|||
if p.has_numbering {
|
||||
self.has_numbering = true
|
||||
}
|
||||
self.contents.push(TableCellContent::Paragraph(p));
|
||||
self.children.push(TableCellContent::Paragraph(p));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -38,8 +58,8 @@ impl TableCell {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, v: usize) -> TableCell {
|
||||
self.property = self.property.width(v, WidthType::DXA);
|
||||
pub fn width(mut self, v: usize, t: WidthType) -> TableCell {
|
||||
self.property = self.property.width(v, t);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -47,10 +67,10 @@ impl TableCell {
|
|||
impl Default for TableCell {
|
||||
fn default() -> Self {
|
||||
let property = TableCellProperty::new();
|
||||
let contents = vec![];
|
||||
let children = vec![];
|
||||
Self {
|
||||
property,
|
||||
contents,
|
||||
children,
|
||||
has_numbering: false,
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +80,7 @@ impl BuildXML for TableCell {
|
|||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
let mut b = b.open_table_cell().add_child(&self.property);
|
||||
for c in &self.contents {
|
||||
for c in &self.children {
|
||||
match c {
|
||||
TableCellContent::Paragraph(p) => b = b.add_child(p),
|
||||
}
|
||||
|
@ -94,4 +114,15 @@ mod tests {
|
|||
r#"<w:tc><w:tcPr /><w:p><w:pPr><w:pStyle w:val="Normal" /><w:rPr /></w:pPr><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r></w:p></w:tc>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cell_json() {
|
||||
let c = TableCell::new()
|
||||
.add_paragraph(Paragraph::new().add_run(Run::new().add_text("Hello")))
|
||||
.grid_span(2);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&c).unwrap(),
|
||||
r#"{"children":[{"type":"paragraph","data":{"children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null},"style":"Normal","numberingProperty":null,"alignment":null,"indent":null},"hasNumbering":false,"attrs":[]}}],"property":{"width":null,"borders":null,"gridSpan":2,"verticalMerge":null},"hasNumbering":false}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
@ -15,7 +17,8 @@ use crate::xml_builder::*;
|
|||
tr2bl – diagonal border from top right corner to bottom left corner
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableCellBorder {
|
||||
position: BorderPosition,
|
||||
border_type: BorderType,
|
||||
|
@ -57,10 +60,10 @@ impl BuildXML for TableCellBorder {
|
|||
BorderPosition::Right => {
|
||||
base.border_right(self.border_type, self.size, self.space, &self.color)
|
||||
}
|
||||
BorderPosition::IndideH => {
|
||||
BorderPosition::InsideH => {
|
||||
base.border_inside_h(self.border_type, self.size, self.space, &self.color)
|
||||
}
|
||||
BorderPosition::IndideV => {
|
||||
BorderPosition::InsideV => {
|
||||
base.border_inside_v(self.border_type, self.size, self.space, &self.color)
|
||||
}
|
||||
};
|
||||
|
@ -68,7 +71,8 @@ impl BuildXML for TableCellBorder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableCellBorders {
|
||||
top: Option<TableCellBorder>,
|
||||
left: Option<TableCellBorder>,
|
||||
|
@ -85,8 +89,8 @@ impl Default for TableCellBorders {
|
|||
left: Some(TableCellBorder::new(BorderPosition::Left)),
|
||||
bottom: Some(TableCellBorder::new(BorderPosition::Bottom)),
|
||||
right: Some(TableCellBorder::new(BorderPosition::Right)),
|
||||
inside_h: Some(TableCellBorder::new(BorderPosition::IndideH)),
|
||||
inside_v: Some(TableCellBorder::new(BorderPosition::IndideV)),
|
||||
inside_h: Some(TableCellBorder::new(BorderPosition::InsideH)),
|
||||
inside_v: Some(TableCellBorder::new(BorderPosition::InsideV)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,8 +106,8 @@ impl TableCellBorders {
|
|||
BorderPosition::Left => self.left = Some(border),
|
||||
BorderPosition::Bottom => self.bottom = Some(border),
|
||||
BorderPosition::Right => self.right = Some(border),
|
||||
BorderPosition::IndideH => self.inside_h = Some(border),
|
||||
BorderPosition::IndideV => self.inside_v = Some(border),
|
||||
BorderPosition::InsideH => self.inside_h = Some(border),
|
||||
BorderPosition::InsideV => self.inside_v = Some(border),
|
||||
};
|
||||
self
|
||||
}
|
||||
|
@ -114,8 +118,8 @@ impl TableCellBorders {
|
|||
BorderPosition::Left => self.left = None,
|
||||
BorderPosition::Bottom => self.bottom = None,
|
||||
BorderPosition::Right => self.right = None,
|
||||
BorderPosition::IndideH => self.inside_h = None,
|
||||
BorderPosition::IndideV => self.inside_v = None,
|
||||
BorderPosition::InsideH => self.inside_h = None,
|
||||
BorderPosition::InsideV => self.inside_v = None,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableCellMargins {
|
||||
top: usize,
|
||||
left: usize,
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::{GridSpan, TableCellBorders, TableCellWidth, VMerge};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableCellProperty {
|
||||
width: Option<TableCellWidth>,
|
||||
borders: Option<TableCellBorders>,
|
||||
|
@ -90,4 +93,16 @@ mod tests {
|
|||
r#"<w:tcPr><w:vMerge w:val="continue" /></w:tcPr>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_cell_prop_json() {
|
||||
let c = TableCellProperty::new()
|
||||
.vertical_merge(VMergeType::Continue)
|
||||
.grid_span(3)
|
||||
.width(200, WidthType::DXA);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&c).unwrap(),
|
||||
r#"{"width":{"width":200,"widthType":"DXA"},"borders":null,"gridSpan":3,"verticalMerge":"continue"}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableCellWidth {
|
||||
width: usize,
|
||||
width_type: WidthType,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableIndent {
|
||||
width: usize,
|
||||
width_type: WidthType,
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::{Justification, TableBorders, TableCellMargins, TableIndent, TableWidth};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableProperty {
|
||||
width: TableWidth,
|
||||
justification: Justification,
|
||||
|
@ -81,4 +84,13 @@ mod tests {
|
|||
</w:tblCellMar></w:tblPr>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_property_json() {
|
||||
let p = TableProperty::new().indent(100);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&p).unwrap(),
|
||||
r#"{"width":{"width":0,"widthType":"Auto"},"justification":"left","borders":{"top":{"position":"top","borderType":"single","size":2,"space":0,"color":"000000"},"left":{"position":"left","borderType":"single","size":2,"space":0,"color":"000000"},"bottom":{"position":"bottom","borderType":"single","size":2,"space":0,"color":"000000"},"right":{"position":"right","borderType":"single","size":2,"space":0,"color":"000000"},"insideH":{"position":"insideH","borderType":"single","size":2,"space":0,"color":"000000"},"insideV":{"position":"insideV","borderType":"single","size":2,"space":0,"color":"000000"}},"margins":{"top":55,"left":54,"bottom":55,"right":55},"indent":{"width":100,"widthType":"DXA"}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::{TableCell, TableRowProperty};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableRow {
|
||||
pub cells: Vec<TableCell>,
|
||||
pub(crate) has_numbering: bool,
|
||||
property: TableRowProperty,
|
||||
pub has_numbering: bool,
|
||||
pub property: TableRowProperty,
|
||||
}
|
||||
|
||||
impl TableRow {
|
||||
|
@ -47,4 +50,13 @@ mod tests {
|
|||
r#"<w:tr><w:trPr /><w:tc><w:tcPr /></w:tc></w:tr>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_row_json() {
|
||||
let r = TableRow::new(vec![TableCell::new()]);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&r).unwrap(),
|
||||
r#"{"cells":[{"children":[],"property":{"width":null,"borders":null,"gridSpan":null,"verticalMerge":null},"hasNumbering":false}],"hasNumbering":false,"property":{}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct TableRowProperty {}
|
||||
|
||||
impl TableRowProperty {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableWidth {
|
||||
width: usize,
|
||||
width_type: WidthType,
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::escape::escape;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Text {
|
||||
text: String,
|
||||
preserve_space: bool,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn new(text: &str) -> Text {
|
||||
pub fn new(text: impl Into<String>) -> Text {
|
||||
Text {
|
||||
text: escape(text),
|
||||
text: escape(&text.into()),
|
||||
preserve_space: true,
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +27,18 @@ impl BuildXML for Text {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Text {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut t = serializer.serialize_struct("Text", 2)?;
|
||||
t.serialize_field("preserveSpace", &self.preserve_space)?;
|
||||
t.serialize_field("text", &self.text)?;
|
||||
t.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -39,4 +55,13 @@ mod tests {
|
|||
r#"<w:t xml:space="preserve">Hello</w:t>"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json() {
|
||||
let t = Text::new("Hello");
|
||||
assert_eq!(
|
||||
serde_json::to_string(&t).unwrap(),
|
||||
r#"{"preserveSpace":true,"text":"Hello"}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Underline {
|
||||
val: String,
|
||||
}
|
||||
|
@ -18,6 +20,15 @@ impl BuildXML for Underline {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Underline {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
pub struct Vanish {}
|
||||
|
||||
impl Vanish {
|
||||
|
@ -23,6 +25,15 @@ impl BuildXML for Vanish {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Vanish {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct VMerge {
|
||||
val: VMergeType,
|
||||
}
|
||||
|
@ -21,6 +23,15 @@ impl BuildXML for VMerge {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for VMerge {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{}", &self.val))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Zoom {
|
||||
val: usize,
|
||||
}
|
||||
|
@ -19,6 +21,15 @@ impl BuildXML for Zoom {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Zoom {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u64(self.val as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@ use crate::documents::BuildXML;
|
|||
use crate::types::FontPitchType;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FontTable {}
|
||||
|
||||
impl FontTable {
|
||||
|
|
|
@ -29,12 +29,15 @@ pub use settings::*;
|
|||
pub use styles::*;
|
||||
pub use xml_docx::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Docx {
|
||||
content_type: ContentTypes,
|
||||
rels: Rels,
|
||||
document_rels: DocumentRels,
|
||||
doc_props: DocProps,
|
||||
pub content_type: ContentTypes,
|
||||
pub rels: Rels,
|
||||
pub document_rels: DocumentRels,
|
||||
pub doc_props: DocProps,
|
||||
pub styles: Styles,
|
||||
pub document: Document,
|
||||
pub comments: Comments,
|
||||
|
@ -45,7 +48,7 @@ pub struct Docx {
|
|||
|
||||
impl Default for Docx {
|
||||
fn default() -> Self {
|
||||
let content_type = ContentTypes::new();
|
||||
let content_type = ContentTypes::new().set_default();
|
||||
let rels = Rels::new();
|
||||
let doc_props = DocProps::new(CorePropsConfig::new());
|
||||
let styles = Styles::new();
|
||||
|
@ -75,6 +78,11 @@ impl Docx {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
pub fn document(mut self, d: Document) -> Docx {
|
||||
self.document = d;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_paragraph(mut self, p: Paragraph) -> Docx {
|
||||
if p.has_numbering {
|
||||
// If this document has numbering, set numberings.xml to document_rels.
|
||||
|
@ -126,6 +134,11 @@ impl Docx {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn json(&mut self) -> String {
|
||||
self.update_comments();
|
||||
serde_json::to_string(&self).unwrap()
|
||||
}
|
||||
|
||||
// Traverse and clone comments from document and add to comments node.
|
||||
fn update_comments(&mut self) {
|
||||
let mut comments: Vec<Comment> = vec![];
|
||||
|
@ -141,7 +154,7 @@ impl Docx {
|
|||
DocumentChild::Table(table) => {
|
||||
for row in &table.rows {
|
||||
for cell in &row.cells {
|
||||
for content in &cell.contents {
|
||||
for content in &cell.children {
|
||||
match content {
|
||||
TableCellContent::Paragraph(paragraph) => {
|
||||
for child in ¶graph.children {
|
||||
|
|
|
@ -3,7 +3,10 @@ use crate::documents::BuildXML;
|
|||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Numberings {
|
||||
numberings: Vec<Numbering>,
|
||||
}
|
||||
|
@ -46,7 +49,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("%1."),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(420, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(420, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -56,7 +59,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("(%2)"),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(840, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(840, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -66,7 +69,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("%3"),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(1260, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(1260, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -76,7 +79,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("%4."),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(1680, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(1680, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -86,7 +89,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("(%5)"),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(2100, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(2100, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -96,7 +99,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("%6"),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(2520, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(2520, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -106,7 +109,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("%7."),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(2940, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(2940, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -116,7 +119,7 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("(%8)"),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(3360, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(3360, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
.add_level(
|
||||
Level::new(
|
||||
|
@ -126,6 +129,6 @@ fn create_default_numbering() -> Numbering {
|
|||
LevelText::new("%9"),
|
||||
LevelJc::new("left"),
|
||||
)
|
||||
.indent(3780, Some(SpecialIndentType::Hanging(420))),
|
||||
.indent(3780, Some(SpecialIndentType::Hanging(420)), None),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,43 +1,69 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Rels {}
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Rels {
|
||||
pub rels: Vec<(String, String, String)>,
|
||||
}
|
||||
|
||||
impl Rels {
|
||||
pub fn new() -> Rels {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn set_default(mut self) -> Self {
|
||||
self.rels.push((
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
|
||||
.to_owned(),
|
||||
"rId1".to_owned(),
|
||||
"docProps/core.xml".to_owned(),
|
||||
));
|
||||
self.rels.push(
|
||||
("http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties".to_owned(),
|
||||
"rId2".to_owned(), "docProps/app.xml".to_owned()),
|
||||
);
|
||||
self.rels.push((
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
|
||||
.to_owned(),
|
||||
"rId3".to_owned(),
|
||||
"word/document.xml".to_owned(),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_rel(
|
||||
mut self,
|
||||
id: impl Into<String>,
|
||||
rel_type: impl Into<String>,
|
||||
target: impl Into<String>,
|
||||
) -> Self {
|
||||
self.rels.push((rel_type.into(), id.into(), target.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn find_target(&self, rel_type: &str) -> Option<&(String, String, String)> {
|
||||
self.rels.iter().find(|rel| rel.0 == rel_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Rels {
|
||||
fn default() -> Self {
|
||||
Rels {}
|
||||
Rels { rels: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildXML for Rels {
|
||||
fn build(&self) -> Vec<u8> {
|
||||
let b = XMLBuilder::new();
|
||||
b.declaration(None)
|
||||
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships")
|
||||
.relationship(
|
||||
"rId1",
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",
|
||||
"docProps/core.xml"
|
||||
)
|
||||
.relationship(
|
||||
"rId2",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
|
||||
"docProps/app.xml"
|
||||
)
|
||||
.relationship(
|
||||
"rId3",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
"word/document.xml"
|
||||
)
|
||||
.close()
|
||||
.build()
|
||||
let mut b = b
|
||||
.declaration(None)
|
||||
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
for (k, id, v) in self.rels.iter() {
|
||||
b = b.relationship(id, k, v);
|
||||
}
|
||||
b.close().build()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +77,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_build() {
|
||||
let c = Rels::new();
|
||||
let c = Rels::new().set_default();
|
||||
let b = c.build();
|
||||
assert_eq!(
|
||||
str::from_utf8(&b).unwrap(),
|
||||
|
|
|
@ -2,7 +2,10 @@ use super::{DefaultTabStop, Zoom};
|
|||
use crate::documents::BuildXML;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Settings {
|
||||
default_tab_stop: DefaultTabStop,
|
||||
zoom: Zoom,
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::{DocDefaults, Style};
|
||||
use crate::documents::BuildXML;
|
||||
use crate::types::*;
|
||||
use crate::xml_builder::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Styles {
|
||||
doc_defaults: DocDefaults,
|
||||
styles: Vec<Style>,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
mod documents;
|
||||
mod errors;
|
||||
mod escape;
|
||||
mod reader;
|
||||
mod types;
|
||||
mod xml_builder;
|
||||
mod zipper;
|
||||
|
||||
pub use documents::*;
|
||||
pub use errors::*;
|
||||
pub use reader::*;
|
||||
pub use types::*;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
mod width;
|
||||
|
||||
pub use width::*;
|
|
@ -0,0 +1,21 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
|
||||
use crate::types::*;
|
||||
|
||||
use super::super::errors::*;
|
||||
|
||||
pub fn read_width(attrs: &[OwnedAttribute]) -> Result<(usize, WidthType), ReaderError> {
|
||||
let mut w = 0;
|
||||
let mut width_type = WidthType::Auto;
|
||||
for a in attrs {
|
||||
let local_name = &a.name.local_name;
|
||||
if local_name == "type" {
|
||||
width_type = WidthType::from_str(&a.value)?;
|
||||
} else if local_name == "w" {
|
||||
w = usize::from_str(&a.value)?;
|
||||
}
|
||||
}
|
||||
Ok((w, width_type))
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl ElementReader for Delete {
|
||||
fn read<R: Read>(
|
||||
r: &mut EventReader<R>,
|
||||
attrs: &[OwnedAttribute],
|
||||
) -> Result<Self, ReaderError> {
|
||||
let mut run: Option<Run> = None;
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name)
|
||||
.expect("should convert to XMLElement");
|
||||
if let XMLElement::Run = e {
|
||||
run = Some(Run::read(r, attrs)?);
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::Delete {
|
||||
if let Some(run) = run {
|
||||
let mut del = Delete::new(run);
|
||||
for attr in attrs {
|
||||
let local_name = &attr.name.local_name;
|
||||
if local_name == "author" {
|
||||
del = del.author(&attr.value);
|
||||
} else if local_name == "date" {
|
||||
del = del.date(&attr.value);
|
||||
}
|
||||
}
|
||||
return Ok(del);
|
||||
} else {
|
||||
return Err(ReaderError::XMLReadError);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::reader::*;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::{Paragraph, Table};
|
||||
|
||||
impl FromXML for Document {
|
||||
fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
|
||||
let mut parser = EventReader::new(reader);
|
||||
let mut doc = Self::default();
|
||||
|
||||
loop {
|
||||
let e = parser.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::Paragraph => {
|
||||
let p = Paragraph::read(&mut parser, &attributes)?;
|
||||
doc = doc.add_paragraph(p);
|
||||
continue;
|
||||
}
|
||||
XMLElement::Table => {
|
||||
let t = Table::read(&mut parser, &attributes)?;
|
||||
doc = doc.add_table(t);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndDocument) => break,
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(doc)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ReaderError {
|
||||
#[error("Failed to read from zip.")]
|
||||
ZipError(#[from] zip::result::ZipError),
|
||||
#[error("Failed to parse int.")]
|
||||
NumError(#[from] std::num::ParseIntError),
|
||||
#[error("Failed to convert type.")]
|
||||
TypeError(#[from] crate::types::TypeError),
|
||||
#[error("Failed to read xml.")]
|
||||
XMLReadError,
|
||||
#[error("Failed to find document.")]
|
||||
DocumentNotFoundError,
|
||||
#[error("Unknown error")]
|
||||
Unknown,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use crate::reader::ReaderError;
|
||||
use std::io::Read;
|
||||
|
||||
pub trait FromXML {
|
||||
fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl ElementReader for Insert {
|
||||
fn read<R: Read>(
|
||||
r: &mut EventReader<R>,
|
||||
attrs: &[OwnedAttribute],
|
||||
) -> Result<Self, ReaderError> {
|
||||
let mut run: Option<Run> = None;
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if let XMLElement::Run = e {
|
||||
run = Some(Run::read(r, attrs)?);
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::Insert {
|
||||
if let Some(run) = run {
|
||||
let mut ins = Insert::new(run);
|
||||
for attr in attrs {
|
||||
let local_name = &attr.name.local_name;
|
||||
if local_name == "author" {
|
||||
ins = ins.author(&attr.value);
|
||||
} else if local_name == "date" {
|
||||
ins = ins.date(&attr.value);
|
||||
}
|
||||
}
|
||||
return Ok(ins);
|
||||
} else {
|
||||
return Err(ReaderError::XMLReadError);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
mod attributes;
|
||||
mod delete;
|
||||
mod document;
|
||||
mod errors;
|
||||
mod from_xml;
|
||||
mod insert;
|
||||
mod numbering_property;
|
||||
mod paragraph;
|
||||
mod rels;
|
||||
mod run;
|
||||
mod table;
|
||||
mod table_cell;
|
||||
mod table_row;
|
||||
mod xml_element;
|
||||
|
||||
use std::io::Cursor;
|
||||
use zip;
|
||||
|
||||
use crate::documents::*;
|
||||
|
||||
pub use attributes::*;
|
||||
pub use errors::ReaderError;
|
||||
pub use from_xml::*;
|
||||
pub use xml_element::*;
|
||||
|
||||
const DOC_RELATIONSHIP_TYPE: &str =
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
||||
|
||||
pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
|
||||
let cur = Cursor::new(buf);
|
||||
let mut archive = zip::ZipArchive::new(cur)?;
|
||||
// First, the content type for relationship parts and the Main Document part
|
||||
// (the only required part) must be defined (physically located at /[Content_Types].xml in the package)
|
||||
let content_types_xml = archive.by_name("[Content_Types].xml")?;
|
||||
let _content_types = ContentTypes::from_xml(content_types_xml)?;
|
||||
// Next, the single required relationship (the package-level relationship to the Main Document part)
|
||||
// must be defined (physically located at /_rels/.rels in the package)
|
||||
let rels_xml = archive.by_name("_rels/.rels")?;
|
||||
let rels = Rels::from_xml(rels_xml)?;
|
||||
// Finally, the minimum content for the Main Document part must be defined
|
||||
// (physically located at /document.xml in the package):
|
||||
let main_rel = rels
|
||||
.find_target(DOC_RELATIONSHIP_TYPE)
|
||||
.ok_or(ReaderError::DocumentNotFoundError)?;
|
||||
let document_xml = archive.by_name(&main_rel.2)?;
|
||||
let document = Document::from_xml(document_xml)?;
|
||||
let docx = Docx::new().document(document);
|
||||
Ok(docx)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl ElementReader for NumberingProperty {
|
||||
fn read<R: Read>(
|
||||
r: &mut EventReader<R>,
|
||||
_attrs: &[OwnedAttribute],
|
||||
) -> Result<Self, ReaderError> {
|
||||
let mut id: Option<usize> = None;
|
||||
let mut level: Option<usize> = None;
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::IndentLevel => {
|
||||
level = Some(usize::from_str(&attributes[0].value)?);
|
||||
continue;
|
||||
}
|
||||
XMLElement::NumberingId => {
|
||||
id = Some(usize::from_str(&attributes[0].value)?);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::NumberingProperty {
|
||||
if id.is_none() || level.is_none() {
|
||||
return Err(ReaderError::XMLReadError);
|
||||
}
|
||||
let np = NumberingProperty::new(
|
||||
NumberingId::new(id.unwrap()),
|
||||
IndentLevel::new(level.unwrap()),
|
||||
);
|
||||
return Ok(np);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::types::*;
|
||||
|
||||
impl ElementReader for Paragraph {
|
||||
fn read<R: Read>(
|
||||
r: &mut EventReader<R>,
|
||||
attrs: &[OwnedAttribute],
|
||||
) -> Result<Self, ReaderError> {
|
||||
let mut p = Paragraph::new();
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::Run => {
|
||||
let run = Run::read(r, attrs)?;
|
||||
p = p.add_run(run);
|
||||
continue;
|
||||
}
|
||||
XMLElement::Insert => {
|
||||
let ins = Insert::read(r, &attributes)?;
|
||||
p = p.add_insert(ins);
|
||||
continue;
|
||||
}
|
||||
XMLElement::Delete => {
|
||||
let del = Delete::read(r, &attributes)?;
|
||||
p = p.add_delete(del);
|
||||
continue;
|
||||
}
|
||||
XMLElement::BookmarkStart => {
|
||||
let mut id: Option<usize> = None;
|
||||
let mut name: Option<String> = None;
|
||||
|
||||
for a in attributes {
|
||||
let local_name = &a.name.local_name;
|
||||
if local_name == "id" {
|
||||
id = Some(usize::from_str(&a.value)?);
|
||||
} else if local_name == "name" {
|
||||
name = Some(a.value.clone());
|
||||
}
|
||||
}
|
||||
if id.is_none() || name.is_none() {
|
||||
return Err(ReaderError::XMLReadError);
|
||||
}
|
||||
p = p.add_bookmark_start(id.unwrap(), name.unwrap());
|
||||
continue;
|
||||
}
|
||||
XMLElement::BookmarkEnd => {
|
||||
let mut id: Option<usize> = None;
|
||||
for a in attributes {
|
||||
let local_name = &a.name.local_name;
|
||||
if local_name == "id" {
|
||||
id = Some(usize::from_str(&a.value)?);
|
||||
}
|
||||
}
|
||||
if let Some(id) = id {
|
||||
p = p.add_bookmark_end(id);
|
||||
} else {
|
||||
return Err(ReaderError::XMLReadError);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
XMLElement::CommentRangeStart => {
|
||||
// TODO: Support comment later.
|
||||
continue;
|
||||
}
|
||||
XMLElement::CommentRangeEnd => {
|
||||
p = p.add_comment_end(usize::from_str(&attributes[0].value)?);
|
||||
continue;
|
||||
}
|
||||
XMLElement::Indent => {
|
||||
let mut start = 0;
|
||||
let mut end: Option<usize> = None;
|
||||
let mut special: Option<SpecialIndentType> = None;
|
||||
|
||||
for a in attributes {
|
||||
let local_name = &a.name.local_name;
|
||||
if local_name == "left" || local_name == "start" {
|
||||
start = usize::from_str(&a.value)?;
|
||||
} else if local_name == "end" || local_name == "right" {
|
||||
end = Some(usize::from_str(&a.value)?);
|
||||
} else if local_name == "hanging" {
|
||||
special =
|
||||
Some(SpecialIndentType::Hanging(usize::from_str(&a.value)?))
|
||||
} else if local_name == "firstLine" {
|
||||
special = Some(SpecialIndentType::FirstLine(usize::from_str(
|
||||
&a.value,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
p = p.indent(start, special, end);
|
||||
continue;
|
||||
}
|
||||
XMLElement::Justification => {
|
||||
p = p.align(AlignmentType::from_str(&attributes[0].value)?);
|
||||
continue;
|
||||
}
|
||||
XMLElement::ParagraphStyle => {
|
||||
p = p.style(&attributes[0].value);
|
||||
continue;
|
||||
}
|
||||
XMLElement::NumberingProperty => {
|
||||
let num_pr = NumberingProperty::read(r, attrs)?;
|
||||
p = p.numbering(num_pr.id, num_pr.level);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::Paragraph {
|
||||
return Ok(p);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_read_indent() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:ind w:left="1470" w:right="1270" w:hanging="0"/>
|
||||
<w:rPr></w:rPr>
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr></w:rPr>
|
||||
<w:t>a</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let p = Paragraph::read(&mut parser, &[]).unwrap();
|
||||
let s: Option<&str> = None;
|
||||
assert_eq!(
|
||||
p,
|
||||
Paragraph {
|
||||
children: vec![ParagraphChild::Run(Run::new().add_text("a"))],
|
||||
property: ParagraphProperty {
|
||||
run_property: RunProperty::new(),
|
||||
style: ParagraphStyle::new(s),
|
||||
numbering_property: None,
|
||||
alignment: None,
|
||||
indent: Some(Indent::new(
|
||||
1470,
|
||||
Some(SpecialIndentType::Hanging(0)),
|
||||
Some(1270)
|
||||
)),
|
||||
},
|
||||
has_numbering: false,
|
||||
attrs: Vec::new(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_jc() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:jc w:val="left"/>
|
||||
</w:pPr>
|
||||
</w:p>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let p = Paragraph::read(&mut parser, &[]).unwrap();
|
||||
let s: Option<&str> = None;
|
||||
assert_eq!(
|
||||
p,
|
||||
Paragraph {
|
||||
children: vec![],
|
||||
property: ParagraphProperty {
|
||||
run_property: RunProperty::new(),
|
||||
style: ParagraphStyle::new(s),
|
||||
numbering_property: None,
|
||||
alignment: Some(Justification::new(AlignmentType::Left.to_string())),
|
||||
indent: None,
|
||||
},
|
||||
has_numbering: false,
|
||||
attrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_numbering() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:numPr>
|
||||
<w:ilvl w:val="0"/>
|
||||
<w:numId w:val="1"/>
|
||||
</w:numPr>
|
||||
</w:pPr>
|
||||
</w:p>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let p = Paragraph::read(&mut parser, &[]).unwrap();
|
||||
let s: Option<&str> = None;
|
||||
assert_eq!(
|
||||
p,
|
||||
Paragraph {
|
||||
children: vec![],
|
||||
property: ParagraphProperty {
|
||||
run_property: RunProperty::new(),
|
||||
style: ParagraphStyle::new(s),
|
||||
numbering_property: Some(NumberingProperty::new(
|
||||
NumberingId::new(1),
|
||||
IndentLevel::new(0),
|
||||
)),
|
||||
alignment: None,
|
||||
indent: None,
|
||||
},
|
||||
has_numbering: true,
|
||||
attrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_insert() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:ins w:id="0" w:author="unknown" w:date="2019-11-15T14:19:04Z">
|
||||
<w:r>
|
||||
<w:rPr></w:rPr>
|
||||
<w:t>W</w:t>
|
||||
</w:r>
|
||||
</w:ins>
|
||||
</w:p>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let p = Paragraph::read(&mut parser, &[]).unwrap();
|
||||
let s: Option<&str> = None;
|
||||
assert_eq!(
|
||||
p,
|
||||
Paragraph {
|
||||
children: vec![ParagraphChild::Insert(
|
||||
Insert::new(Run::new().add_text("W"))
|
||||
.author("unknown")
|
||||
.date("2019-11-15T14:19:04Z")
|
||||
)],
|
||||
property: ParagraphProperty {
|
||||
run_property: RunProperty::new(),
|
||||
style: ParagraphStyle::new(s),
|
||||
numbering_property: None,
|
||||
alignment: None,
|
||||
indent: None,
|
||||
},
|
||||
has_numbering: false,
|
||||
attrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_delete() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:del w:id="3" w:author="unknown" w:date="2019-11-15T14:19:04Z">
|
||||
<w:r>
|
||||
<w:rPr></w:rPr>
|
||||
<w:delText xml:space="preserve">Hello </w:delText>
|
||||
</w:r>
|
||||
</w:del>
|
||||
</w:p>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let p = Paragraph::read(&mut parser, &[]).unwrap();
|
||||
let s: Option<&str> = None;
|
||||
assert_eq!(
|
||||
p,
|
||||
Paragraph {
|
||||
children: vec![ParagraphChild::Delete(
|
||||
Delete::new(Run::new().add_delete_text("Hello "))
|
||||
.author("unknown")
|
||||
.date("2019-11-15T14:19:04Z")
|
||||
)],
|
||||
property: ParagraphProperty {
|
||||
run_property: RunProperty::new(),
|
||||
style: ParagraphStyle::new(s),
|
||||
numbering_property: None,
|
||||
alignment: None,
|
||||
indent: None,
|
||||
},
|
||||
has_numbering: false,
|
||||
attrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_bookmark() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:bookmarkStart w:id="0" w:name="ABCD-1234"/>
|
||||
<w:r>
|
||||
<w:rPr></w:rPr>
|
||||
<w:t>Bookmarked</w:t>
|
||||
</w:r>
|
||||
<w:bookmarkEnd w:id="0"/>
|
||||
</w:p>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let p = Paragraph::read(&mut parser, &[]).unwrap();
|
||||
let s: Option<&str> = None;
|
||||
assert_eq!(
|
||||
p,
|
||||
Paragraph {
|
||||
children: vec![
|
||||
ParagraphChild::BookmarkStart(BookmarkStart::new(0, "ABCD-1234")),
|
||||
ParagraphChild::Run(Run::new().add_text("Bookmarked")),
|
||||
ParagraphChild::BookmarkEnd(BookmarkEnd::new(0)),
|
||||
],
|
||||
property: ParagraphProperty {
|
||||
run_property: RunProperty::new(),
|
||||
style: ParagraphStyle::new(s),
|
||||
numbering_property: None,
|
||||
alignment: None,
|
||||
indent: None,
|
||||
},
|
||||
has_numbering: false,
|
||||
attrs: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
use std::io::Read;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
use crate::reader::{FromXML, ReaderError};
|
||||
|
||||
impl FromXML for Rels {
|
||||
fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
|
||||
let parser = EventReader::new(reader);
|
||||
let mut s = Self::default();
|
||||
let mut depth = 0;
|
||||
for e in parser {
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement { attributes, .. }) => {
|
||||
if depth == 1 {
|
||||
let mut id = "".to_owned();
|
||||
let mut rel_type = "".to_owned();
|
||||
let mut target = "".to_owned();
|
||||
for attr in attributes {
|
||||
let name: &str = &attr.name.local_name;
|
||||
if name == "Id" {
|
||||
id = attr.value.clone();
|
||||
} else if name == "Type" {
|
||||
rel_type = attr.value.clone();
|
||||
} else if name == "Target" {
|
||||
target = attr.value.clone();
|
||||
}
|
||||
}
|
||||
s = s.add_rel(id, rel_type, target);
|
||||
}
|
||||
depth += 1;
|
||||
}
|
||||
Ok(XmlEvent::EndElement { .. }) => {
|
||||
depth -= 1;
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_xml() {
|
||||
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" />
|
||||
</Relationships>"#;
|
||||
let c = Rels::from_xml(xml.as_bytes()).unwrap();
|
||||
let mut rels = Vec::new();
|
||||
rels.push((
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
|
||||
.to_owned(),
|
||||
"rId1".to_owned(),
|
||||
"docProps/core.xml".to_owned(),
|
||||
));
|
||||
assert_eq!(Rels { rels }, c);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::Run;
|
||||
|
||||
use crate::reader::*;
|
||||
use crate::types::BreakType;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum TextState {
|
||||
Text,
|
||||
Delete,
|
||||
}
|
||||
|
||||
impl ElementReader for Run {
|
||||
fn read<R: Read>(
|
||||
r: &mut EventReader<R>,
|
||||
_attrs: &[OwnedAttribute],
|
||||
) -> Result<Self, ReaderError> {
|
||||
let mut run = Run::new();
|
||||
let mut text_state = TextState::Text;
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::Tab => run = run.add_tab(),
|
||||
XMLElement::Bold => run = run.bold(),
|
||||
XMLElement::Highlight => run = run.highlight(attributes[0].value.clone()),
|
||||
XMLElement::Color => run = run.color(attributes[0].value.clone()),
|
||||
XMLElement::Size => run = run.size(usize::from_str(&attributes[0].value)?),
|
||||
XMLElement::Underline => run = run.underline(&attributes[0].value.clone()),
|
||||
XMLElement::Italic => run = run.italic(),
|
||||
XMLElement::Vanish => run = run.vanish(),
|
||||
XMLElement::Text => text_state = TextState::Text,
|
||||
XMLElement::DeleteText => text_state = TextState::Delete,
|
||||
XMLElement::Break => {
|
||||
run = run.add_break(BreakType::from_str(&attributes[0].value)?)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::Characters(c)) => {
|
||||
if text_state == TextState::Delete {
|
||||
run = run.add_delete_text(c);
|
||||
} else {
|
||||
run = run.add_text(c);
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if let XMLElement::Run = e {
|
||||
return Ok(run);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_read_size_color() {
|
||||
let c = r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:r><w:rPr><w:color w:val="C9211E"/><w:sz w:val="30"/><w:szCs w:val="30"/></w:rPr><w:t>H</w:t></w:r>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let run = Run::read(&mut parser, &[]).unwrap();
|
||||
assert_eq!(
|
||||
run,
|
||||
Run {
|
||||
children: vec![RunChild::Text(Text::new("H"))],
|
||||
run_property: RunProperty {
|
||||
sz: Some(Sz::new(30)),
|
||||
sz_cs: Some(SzCs::new(30)),
|
||||
color: Some(Color::new("C9211E")),
|
||||
highlight: None,
|
||||
underline: None,
|
||||
bold: None,
|
||||
bold_cs: None,
|
||||
italic: None,
|
||||
italic_cs: None,
|
||||
vanish: None,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_tab() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:r><w:tab /></w:r>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let run = Run::read(&mut parser, &[]).unwrap();
|
||||
assert_eq!(
|
||||
run,
|
||||
Run {
|
||||
children: vec![RunChild::Tab(Tab::new())],
|
||||
run_property: RunProperty {
|
||||
sz: None,
|
||||
sz_cs: None,
|
||||
color: None,
|
||||
highlight: None,
|
||||
underline: None,
|
||||
bold: None,
|
||||
bold_cs: None,
|
||||
italic: None,
|
||||
italic_cs: None,
|
||||
vanish: None,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_br() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:r><w:br w:type="page" /></w:r>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let run = Run::read(&mut parser, &[]).unwrap();
|
||||
assert_eq!(
|
||||
run,
|
||||
Run {
|
||||
children: vec![RunChild::Break(Break::new(BreakType::Page))],
|
||||
run_property: RunProperty {
|
||||
sz: None,
|
||||
sz_cs: None,
|
||||
color: None,
|
||||
highlight: None,
|
||||
underline: None,
|
||||
bold: None,
|
||||
bold_cs: None,
|
||||
italic: None,
|
||||
italic_cs: None,
|
||||
vanish: None,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
use crate::types::*;
|
||||
|
||||
impl ElementReader for Table {
|
||||
fn read<R: Read>(r: &mut EventReader<R>, _: &[OwnedAttribute]) -> Result<Self, ReaderError> {
|
||||
let mut t = Table::new(vec![]);
|
||||
let mut grid_col: Vec<usize> = vec![];
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::TableRow => {
|
||||
t = t.add_row(TableRow::read(r, &attributes)?);
|
||||
continue;
|
||||
}
|
||||
XMLElement::TableWidth => {
|
||||
let (w, width_type) = read_width(&attributes)?;
|
||||
t = t.width(w, width_type);
|
||||
continue;
|
||||
}
|
||||
XMLElement::Justification => {
|
||||
t = t.align(TableAlignmentType::from_str(&attributes[0].value)?);
|
||||
}
|
||||
XMLElement::TableIndent => {
|
||||
let (w, _) = read_width(&attributes)?;
|
||||
t = t.indent(w);
|
||||
continue;
|
||||
}
|
||||
XMLElement::TableBorders => {
|
||||
// TODO: Support later
|
||||
}
|
||||
XMLElement::TableCellMargin => {
|
||||
// TODO: Support later
|
||||
}
|
||||
XMLElement::GridCol => {
|
||||
let (w, _) = read_width(&attributes)?;
|
||||
grid_col.push(w);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::Table {
|
||||
t = t.set_grid(grid_col);
|
||||
return Ok(t);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_read_table_with_width_prop() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:tbl>
|
||||
<w:tblPr>
|
||||
<w:tblW w:w="9638" w:type="dxa"/>
|
||||
</w:tblPr>
|
||||
<w:tblGrid>
|
||||
<w:gridCol w:w="3212"/>
|
||||
<w:gridCol w:w="3213"/>
|
||||
<w:gridCol w:w="3213"/>
|
||||
</w:tblGrid>
|
||||
</w:tbl>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let t = Table::read(&mut parser, &[]).unwrap();
|
||||
assert_eq!(
|
||||
t,
|
||||
Table::new(vec![])
|
||||
.set_grid(vec![3212, 3213, 3213])
|
||||
.width(9638, WidthType::DXA)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_table_with_layout() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:tbl>
|
||||
<w:tblPr>
|
||||
<w:jc w:val="center"/>
|
||||
<w:tblInd w:w="100" w:type="dxa"/>
|
||||
</w:tblPr>
|
||||
</w:tbl>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let t = Table::read(&mut parser, &[]).unwrap();
|
||||
assert_eq!(
|
||||
t,
|
||||
Table::new(vec![])
|
||||
.align(TableAlignmentType::Center)
|
||||
.indent(100)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
use crate::types::*;
|
||||
|
||||
impl ElementReader for TableCell {
|
||||
fn read<R: Read>(r: &mut EventReader<R>, _: &[OwnedAttribute]) -> Result<Self, ReaderError> {
|
||||
let mut cell = TableCell::new();
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::Paragraph => {
|
||||
let p = Paragraph::read(r, &attributes)?;
|
||||
cell = cell.add_paragraph(p);
|
||||
continue;
|
||||
}
|
||||
XMLElement::TableCellProperty => loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
match e {
|
||||
XMLElement::TableCellWidth => {
|
||||
let mut w = 0;
|
||||
let mut width_type = WidthType::Auto;
|
||||
for a in attributes {
|
||||
let local_name = &a.name.local_name;
|
||||
if local_name == "type" {
|
||||
width_type = WidthType::from_str(&a.value)?;
|
||||
} else if local_name == "w" {
|
||||
w = usize::from_str(&a.value)?;
|
||||
}
|
||||
}
|
||||
cell = cell.width(w, width_type);
|
||||
}
|
||||
XMLElement::TableGridSpan => {
|
||||
cell = cell
|
||||
.grid_span(usize::from_str(&attributes[0].value)?)
|
||||
}
|
||||
XMLElement::TableVMerge => {
|
||||
cell = cell.vertical_merge(VMergeType::from_str(
|
||||
&attributes[0].value,
|
||||
)?);
|
||||
}
|
||||
XMLElement::TableCellBorders => {
|
||||
// TODO: Support table cell borders later
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::TableCellProperty {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::TableCell {
|
||||
return Ok(cell);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_read_cell_with_prop() {
|
||||
let c =
|
||||
r#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:tc>
|
||||
<w:tcPr>
|
||||
<w:tcW w:w="6425" w:type="dxa"/>
|
||||
<w:vMerge w:val="restart"/>
|
||||
<w:gridSpan w:val="2"/>
|
||||
<w:tcBorders>
|
||||
<w:top w:val="single" w:sz="2" w:space="0" w:color="000000"/>
|
||||
<w:left w:val="single" w:sz="2" w:space="0" w:color="000000"/>
|
||||
<w:bottom w:val="single" w:sz="2" w:space="0" w:color="000000"/>
|
||||
<w:insideH w:val="single" w:sz="2" w:space="0" w:color="000000"/>
|
||||
</w:tcBorders>
|
||||
<w:shd w:fill="auto" w:val="clear"/>
|
||||
</w:tcPr>
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:rPr></w:rPr>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:tc>
|
||||
</w:document>"#;
|
||||
let mut parser = EventReader::new(c.as_bytes());
|
||||
let cell = TableCell::read(&mut parser, &[]).unwrap();
|
||||
assert_eq!(
|
||||
cell,
|
||||
TableCell::new()
|
||||
.add_paragraph(Paragraph::new().add_run(Run::new()))
|
||||
.width(6425, WidthType::DXA)
|
||||
.grid_span(2)
|
||||
.vertical_merge(VMergeType::Restart),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl ElementReader for TableRow {
|
||||
fn read<R: Read>(r: &mut EventReader<R>, _: &[OwnedAttribute]) -> Result<Self, ReaderError> {
|
||||
let mut cells = vec![];
|
||||
loop {
|
||||
let e = r.next();
|
||||
match e {
|
||||
Ok(XmlEvent::StartElement {
|
||||
attributes, name, ..
|
||||
}) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if let XMLElement::TableCell = e {
|
||||
cells.push(TableCell::read(r, &attributes)?);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name, .. }) => {
|
||||
let e = XMLElement::from_str(&name.local_name).unwrap();
|
||||
if e == XMLElement::TableRow {
|
||||
return Ok(TableRow::new(cells));
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(ReaderError::XMLReadError),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
use xml::attribute::OwnedAttribute;
|
||||
use xml::reader::EventReader;
|
||||
|
||||
use crate::reader::ReaderError;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum XMLElement {
|
||||
Body,
|
||||
Paragraph,
|
||||
Run,
|
||||
RunProperty,
|
||||
Color,
|
||||
Underline,
|
||||
Size,
|
||||
SizeCs,
|
||||
Vanish,
|
||||
Italic,
|
||||
ItalicCs,
|
||||
Text,
|
||||
Highlight,
|
||||
Bold,
|
||||
BoldCs,
|
||||
Break,
|
||||
Tab,
|
||||
ParagraphStyle,
|
||||
Indent,
|
||||
Alignment,
|
||||
NumberingProperty,
|
||||
IndentLevel,
|
||||
NumberingId,
|
||||
Justification,
|
||||
Insert,
|
||||
Delete,
|
||||
DeleteText,
|
||||
BookmarkStart,
|
||||
BookmarkEnd,
|
||||
CommentRangeStart,
|
||||
CommentRangeEnd,
|
||||
Table,
|
||||
TableProperty,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCellProperty,
|
||||
TableCellWidth,
|
||||
TableCellBorders,
|
||||
TableVMerge,
|
||||
TableGridSpan,
|
||||
TableWidth,
|
||||
TableIndent,
|
||||
TableBorders,
|
||||
Top,
|
||||
Left,
|
||||
Bottom,
|
||||
InsideH,
|
||||
TableCellMargin,
|
||||
TableGrid,
|
||||
GridCol,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl FromStr for XMLElement {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"body" => Ok(XMLElement::Body),
|
||||
"p" => Ok(XMLElement::Paragraph),
|
||||
"r" => Ok(XMLElement::Run),
|
||||
"rPr" => Ok(XMLElement::RunProperty),
|
||||
"color" => Ok(XMLElement::Color),
|
||||
"t" => Ok(XMLElement::Text),
|
||||
"sz" => Ok(XMLElement::Size),
|
||||
"szCs" => Ok(XMLElement::SizeCs),
|
||||
"u" => Ok(XMLElement::Underline),
|
||||
"pStyle" => Ok(XMLElement::ParagraphStyle),
|
||||
"highlight" => Ok(XMLElement::Highlight),
|
||||
"b" => Ok(XMLElement::Bold),
|
||||
"bCs" => Ok(XMLElement::BoldCs),
|
||||
"i" => Ok(XMLElement::Italic),
|
||||
"iCs" => Ok(XMLElement::ItalicCs),
|
||||
"vanish" => Ok(XMLElement::Vanish),
|
||||
"italic" => Ok(XMLElement::Italic),
|
||||
"tab" => Ok(XMLElement::Tab),
|
||||
"br" => Ok(XMLElement::Break),
|
||||
"ind" => Ok(XMLElement::Indent),
|
||||
"numPr" => Ok(XMLElement::NumberingProperty),
|
||||
"ilvl" => Ok(XMLElement::IndentLevel),
|
||||
"numId" => Ok(XMLElement::NumberingId),
|
||||
"jc" => Ok(XMLElement::Justification),
|
||||
"ins" => Ok(XMLElement::Insert),
|
||||
"del" => Ok(XMLElement::Delete),
|
||||
"delText" => Ok(XMLElement::DeleteText),
|
||||
"bookmarkStart" => Ok(XMLElement::BookmarkStart),
|
||||
"bookmarkEnd" => Ok(XMLElement::BookmarkEnd),
|
||||
"commentRangeStart" => Ok(XMLElement::CommentRangeStart),
|
||||
"commentRangeEnd" => Ok(XMLElement::CommentRangeEnd),
|
||||
"tbl" => Ok(XMLElement::Table),
|
||||
"tblPr" => Ok(XMLElement::TableProperty),
|
||||
"tr" => Ok(XMLElement::TableRow),
|
||||
"tc" => Ok(XMLElement::TableCell),
|
||||
"tcPr" => Ok(XMLElement::TableCellProperty),
|
||||
"tcW" => Ok(XMLElement::TableCellWidth),
|
||||
"tcBorders" => Ok(XMLElement::TableCellBorders),
|
||||
"vMerge" => Ok(XMLElement::TableVMerge),
|
||||
"gridSpan" => Ok(XMLElement::TableGridSpan),
|
||||
"tblW" => Ok(XMLElement::TableWidth),
|
||||
"tblInd" => Ok(XMLElement::TableIndent),
|
||||
"tblBorders" => Ok(XMLElement::TableBorders),
|
||||
"top" => Ok(XMLElement::Top),
|
||||
"left" => Ok(XMLElement::Left),
|
||||
"bottom" => Ok(XMLElement::Bottom),
|
||||
"insideH" => Ok(XMLElement::InsideH),
|
||||
"tblCellMar" => Ok(XMLElement::TableCellMargin),
|
||||
"tblGrid" => Ok(XMLElement::TableGrid),
|
||||
"gridCol" => Ok(XMLElement::GridCol),
|
||||
_ => Ok(XMLElement::Unsupported),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ElementReader {
|
||||
fn read<R: Read>(r: &mut EventReader<R>, attrs: &[OwnedAttribute]) -> Result<Self, ReaderError>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use super::errors;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AlignmentType {
|
||||
|
@ -20,3 +23,16 @@ impl fmt::Display for AlignmentType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AlignmentType {
|
||||
type Err = errors::TypeError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"left" => Ok(AlignmentType::Left),
|
||||
"right" => Ok(AlignmentType::Right),
|
||||
"center" => Ok(AlignmentType::Center),
|
||||
"justified" => Ok(AlignmentType::Justified),
|
||||
_ => Err(errors::TypeError::FromStrError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BorderPosition {
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
IndideH,
|
||||
IndideV,
|
||||
InsideH,
|
||||
InsideV,
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
//
|
||||
// Please see p3813 <xsd:simpleType name="ST_Border">
|
||||
//
|
||||
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BorderType {
|
||||
None,
|
||||
Single,
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
//
|
||||
// Please see <xsd:simpleType name="ST_BrType">
|
||||
//
|
||||
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use super::errors;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum BreakType {
|
||||
Page,
|
||||
Column,
|
||||
|
@ -22,3 +27,15 @@ impl fmt::Display for BreakType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BreakType {
|
||||
type Err = errors::TypeError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"page" => Ok(BreakType::Page),
|
||||
"column" => Ok(BreakType::Column),
|
||||
"textWrapping" => Ok(BreakType::TextWrapping),
|
||||
_ => Err(errors::TypeError::FromStrError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypeError {
|
||||
#[error("Failed to convert str to enum.")]
|
||||
FromStrError,
|
||||
#[error("Unknown error.")]
|
||||
Unknown,
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue