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: reader
main
bokuweb 2020-02-11 17:01:39 +09:00 committed by GitHub
parent 6c7dad56e5
commit 5fe4e4d646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
143 changed files with 2887 additions and 369 deletions

79
Cargo.lock generated
View File

@ -1,5 +1,10 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # 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]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@ -79,10 +84,12 @@ name = "docx-rs"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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]] [[package]]
@ -114,6 +121,17 @@ dependencies = [
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "futures" name = "futures"
version = "0.1.29" version = "0.1.29"
@ -127,6 +145,11 @@ dependencies = [
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.30" version = "0.3.30"
@ -158,6 +181,14 @@ name = "memchr"
version = "2.2.1" version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "nom" name = "nom"
version = "4.2.3" version = "4.2.3"
@ -228,11 +259,44 @@ name = "rustc-demangle"
version = "0.1.16" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "sourcefile" name = "sourcefile"
version = "0.1.4" version = "0.1.4"
@ -442,14 +506,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "zip" name = "zip"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[metadata] [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 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 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" "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 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 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 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 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 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 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 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 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 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 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 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 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" "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 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 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 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 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 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 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" "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-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 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 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"

View File

@ -23,8 +23,9 @@ path = "src/lib.rs"
xml-rs = "0.8.0" xml-rs = "0.8.0"
wasm-bindgen = "0.2.50" wasm-bindgen = "0.2.50"
thiserror = "1.0" 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] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View File

@ -9,20 +9,20 @@ pub fn main() -> Result<(), DocxError> {
.add_paragraph( .add_paragraph(
Paragraph::new() Paragraph::new()
.add_run(Run::new().add_text(DUMMY)) .add_run(Run::new().add_text(DUMMY))
.indent(840, None), .indent(840, None, None),
) )
.add_paragraph(Paragraph::new()) .add_paragraph(Paragraph::new())
.add_paragraph( .add_paragraph(Paragraph::new().add_run(Run::new().add_text(DUMMY)).indent(
Paragraph::new() 840,
.add_run(Run::new().add_text(DUMMY)) Some(SpecialIndentType::FirstLine(720)),
.indent(840, Some(SpecialIndentType::FirstLine(720))), None,
) ))
.add_paragraph(Paragraph::new()) .add_paragraph(Paragraph::new())
.add_paragraph( .add_paragraph(Paragraph::new().add_run(Run::new().add_text(DUMMY)).indent(
Paragraph::new() 1560,
.add_run(Run::new().add_text(DUMMY)) Some(SpecialIndentType::Hanging(720)),
.indent(1560, Some(SpecialIndentType::Hanging(720))), None,
) ))
.build() .build()
.pack(file)?; .pack(file)?;
Ok(()) Ok(())

View File

@ -1,6 +1,5 @@
use docx_rs::*; use docx_rs::*;
pub fn main() -> Result<(), DocxError> { pub fn main() -> Result<(), DocxError> {
let path = std::path::Path::new("./numbering.docx"); let path = std::path::Path::new("./numbering.docx");
let file = std::fs::File::create(&path).unwrap(); let file = std::fs::File::create(&path).unwrap();
@ -19,7 +18,7 @@ pub fn main() -> Result<(), DocxError> {
LevelText::new("Section %1."), LevelText::new("Section %1."),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(1620, Some(SpecialIndentType::Hanging(320))), .indent(1620, Some(SpecialIndentType::Hanging(320)), None),
), ),
) )
.build() .build()

View File

@ -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());
}

View File

@ -2,7 +2,10 @@ use super::Comment;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Comments { pub struct Comments {
comments: Vec<Comment>, comments: Vec<Comment>,
} }

View File

@ -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::documents::BuildXML;
use crate::reader::{FromXML, ReaderError};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ContentTypes {} pub struct ContentTypes {
types: HashMap<String, String>,
}
impl ContentTypes { impl ContentTypes {
pub fn new() -> ContentTypes { pub fn new() -> ContentTypes {
Default::default() 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 { impl Default for ContentTypes {
fn default() -> Self { fn default() -> Self {
ContentTypes {} ContentTypes {
types: HashMap::new(),
}
} }
} }
impl BuildXML for ContentTypes { impl BuildXML for ContentTypes {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();
b.declaration(None) let mut b = b
.open_types("http://schemas.openxmlformats.org/package/2006/content-types") .declaration(None)
.add_override( .open_types("http://schemas.openxmlformats.org/package/2006/content-types");
"/_rels/.rels", for (k, v) in self.types.iter() {
"application/vnd.openxmlformats-package.relationships+xml", b = b.add_override(k, v);
) }
.add_override( b.close().build()
"/docProps/app.xml", }
"application/vnd.openxmlformats-officedocument.extended-properties+xml", }
)
.add_override( impl FromXML for ContentTypes {
"/docProps/core.xml", fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
"application/vnd.openxmlformats-package.core-properties+xml", let parser = EventReader::new(reader);
) let mut s = Self::default();
.add_override( let mut depth = 0;
"/word/_rels/document.xml.rels", for e in parser {
"application/vnd.openxmlformats-package.relationships+xml", match e {
) Ok(XmlEvent::StartElement { attributes, .. }) => {
.add_override( if depth == 1 {
"/word/settings.xml", let namespace = attributes[0].value.clone();
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", let path = attributes[1].value.clone();
) s = s.add_content(namespace, path);
.add_override( }
"/word/fontTable.xml", depth += 1;
"application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml", }
) Ok(XmlEvent::EndElement { .. }) => {
.add_override( depth -= 1;
"/word/document.xml", }
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", Err(_) => return Err(ReaderError::XMLReadError),
) _ => {}
.add_override( }
"/word/styles.xml", }
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", Ok(s)
) }
.add_override( }
"/word/comments.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml", #[cfg(test)]
) mod tests {
.add_override(
"/word/numbering.xml", use super::*;
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml", #[cfg(test)]
) use pretty_assertions::assert_eq;
.close()
.build() #[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);
} }
} }

View File

@ -1,7 +1,10 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AppProps {} pub struct AppProps {}
impl AppProps { impl AppProps {

View File

@ -1,12 +1,16 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CoreProps { pub struct CoreProps {
config: CorePropsConfig, config: CorePropsConfig,
} }
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CorePropsConfig { pub struct CorePropsConfig {
created: Option<String>, created: Option<String>,
creator: Option<String>, creator: Option<String>,

View File

@ -3,12 +3,16 @@ mod core;
pub use self::app::*; pub use self::app::*;
pub use self::core::*; pub use self::core::*;
use crate::documents::BuildXML; use crate::documents::BuildXML;
#[derive(Debug)] use serde::Serialize;
pub(crate) struct DocProps {
app: AppProps, #[derive(Debug, Clone, PartialEq, Serialize)]
core: CoreProps, #[serde(rename_all = "camelCase")]
pub struct DocProps {
pub app: AppProps,
pub core: CoreProps,
} }
impl DocProps { impl DocProps {

View File

@ -1,20 +1,56 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use super::{Paragraph, SectionProperty, Table}; use super::{Paragraph, SectionProperty, Table};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Document { pub struct Document {
pub(crate) children: Vec<DocumentChild>, pub children: Vec<DocumentChild>,
pub section_property: SectionProperty, pub section_property: SectionProperty,
pub has_numbering: bool, pub has_numbering: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum DocumentChild { pub enum DocumentChild {
Paragraph(Paragraph), Paragraph(Paragraph),
Table(Table), 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 { impl Document {
pub fn new() -> Document { pub fn new() -> Document {
Default::default() 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 { impl BuildXML for Document {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let mut b = XMLBuilder::new() let mut b = XMLBuilder::new()

View File

@ -1,10 +1,13 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentRels { pub struct DocumentRels {
pub(crate) has_comments: bool, pub has_comments: bool,
pub(crate) has_numberings: bool, pub has_numberings: bool,
} }
impl DocumentRels { impl DocumentRels {

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Bold {} pub struct Bold {}
impl 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 { impl BuildXML for Bold {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct BoldCs {} pub struct BoldCs {}
impl 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 { impl BuildXML for BoldCs {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();

View File

@ -1,21 +1,23 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct BookmarkEnd { pub struct BookmarkEnd {
id: String, id: usize,
} }
impl BookmarkEnd { impl BookmarkEnd {
pub fn new(id: impl Into<String>) -> BookmarkEnd { pub fn new(id: usize) -> BookmarkEnd {
BookmarkEnd { id: id.into() } BookmarkEnd { id }
} }
} }
impl BuildXML for BookmarkEnd { impl BuildXML for BookmarkEnd {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();
b.bookmark_end(&self.id).build() b.bookmark_end(&format!("{}", self.id)).build()
} }
} }
@ -29,11 +31,8 @@ mod tests {
#[test] #[test]
fn test_bookmark_end() { fn test_bookmark_end() {
let c = BookmarkEnd::new("mockid"); let c = BookmarkEnd::new(0);
let b = c.build(); let b = c.build();
assert_eq!( assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:bookmarkEnd w:id="0" />"#);
str::from_utf8(&b).unwrap(),
r#"<w:bookmarkEnd w:id="mockid" />"#
);
} }
} }

View File

@ -1,16 +1,18 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct BookmarkStart { pub struct BookmarkStart {
id: String, id: usize,
name: String, name: String,
} }
impl BookmarkStart { 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 { BookmarkStart {
id: id.into(), id,
name: name.into(), name: name.into(),
} }
} }
@ -19,7 +21,8 @@ impl BookmarkStart {
impl BuildXML for BookmarkStart { impl BuildXML for BookmarkStart {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); 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] #[test]
fn test_bookmark_start() { fn test_bookmark_start() {
let c = BookmarkStart::new("mockid", "mockname"); let c = BookmarkStart::new(0, "mockname");
let b = c.build(); let b = c.build();
assert_eq!( assert_eq!(
str::from_utf8(&b).unwrap(), str::from_utf8(&b).unwrap(),
r#"<w:bookmarkStart w:id="mockid" w:name="mockname" />"# r#"<w:bookmarkStart w:id="0" w:name="mockname" />"#
); );
} }
} }

View File

@ -1,8 +1,11 @@
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde::Deserialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Break { pub struct Break {
break_type: BreakType, break_type: BreakType,
} }
@ -19,3 +22,14 @@ impl BuildXML for Break {
b.br(&self.break_type.to_string()).build() 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()
}
}

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct Color { pub struct Color {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::documents::{BuildXML, Paragraph}; use crate::documents::{BuildXML, Paragraph};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct Comment { pub struct Comment {
pub id: usize, pub id: usize,
pub author: String, pub author: String,

View File

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct CommentRangeEnd { pub struct CommentRangeEnd {
id: usize, id: usize,
} }

View File

@ -1,8 +1,10 @@
use serde::Serialize;
use super::Comment; use super::Comment;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct CommentRangeStart { pub struct CommentRangeStart {
id: usize, id: usize,
comment: Comment, comment: Comment,

View File

@ -1,7 +1,9 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::{Serialize, Serializer};
#[derive(Debug, Clone, PartialEq)]
pub struct DefaultTabStop { pub struct DefaultTabStop {
val: usize, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::documents::{BuildXML, HistoryId, Run}; use crate::documents::{BuildXML, HistoryId, Run};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct Delete { pub struct Delete {
pub author: String, pub author: String,
pub date: String, pub date: String,

View File

@ -1,7 +1,10 @@
use serde::{Deserialize, Serialize};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteText { pub struct DeleteText {
text: String, text: String,
preserve_space: bool, preserve_space: bool,

View File

@ -1,9 +1,12 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
use super::run_property_default::*; use super::run_property_default::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocDefaults { pub struct DocDefaults {
run_property_default: RunPropertyDefault, run_property_default: RunPropertyDefault,
} }

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct GridSpan { pub struct GridSpan {
val: usize, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Highlight { pub struct Highlight {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {
@ -35,4 +46,11 @@ mod tests {
r#"<w:highlight w:val="FFFFFF" />"# 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""#);
}
} }

View File

@ -1,30 +1,61 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Indent { pub struct Indent {
left: usize, start: usize,
end: Option<usize>,
special_indent: Option<SpecialIndentType>, special_indent: Option<SpecialIndentType>,
} }
impl Indent { 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 { Indent {
left, start,
end,
special_indent, special_indent,
} }
} }
pub fn end(mut self, end: usize) -> Self {
self.end = Some(end);
self
}
} }
impl BuildXML for Indent { impl BuildXML for Indent {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
XMLBuilder::new() XMLBuilder::new()
.indent(self.left, self.special_indent) .indent(
self.start,
self.special_indent,
self.end.unwrap_or_default(),
)
.build() .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)] #[cfg(test)]
mod tests { mod tests {
@ -35,25 +66,28 @@ mod tests {
#[test] #[test]
fn test_left() { fn test_left() {
let b = Indent::new(20, None).build(); let b = Indent::new(20, None, None).build();
assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:ind w:left="20" />"#); assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:ind w:left="20" w:right="0" />"#
);
} }
#[test] #[test]
fn test_first_line() { 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!( assert_eq!(
str::from_utf8(&b).unwrap(), 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] #[test]
fn test_hanging() { 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!( assert_eq!(
str::from_utf8(&b).unwrap(), 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" />"#
); );
} }
} }

View File

@ -1,9 +1,9 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct IndentLevel { pub struct IndentLevel {
val: usize, pub val: usize,
} }
impl IndentLevel { impl IndentLevel {

View File

@ -1,7 +1,10 @@
use serde::Serialize;
use crate::documents::{BuildXML, HistoryId, Run}; use crate::documents::{BuildXML, HistoryId, Run};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
pub struct Insert { pub struct Insert {
pub run: Run, pub run: Run,
pub author: String, pub author: String,

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Italic {} pub struct Italic {}
impl 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 { impl BuildXML for Italic {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct ItalicCs {} pub struct ItalicCs {}
impl 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 { impl BuildXML for ItalicCs {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();

View File

@ -1,3 +1,5 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; 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 // 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 // of mathematical text can be aligned with respect to each other, but the entire group of mathematical text is
// centered as a whole. // centered as a whole.
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Justification { pub struct Justification {
val: String, pub val: String,
} }
impl Justification { 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -2,13 +2,16 @@ use crate::documents::{BuildXML, LevelJc, LevelText, NumberFormat, ParagraphProp
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Level { pub struct Level {
level: usize, pub level: usize,
start: Start, pub start: Start,
format: NumberFormat, pub format: NumberFormat,
text: LevelText, pub text: LevelText,
jc: LevelJc, pub jc: LevelJc,
pub paragraph_property: ParagraphProperty, pub paragraph_property: ParagraphProperty,
} }
@ -30,8 +33,13 @@ impl Level {
} }
} }
pub fn indent(mut self, left: usize, special_indent: Option<SpecialIndentType>) -> Self { pub fn indent(
self.paragraph_property = self.paragraph_property.indent(left, special_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 self
} }
} }
@ -83,11 +91,11 @@ mod tests {
LevelText::new("%4."), LevelText::new("%4."),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(320, Some(SpecialIndentType::Hanging(200))) .indent(320, Some(SpecialIndentType::Hanging(200)), None)
.build(); .build();
assert_eq!( assert_eq!(
str::from_utf8(&b).unwrap(), 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>"#
); );
} }
} }

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct LevelJc { pub struct LevelJc {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct LevelText { pub struct LevelText {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
pub struct Name { pub struct Name {
name: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct NumberFormat { pub struct NumberFormat {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,10 @@
use crate::documents::{BuildXML, Level}; use crate::documents::{BuildXML, Level};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Numbering { pub struct Numbering {
id: usize, id: usize,
levels: Vec<Level>, levels: Vec<Level>,

View File

@ -1,9 +1,9 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct NumberingId { pub struct NumberingId {
id: usize, pub id: usize,
} }
impl NumberingId { impl NumberingId {

View File

@ -1,11 +1,14 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use super::{IndentLevel, NumberingId}; use super::{IndentLevel, NumberingId};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct NumberingProperty { pub struct NumberingProperty {
id: NumberingId, pub id: NumberingId,
level: IndentLevel, pub level: IndentLevel,
} }
impl NumberingProperty { 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,10 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PageMargin { pub struct PageMargin {
top: usize, top: usize,
left: usize, left: usize,

View File

@ -1,7 +1,10 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PageSize { pub struct PageSize {
w: usize, w: usize,
h: usize, h: usize,

View File

@ -1,14 +1,18 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use super::*; use super::*;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Paragraph { pub struct Paragraph {
pub children: Vec<ParagraphChild>, pub children: Vec<ParagraphChild>,
pub property: ParagraphProperty, pub property: ParagraphProperty,
pub has_numbering: bool, pub has_numbering: bool,
attrs: Vec<(String, String)>, pub attrs: Vec<(String, String)>,
} }
impl Default for Paragraph { impl Default for Paragraph {
@ -22,7 +26,7 @@ impl Default for Paragraph {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum ParagraphChild { pub enum ParagraphChild {
Run(Run), Run(Run),
Insert(Insert), 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 { impl Paragraph {
pub fn new() -> Paragraph { pub fn new() -> Paragraph {
Default::default() Default::default()
@ -76,17 +107,13 @@ impl Paragraph {
self self
} }
pub fn add_bookmark_start( pub fn add_bookmark_start(mut self, id: usize, name: impl Into<String>) -> Paragraph {
mut self,
id: impl Into<String>,
name: impl Into<String>,
) -> Paragraph {
self.children self.children
.push(ParagraphChild::BookmarkStart(BookmarkStart::new(id, name))); .push(ParagraphChild::BookmarkStart(BookmarkStart::new(id, name)));
self 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 self.children
.push(ParagraphChild::BookmarkEnd(BookmarkEnd::new(id))); .push(ParagraphChild::BookmarkEnd(BookmarkEnd::new(id)));
self self
@ -115,8 +142,13 @@ impl Paragraph {
self self
} }
pub fn indent(mut self, left: usize, special_indent: Option<SpecialIndentType>) -> Paragraph { pub fn indent(
self.property = self.property.indent(left, special_indent); mut self,
left: usize,
special_indent: Option<SpecialIndentType>,
end: Option<usize>,
) -> Paragraph {
self.property = self.property.indent(left, special_indent, end);
self self
} }
@ -172,13 +204,13 @@ mod tests {
#[test] #[test]
fn test_bookmark() { fn test_bookmark() {
let b = Paragraph::new() let b = Paragraph::new()
.add_bookmark_start("1234-5678", "article") .add_bookmark_start(0, "article")
.add_run(Run::new().add_text("Hello")) .add_run(Run::new().add_text("Hello"))
.add_bookmark_end("1234-5678") .add_bookmark_end(0)
.build(); .build();
assert_eq!( assert_eq!(
str::from_utf8(&b).unwrap(), 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>"# 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":[]}"#
);
}
} }

View File

@ -1,15 +1,18 @@
use serde::Serialize;
use super::*; use super::*;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::{AlignmentType, SpecialIndentType}; use crate::types::{AlignmentType, SpecialIndentType};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ParagraphProperty { pub struct ParagraphProperty {
run_property: RunProperty, pub run_property: RunProperty,
style: ParagraphStyle, pub style: ParagraphStyle,
numbering_property: Option<NumberingProperty>, pub numbering_property: Option<NumberingProperty>,
alignment: Option<Justification>, pub alignment: Option<Justification>,
indent: Option<Indent>, pub indent: Option<Indent>,
} }
impl Default for ParagraphProperty { impl Default for ParagraphProperty {
@ -45,8 +48,13 @@ impl ParagraphProperty {
self self
} }
pub fn indent(mut self, left: usize, special_indent: Option<SpecialIndentType>) -> Self { pub fn indent(
self.indent = Some(Indent::new(left, special_indent)); mut self,
left: usize,
special_indent: Option<SpecialIndentType>,
end: Option<usize>,
) -> Self {
self.indent = Some(Indent::new(left, special_indent, end));
self self
} }
@ -101,10 +109,20 @@ mod tests {
#[test] #[test]
fn test_indent() { fn test_indent() {
let c = ParagraphProperty::new(); let c = ParagraphProperty::new();
let b = c.indent(20, None).build(); let b = c.indent(20, None, None).build();
assert_eq!( assert_eq!(
str::from_utf8(&b).unwrap(), 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}}}"#
); );
} }
} }

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct ParagraphStyle { pub struct ParagraphStyle {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,9 +1,13 @@
use super::{Break, DeleteText, RunProperty, Tab, Text}; use super::{Break, DeleteText, RunProperty, Tab, Text};
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::BreakType; use crate::types::BreakType;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Run { pub struct Run {
pub run_property: RunProperty, pub run_property: RunProperty,
pub children: Vec<RunChild>, pub children: Vec<RunChild>,
@ -19,7 +23,7 @@ impl Default for Run {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub enum RunChild { pub enum RunChild {
Text(Text), Text(Text),
DeleteText(DeleteText), DeleteText(DeleteText),
@ -27,6 +31,39 @@ pub enum RunChild {
Break(Break), 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 { impl Run {
pub fn new() -> Run { pub fn new() -> Run {
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.children.push(RunChild::Text(Text::new(text)));
self 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 self.children
.push(RunChild::DeleteText(DeleteText::new(text))); .push(RunChild::DeleteText(DeleteText::new(text)));
self self
@ -110,6 +147,7 @@ impl BuildXML for Run {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::*;
use super::*; use super::*;
#[cfg(test)] #[cfg(test)]
use pretty_assertions::assert_eq; 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>"# 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}}]}"#
);
}
} }

View File

@ -1,19 +1,22 @@
use serde::{Deserialize, Serialize};
use super::{Bold, BoldCs, Color, Highlight, Italic, ItalicCs, Sz, SzCs, Underline, Vanish}; use super::{Bold, BoldCs, Color, Highlight, Italic, ItalicCs, Sz, SzCs, Underline, Vanish};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RunProperty { pub struct RunProperty {
sz: Option<Sz>, pub sz: Option<Sz>,
sz_cs: Option<SzCs>, pub sz_cs: Option<SzCs>,
color: Option<Color>, pub color: Option<Color>,
highlight: Option<Highlight>, pub highlight: Option<Highlight>,
underline: Option<Underline>, pub underline: Option<Underline>,
bold: Option<Bold>, pub bold: Option<Bold>,
bold_cs: Option<BoldCs>, pub bold_cs: Option<BoldCs>,
italic: Option<Italic>, pub italic: Option<Italic>,
italic_cs: Option<ItalicCs>, pub italic_cs: Option<ItalicCs>,
vanish: Option<Vanish>, pub vanish: Option<Vanish>,
} }
impl RunProperty { impl RunProperty {

View File

@ -1,8 +1,11 @@
use serde::Serialize;
use super::RunProperty; use super::RunProperty;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RunPropertyDefault { pub struct RunPropertyDefault {
run_property: RunProperty, run_property: RunProperty,
} }

View File

@ -2,7 +2,10 @@ use super::*;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SectionProperty { pub struct SectionProperty {
page_size: PageSize, page_size: PageSize,
page_margin: PageMargin, page_margin: PageMargin,

View File

@ -1,7 +1,9 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Start { pub struct Start {
val: usize, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,16 +1,19 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
use crate::StyleType; use crate::StyleType;
use super::{BasedOn, Name, Next, ParagraphProperty, QFormat, RunProperty}; use super::{BasedOn, Name, Next, ParagraphProperty, QFormat, RunProperty};
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Style { pub struct Style {
style_id: String, pub style_id: String,
name: Name, pub name: Name,
style_type: StyleType, pub style_type: StyleType,
run_property: RunProperty, pub run_property: RunProperty,
paragraph_property: ParagraphProperty, pub paragraph_property: ParagraphProperty,
} }
impl Default for Style { impl Default for Style {

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Sz { pub struct Sz {
val: usize, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,8 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
use serde::{Deserialize, Serialize, Serializer};
#[derive(Debug, Clone)] #[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct SzCs { pub struct SzCs {
val: usize, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Tab {} pub struct Tab {}
impl Tab { impl Tab {

View File

@ -1,14 +1,17 @@
use serde::Serialize;
use super::{TableGrid, TableProperty, TableRow}; use super::{TableGrid, TableProperty, TableRow};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Table { pub struct Table {
pub rows: Vec<TableRow>, pub rows: Vec<TableRow>,
pub grid: Vec<usize>, pub grid: Vec<usize>,
pub(crate) has_numbering: bool, pub has_numbering: bool,
property: TableProperty, pub property: TableProperty,
} }
impl Table { 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 { pub fn set_grid(mut self, grid: Vec<usize>) -> Table {
self.grid = grid; self.grid = grid;
self self
@ -39,8 +47,8 @@ impl Table {
self self
} }
pub fn width(mut self, w: usize) -> Table { pub fn width(mut self, w: usize, t: WidthType) -> Table {
self.property = self.property.width(w, WidthType::DXA); self.property = self.property.width(w, t);
self self
} }
} }
@ -97,4 +105,13 @@ mod tests {
</w:tblGrid><w:tr><w:trPr /></w:tr></w:tbl>"# </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}}"#
);
}
} }

View File

@ -1,3 +1,5 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
@ -15,7 +17,8 @@ use crate::xml_builder::*;
tr2bl diagonal border from top right corner to bottom left corner 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 { pub struct TableBorder {
position: BorderPosition, position: BorderPosition,
border_type: BorderType, border_type: BorderType,
@ -57,10 +60,10 @@ impl BuildXML for TableBorder {
BorderPosition::Right => { BorderPosition::Right => {
base.border_right(self.border_type, self.size, self.space, &self.color) 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) 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) 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 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 // If this element is omitted, then this table shall have the borders specified by the associated table level borders
// (§17.4.38). // (§17.4.38).
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TableBorders { pub struct TableBorders {
top: Option<TableBorder>, top: Option<TableBorder>,
left: Option<TableBorder>, left: Option<TableBorder>,
@ -97,8 +101,8 @@ impl Default for TableBorders {
left: Some(TableBorder::new(BorderPosition::Left)), left: Some(TableBorder::new(BorderPosition::Left)),
bottom: Some(TableBorder::new(BorderPosition::Bottom)), bottom: Some(TableBorder::new(BorderPosition::Bottom)),
right: Some(TableBorder::new(BorderPosition::Right)), right: Some(TableBorder::new(BorderPosition::Right)),
inside_h: Some(TableBorder::new(BorderPosition::IndideH)), inside_h: Some(TableBorder::new(BorderPosition::InsideH)),
inside_v: Some(TableBorder::new(BorderPosition::IndideV)), inside_v: Some(TableBorder::new(BorderPosition::InsideV)),
} }
} }
} }
@ -114,8 +118,8 @@ impl TableBorders {
BorderPosition::Left => self.left = Some(border), BorderPosition::Left => self.left = Some(border),
BorderPosition::Bottom => self.bottom = Some(border), BorderPosition::Bottom => self.bottom = Some(border),
BorderPosition::Right => self.right = Some(border), BorderPosition::Right => self.right = Some(border),
BorderPosition::IndideH => self.inside_h = Some(border), BorderPosition::InsideH => self.inside_h = Some(border),
BorderPosition::IndideV => self.inside_v = Some(border), BorderPosition::InsideV => self.inside_v = Some(border),
}; };
self self
} }
@ -126,8 +130,8 @@ impl TableBorders {
BorderPosition::Left => self.left = None, BorderPosition::Left => self.left = None,
BorderPosition::Bottom => self.bottom = None, BorderPosition::Bottom => self.bottom = None,
BorderPosition::Right => self.right = None, BorderPosition::Right => self.right = None,
BorderPosition::IndideH => self.inside_h = None, BorderPosition::InsideH => self.inside_h = None,
BorderPosition::IndideV => self.inside_v = None, BorderPosition::InsideV => self.inside_v = None,
}; };
self self
} }

View File

@ -1,20 +1,40 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use super::{Paragraph, TableCellProperty}; use super::{Paragraph, TableCellProperty};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TableCell { pub struct TableCell {
pub contents: Vec<TableCellContent>, pub children: Vec<TableCellContent>,
pub property: TableCellProperty, pub property: TableCellProperty,
pub has_numbering: bool, pub has_numbering: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum TableCellContent { pub enum TableCellContent {
Paragraph(Paragraph), 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 { impl TableCell {
pub fn new() -> TableCell { pub fn new() -> TableCell {
Default::default() Default::default()
@ -24,7 +44,7 @@ impl TableCell {
if p.has_numbering { if p.has_numbering {
self.has_numbering = true self.has_numbering = true
} }
self.contents.push(TableCellContent::Paragraph(p)); self.children.push(TableCellContent::Paragraph(p));
self self
} }
@ -38,8 +58,8 @@ impl TableCell {
self self
} }
pub fn width(mut self, v: usize) -> TableCell { pub fn width(mut self, v: usize, t: WidthType) -> TableCell {
self.property = self.property.width(v, WidthType::DXA); self.property = self.property.width(v, t);
self self
} }
} }
@ -47,10 +67,10 @@ impl TableCell {
impl Default for TableCell { impl Default for TableCell {
fn default() -> Self { fn default() -> Self {
let property = TableCellProperty::new(); let property = TableCellProperty::new();
let contents = vec![]; let children = vec![];
Self { Self {
property, property,
contents, children,
has_numbering: false, has_numbering: false,
} }
} }
@ -60,7 +80,7 @@ impl BuildXML for TableCell {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();
let mut b = b.open_table_cell().add_child(&self.property); let mut b = b.open_table_cell().add_child(&self.property);
for c in &self.contents { for c in &self.children {
match c { match c {
TableCellContent::Paragraph(p) => b = b.add_child(p), 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>"# 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}"#
);
}
} }

View File

@ -1,3 +1,5 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
@ -15,7 +17,8 @@ use crate::xml_builder::*;
tr2bl diagonal border from top right corner to bottom left corner 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 { pub struct TableCellBorder {
position: BorderPosition, position: BorderPosition,
border_type: BorderType, border_type: BorderType,
@ -57,10 +60,10 @@ impl BuildXML for TableCellBorder {
BorderPosition::Right => { BorderPosition::Right => {
base.border_right(self.border_type, self.size, self.space, &self.color) 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) 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) 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 { pub struct TableCellBorders {
top: Option<TableCellBorder>, top: Option<TableCellBorder>,
left: Option<TableCellBorder>, left: Option<TableCellBorder>,
@ -85,8 +89,8 @@ impl Default for TableCellBorders {
left: Some(TableCellBorder::new(BorderPosition::Left)), left: Some(TableCellBorder::new(BorderPosition::Left)),
bottom: Some(TableCellBorder::new(BorderPosition::Bottom)), bottom: Some(TableCellBorder::new(BorderPosition::Bottom)),
right: Some(TableCellBorder::new(BorderPosition::Right)), right: Some(TableCellBorder::new(BorderPosition::Right)),
inside_h: Some(TableCellBorder::new(BorderPosition::IndideH)), inside_h: Some(TableCellBorder::new(BorderPosition::InsideH)),
inside_v: Some(TableCellBorder::new(BorderPosition::IndideV)), inside_v: Some(TableCellBorder::new(BorderPosition::InsideV)),
} }
} }
} }
@ -102,8 +106,8 @@ impl TableCellBorders {
BorderPosition::Left => self.left = Some(border), BorderPosition::Left => self.left = Some(border),
BorderPosition::Bottom => self.bottom = Some(border), BorderPosition::Bottom => self.bottom = Some(border),
BorderPosition::Right => self.right = Some(border), BorderPosition::Right => self.right = Some(border),
BorderPosition::IndideH => self.inside_h = Some(border), BorderPosition::InsideH => self.inside_h = Some(border),
BorderPosition::IndideV => self.inside_v = Some(border), BorderPosition::InsideV => self.inside_v = Some(border),
}; };
self self
} }
@ -114,8 +118,8 @@ impl TableCellBorders {
BorderPosition::Left => self.left = None, BorderPosition::Left => self.left = None,
BorderPosition::Bottom => self.bottom = None, BorderPosition::Bottom => self.bottom = None,
BorderPosition::Right => self.right = None, BorderPosition::Right => self.right = None,
BorderPosition::IndideH => self.inside_h = None, BorderPosition::InsideH => self.inside_h = None,
BorderPosition::IndideV => self.inside_v = None, BorderPosition::InsideV => self.inside_v = None,
}; };
self self
} }

View File

@ -1,8 +1,11 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TableCellMargins { pub struct TableCellMargins {
top: usize, top: usize,
left: usize, left: usize,

View File

@ -1,9 +1,12 @@
use serde::Serialize;
use super::{GridSpan, TableCellBorders, TableCellWidth, VMerge}; use super::{GridSpan, TableCellBorders, TableCellWidth, VMerge};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TableCellProperty { pub struct TableCellProperty {
width: Option<TableCellWidth>, width: Option<TableCellWidth>,
borders: Option<TableCellBorders>, borders: Option<TableCellBorders>,
@ -90,4 +93,16 @@ mod tests {
r#"<w:tcPr><w:vMerge w:val="continue" /></w:tcPr>"# 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"}"#
);
}
} }

View File

@ -1,8 +1,11 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TableCellWidth { pub struct TableCellWidth {
width: usize, width: usize,
width_type: WidthType, width_type: WidthType,

View File

@ -1,8 +1,11 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TableIndent { pub struct TableIndent {
width: usize, width: usize,
width_type: WidthType, width_type: WidthType,

View File

@ -1,9 +1,12 @@
use serde::Serialize;
use super::{Justification, TableBorders, TableCellMargins, TableIndent, TableWidth}; use super::{Justification, TableBorders, TableCellMargins, TableIndent, TableWidth};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TableProperty { pub struct TableProperty {
width: TableWidth, width: TableWidth,
justification: Justification, justification: Justification,
@ -81,4 +84,13 @@ mod tests {
</w:tblCellMar></w:tblPr>"# </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"}}"#
);
}
} }

View File

@ -1,12 +1,15 @@
use serde::Serialize;
use super::{TableCell, TableRowProperty}; use super::{TableCell, TableRowProperty};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TableRow { pub struct TableRow {
pub cells: Vec<TableCell>, pub cells: Vec<TableCell>,
pub(crate) has_numbering: bool, pub has_numbering: bool,
property: TableRowProperty, pub property: TableRowProperty,
} }
impl TableRow { impl TableRow {
@ -47,4 +50,13 @@ mod tests {
r#"<w:tr><w:trPr /><w:tc><w:tcPr /></w:tc></w:tr>"# 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":{}}"#
);
}
} }

View File

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
pub struct TableRowProperty {} pub struct TableRowProperty {}
impl TableRowProperty { impl TableRowProperty {

View File

@ -1,8 +1,11 @@
use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TableWidth { pub struct TableWidth {
width: usize, width: usize,
width_type: WidthType, width_type: WidthType,

View File

@ -1,17 +1,21 @@
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde::Deserialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::escape::escape; use crate::escape::escape;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Text { pub struct Text {
text: String, text: String,
preserve_space: bool, preserve_space: bool,
} }
impl Text { impl Text {
pub fn new(text: &str) -> Text { pub fn new(text: impl Into<String>) -> Text {
Text { Text {
text: escape(text), text: escape(&text.into()),
preserve_space: true, 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)] #[cfg(test)]
mod tests { mod tests {
@ -39,4 +55,13 @@ mod tests {
r#"<w:t xml:space="preserve">Hello</w:t>"# 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"}"#
);
}
} }

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Underline { pub struct Underline {
val: String, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Vanish {} pub struct Vanish {}
impl 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,8 +1,10 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct VMerge { pub struct VMerge {
val: VMergeType, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,7 +1,9 @@
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug, Clone)] use serde::{Serialize, Serializer};
#[derive(Debug, Clone, PartialEq)]
pub struct Zoom { pub struct Zoom {
val: usize, 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)] #[cfg(test)]
mod tests { mod tests {

View File

@ -3,7 +3,10 @@ use crate::documents::BuildXML;
use crate::types::FontPitchType; use crate::types::FontPitchType;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FontTable {} pub struct FontTable {}
impl FontTable { impl FontTable {

View File

@ -29,12 +29,15 @@ pub use settings::*;
pub use styles::*; pub use styles::*;
pub use xml_docx::*; pub use xml_docx::*;
#[derive(Debug)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Docx { pub struct Docx {
content_type: ContentTypes, pub content_type: ContentTypes,
rels: Rels, pub rels: Rels,
document_rels: DocumentRels, pub document_rels: DocumentRels,
doc_props: DocProps, pub doc_props: DocProps,
pub styles: Styles, pub styles: Styles,
pub document: Document, pub document: Document,
pub comments: Comments, pub comments: Comments,
@ -45,7 +48,7 @@ pub struct Docx {
impl Default for Docx { impl Default for Docx {
fn default() -> Self { fn default() -> Self {
let content_type = ContentTypes::new(); let content_type = ContentTypes::new().set_default();
let rels = Rels::new(); let rels = Rels::new();
let doc_props = DocProps::new(CorePropsConfig::new()); let doc_props = DocProps::new(CorePropsConfig::new());
let styles = Styles::new(); let styles = Styles::new();
@ -75,6 +78,11 @@ impl Docx {
Default::default() Default::default()
} }
pub fn document(mut self, d: Document) -> Docx {
self.document = d;
self
}
pub fn add_paragraph(mut self, p: Paragraph) -> Docx { pub fn add_paragraph(mut self, p: Paragraph) -> Docx {
if p.has_numbering { if p.has_numbering {
// If this document has numbering, set numberings.xml to document_rels. // 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. // Traverse and clone comments from document and add to comments node.
fn update_comments(&mut self) { fn update_comments(&mut self) {
let mut comments: Vec<Comment> = vec![]; let mut comments: Vec<Comment> = vec![];
@ -141,7 +154,7 @@ impl Docx {
DocumentChild::Table(table) => { DocumentChild::Table(table) => {
for row in &table.rows { for row in &table.rows {
for cell in &row.cells { for cell in &row.cells {
for content in &cell.contents { for content in &cell.children {
match content { match content {
TableCellContent::Paragraph(paragraph) => { TableCellContent::Paragraph(paragraph) => {
for child in &paragraph.children { for child in &paragraph.children {

View File

@ -3,7 +3,10 @@ use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Numberings { pub struct Numberings {
numberings: Vec<Numbering>, numberings: Vec<Numbering>,
} }
@ -46,7 +49,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("%1."), LevelText::new("%1."),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(420, Some(SpecialIndentType::Hanging(420))), .indent(420, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -56,7 +59,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("(%2)"), LevelText::new("(%2)"),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(840, Some(SpecialIndentType::Hanging(420))), .indent(840, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -66,7 +69,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("%3"), LevelText::new("%3"),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(1260, Some(SpecialIndentType::Hanging(420))), .indent(1260, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -76,7 +79,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("%4."), LevelText::new("%4."),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(1680, Some(SpecialIndentType::Hanging(420))), .indent(1680, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -86,7 +89,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("(%5)"), LevelText::new("(%5)"),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(2100, Some(SpecialIndentType::Hanging(420))), .indent(2100, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -96,7 +99,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("%6"), LevelText::new("%6"),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(2520, Some(SpecialIndentType::Hanging(420))), .indent(2520, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -106,7 +109,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("%7."), LevelText::new("%7."),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(2940, Some(SpecialIndentType::Hanging(420))), .indent(2940, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -116,7 +119,7 @@ fn create_default_numbering() -> Numbering {
LevelText::new("(%8)"), LevelText::new("(%8)"),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(3360, Some(SpecialIndentType::Hanging(420))), .indent(3360, Some(SpecialIndentType::Hanging(420)), None),
) )
.add_level( .add_level(
Level::new( Level::new(
@ -126,6 +129,6 @@ fn create_default_numbering() -> Numbering {
LevelText::new("%9"), LevelText::new("%9"),
LevelJc::new("left"), LevelJc::new("left"),
) )
.indent(3780, Some(SpecialIndentType::Hanging(420))), .indent(3780, Some(SpecialIndentType::Hanging(420)), None),
) )
} }

View File

@ -1,43 +1,69 @@
use serde::{Deserialize, Serialize};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Rels {} pub struct Rels {
pub rels: Vec<(String, String, String)>,
}
impl Rels { impl Rels {
pub fn new() -> Rels { pub fn new() -> Rels {
Default::default() 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 { impl Default for Rels {
fn default() -> Self { fn default() -> Self {
Rels {} Rels { rels: Vec::new() }
} }
} }
impl BuildXML for Rels { impl BuildXML for Rels {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();
b.declaration(None) let mut b = b
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships") .declaration(None)
.relationship( .open_relationships("http://schemas.openxmlformats.org/package/2006/relationships");
"rId1", for (k, id, v) in self.rels.iter() {
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", b = b.relationship(id, k, v);
"docProps/core.xml" }
) b.close().build()
.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()
} }
} }
@ -51,7 +77,7 @@ mod tests {
#[test] #[test]
fn test_build() { fn test_build() {
let c = Rels::new(); let c = Rels::new().set_default();
let b = c.build(); let b = c.build();
assert_eq!( assert_eq!(
str::from_utf8(&b).unwrap(), str::from_utf8(&b).unwrap(),

View File

@ -2,7 +2,10 @@ use super::{DefaultTabStop, Zoom};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Settings { pub struct Settings {
default_tab_stop: DefaultTabStop, default_tab_stop: DefaultTabStop,
zoom: Zoom, zoom: Zoom,

View File

@ -1,9 +1,12 @@
use serde::Serialize;
use super::{DocDefaults, Style}; use super::{DocDefaults, Style};
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Styles { pub struct Styles {
doc_defaults: DocDefaults, doc_defaults: DocDefaults,
styles: Vec<Style>, styles: Vec<Style>,

View File

@ -1,10 +1,12 @@
mod documents; mod documents;
mod errors; mod errors;
mod escape; mod escape;
mod reader;
mod types; mod types;
mod xml_builder; mod xml_builder;
mod zipper; mod zipper;
pub use documents::*; pub use documents::*;
pub use errors::*; pub use errors::*;
pub use reader::*;
pub use types::*; pub use types::*;

View File

@ -0,0 +1,3 @@
mod width;
pub use width::*;

View File

@ -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))
}

View File

@ -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),
_ => {}
}
}
}
}

View File

@ -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)
}
}

View File

@ -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,
}

View File

@ -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;
}

View File

@ -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),
_ => {}
}
}
}
}

View File

@ -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)
}

View File

@ -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),
_ => {}
}
}
}
}

View File

@ -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![],
}
);
}
}

View File

@ -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);
}
}

View File

@ -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,
},
}
);
}
}

View File

@ -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)
);
}
}

View File

@ -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),
);
}
}

View File

@ -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),
_ => {}
}
}
}
}

View File

@ -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;
}

View File

@ -1,6 +1,9 @@
use std::fmt; use std::fmt;
use std::str::FromStr;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use super::errors;
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum AlignmentType { 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),
}
}
}

View File

@ -1,9 +1,12 @@
#[derive(Debug, Clone)] use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BorderPosition { pub enum BorderPosition {
Left, Left,
Right, Right,
Top, Top,
Bottom, Bottom,
IndideH, InsideH,
IndideV, InsideV,
} }

View File

@ -1,12 +1,13 @@
// //
// Please see p3813 <xsd:simpleType name="ST_Border"> // Please see p3813 <xsd:simpleType name="ST_Border">
// //
use serde::Serialize;
use std::fmt; use std::fmt;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BorderType { pub enum BorderType {
None, None,
Single, Single,

View File

@ -1,12 +1,17 @@
use serde::{Deserialize, Serialize};
// //
// Please see <xsd:simpleType name="ST_BrType"> // Please see <xsd:simpleType name="ST_BrType">
// //
use std::fmt; use std::fmt;
use std::str::FromStr;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use super::errors;
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum BreakType { pub enum BreakType {
Page, Page,
Column, 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),
}
}
}

View File

@ -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