Read table style (#191)

* feat: Read table style

* feat: reader and json types

* spec: update snaps

* fix: lint error

* publish
main
bokuweb 2020-10-30 20:29:06 +09:00 committed by GitHub
parent a365d75a8d
commit 8d78be9ceb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2032 additions and 48 deletions

View File

@ -4,7 +4,7 @@ use std::fs::File;
use std::io::{Read, Write};
pub fn main() {
let mut file = File::open("./issue2405.docx").unwrap();
let mut file = File::open("./test.docx").unwrap();
let mut buf = vec![];
file.read_to_end(&mut buf).unwrap();
dbg!(read_docx(&buf).unwrap().json());

View File

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

View File

@ -70,6 +70,7 @@ mod table_indent;
mod table_property;
mod table_row;
mod table_row_property;
mod table_style;
mod table_width;
mod text;
mod text_box_content;
@ -154,6 +155,7 @@ pub use table_indent::*;
pub use table_property::*;
pub use table_row::*;
pub use table_row_property::*;
pub use table_style::*;
pub use table_width::*;
pub use text::*;
pub use text_box_content::*;

View File

@ -5,7 +5,7 @@ use crate::types::*;
use crate::xml_builder::*;
use crate::StyleType;
use super::{BasedOn, Name, Next, ParagraphProperty, QFormat, RunProperty};
use super::{BasedOn, Name, Next, ParagraphProperty, QFormat, RunProperty, TableProperty};
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
@ -15,6 +15,8 @@ pub struct Style {
pub style_type: StyleType,
pub run_property: RunProperty,
pub paragraph_property: ParagraphProperty,
pub table_property: TableProperty,
pub based_on: Option<BasedOn>,
}
impl Default for Style {
@ -28,6 +30,8 @@ impl Default for Style {
name,
run_property: rpr,
paragraph_property: ppr,
table_property: TableProperty::new(),
based_on: None,
}
}
}
@ -47,6 +51,11 @@ impl Style {
self
}
pub fn based_on(mut self, base: impl Into<String>) -> Self {
self.based_on = Some(BasedOn::new(base));
self
}
pub fn size(mut self, size: usize) -> Self {
self.run_property = self.run_property.size(size);
self
@ -99,6 +108,11 @@ impl Style {
.indent(left, special_indent, end, start_chars);
self
}
pub fn table_property(mut self, p: TableProperty) -> Self {
self.table_property = p;
self
}
}
impl BuildXML for Style {
@ -112,6 +126,7 @@ impl BuildXML for Style {
.add_child(&BasedOn::new("Normal"))
.add_child(&Next::new("Normal"))
.add_child(&QFormat::new())
.add_optional_child(&self.based_on)
.close()
.build()
}

View File

@ -47,6 +47,11 @@ impl Table {
self
}
pub fn style(mut self, s: impl Into<String>) -> Table {
self.property = self.property.style(s);
self
}
pub fn width(mut self, w: usize, t: WidthType) -> Table {
self.property = self.property.width(w, t);
self
@ -136,7 +141,7 @@ mod tests {
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":{"borderType":"single","size":2,"color":"000000","position":"top","space":0},"left":{"borderType":"single","size":2,"color":"000000","position":"left","space":0},"bottom":{"borderType":"single","size":2,"color":"000000","position":"bottom","space":0},"right":{"borderType":"single","size":2,"color":"000000","position":"right","space":0},"insideH":{"borderType":"single","size":2,"color":"000000","position":"insideH","space":0},"insideV":{"borderType":"single","size":2,"color":"000000","position":"insideV","space":0}},"margins":{"top":55,"left":54,"bottom":55,"right":55},"indent":null}}"#
r#"{"rows":[],"grid":[100,200,300],"hasNumbering":false,"property":{"width":{"width":0,"widthType":"Auto"},"justification":"left","borders":{"top":{"borderType":"single","size":2,"color":"000000","position":"top","space":0},"left":{"borderType":"single","size":2,"color":"000000","position":"left","space":0},"bottom":{"borderType":"single","size":2,"color":"000000","position":"bottom","space":0},"right":{"borderType":"single","size":2,"color":"000000","position":"right","space":0},"insideH":{"borderType":"single","size":2,"color":"000000","position":"insideH","space":0},"insideV":{"borderType":"single","size":2,"color":"000000","position":"insideV","space":0}},"margins":{"top":55,"left":54,"bottom":55,"right":55},"indent":null,"style":null}}"#
);
}
}

View File

@ -13,6 +13,7 @@ pub struct TableProperty {
borders: TableBorders,
margins: TableCellMargins,
indent: Option<TableIndent>,
style: Option<TableStyle>,
}
impl Default for TableProperty {
@ -23,6 +24,7 @@ impl Default for TableProperty {
borders: TableBorders::new(),
margins: TableCellMargins::new(),
indent: None,
style: None,
}
}
}
@ -71,6 +73,11 @@ impl TableProperty {
self.borders = self.borders.clear_all();
self
}
pub fn style(mut self, s: impl Into<String>) -> Self {
self.style = Some(TableStyle::new(s));
self
}
}
impl BuildXML for TableProperty {
@ -82,6 +89,7 @@ impl BuildXML for TableProperty {
.add_child(&self.borders)
.add_child(&self.margins)
.add_optional_child(&self.indent)
.add_optional_child(&self.style)
.close()
.build()
}
@ -115,7 +123,7 @@ mod tests {
let p = TableProperty::new().indent(100);
assert_eq!(
serde_json::to_string(&p).unwrap(),
r#"{"width":{"width":0,"widthType":"Auto"},"justification":"left","borders":{"top":{"borderType":"single","size":2,"color":"000000","position":"top","space":0},"left":{"borderType":"single","size":2,"color":"000000","position":"left","space":0},"bottom":{"borderType":"single","size":2,"color":"000000","position":"bottom","space":0},"right":{"borderType":"single","size":2,"color":"000000","position":"right","space":0},"insideH":{"borderType":"single","size":2,"color":"000000","position":"insideH","space":0},"insideV":{"borderType":"single","size":2,"color":"000000","position":"insideV","space":0}},"margins":{"top":55,"left":54,"bottom":55,"right":55},"indent":{"width":100,"widthType":"DXA"}}"#
r#"{"width":{"width":0,"widthType":"Auto"},"justification":"left","borders":{"top":{"borderType":"single","size":2,"color":"000000","position":"top","space":0},"left":{"borderType":"single","size":2,"color":"000000","position":"left","space":0},"bottom":{"borderType":"single","size":2,"color":"000000","position":"bottom","space":0},"right":{"borderType":"single","size":2,"color":"000000","position":"right","space":0},"insideH":{"borderType":"single","size":2,"color":"000000","position":"insideH","space":0},"insideV":{"borderType":"single","size":2,"color":"000000","position":"insideV","space":0}},"margins":{"top":55,"left":54,"bottom":55,"right":55},"indent":{"width":100,"widthType":"DXA"},"style":null}"#
);
}
}

View File

@ -0,0 +1,31 @@
use serde::{Serialize, Serializer};
use crate::documents::BuildXML;
use crate::xml_builder::*;
#[derive(Debug, Clone, PartialEq)]
pub struct TableStyle {
val: String,
}
impl TableStyle {
pub fn new(val: impl Into<String>) -> TableStyle {
TableStyle { val: val.into() }
}
}
impl BuildXML for TableStyle {
fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new();
b.table_style(&self.val).build()
}
}
impl Serialize for TableStyle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.val)
}
}

View File

@ -26,8 +26,10 @@ mod settings;
mod style;
mod styles;
mod table;
mod table_borders;
mod table_cell;
mod table_cell_borders;
mod table_property;
mod table_row;
mod text_box_content;
mod wp_anchor;

View File

@ -36,6 +36,12 @@ impl ElementReader for Style {
style = style.name(&attributes[0].value.clone());
continue;
}
XMLElement::BasedOn => {
if let Some(v) = read_val(&attributes) {
style = style.based_on(v);
}
continue;
}
// pPr
XMLElement::Indent => {
let (start, end, special, start_chars) = read_indent(&attributes)?;
@ -70,6 +76,11 @@ impl ElementReader for Style {
style = style.italic();
}
XMLElement::Vanish => style = style.vanish(),
XMLElement::TableProperty => {
if let Ok(p) = TableProperty::read(r, &attributes) {
style = style.table_property(p);
}
}
_ => {}
}
}

View File

@ -67,7 +67,8 @@ mod tests {
styles = styles.add_style(
Style::new("FootnoteTextChar", StyleType::Character)
.name("Footnote Text Char")
.size(20),
.size(20)
.based_on("DefaultParagraphFont"),
);
assert_eq!(s, styles);
}

View File

@ -41,7 +41,14 @@ impl ElementReader for Table {
continue;
}
XMLElement::TableBorders => {
// TODO: Support later
if let Ok(borders) = TableBorders::read(r, &attributes) {
t = t.set_borders(borders);
}
}
XMLElement::TableStyle => {
if let Some(s) = read_val(&attributes) {
t = t.style(s);
}
}
XMLElement::TableCellMargin => {
// TODO: Support later

View File

@ -0,0 +1,101 @@
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 TableBorders {
fn read<R: Read>(r: &mut EventReader<R>, _: &[OwnedAttribute]) -> Result<Self, ReaderError> {
let mut borders = TableBorders::with_empty();
loop {
let e = r.next();
match e {
Ok(XmlEvent::StartElement {
attributes, name, ..
}) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
match e {
XMLElement::Top => {
let attr = read_border(&attributes)?;
let mut border = TableBorder::new(BorderPosition::Top)
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
borders = borders.set(border);
continue;
}
XMLElement::Right => {
let attr = read_border(&attributes)?;
let mut border = TableBorder::new(BorderPosition::Right)
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
borders = borders.set(border);
continue;
}
XMLElement::Bottom => {
let attr = read_border(&attributes)?;
let mut border = TableBorder::new(BorderPosition::Bottom)
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
borders = borders.set(border);
continue;
}
XMLElement::Left => {
let attr = read_border(&attributes)?;
let mut border = TableBorder::new(BorderPosition::Left)
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
borders = borders.set(border);
continue;
}
XMLElement::InsideH => {
let attr = read_border(&attributes)?;
let mut border = TableBorder::new(BorderPosition::InsideH)
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
borders = borders.set(border);
continue;
}
XMLElement::InsideV => {
let attr = read_border(&attributes)?;
let mut border = TableBorder::new(BorderPosition::InsideV)
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
borders = borders.set(border);
continue;
}
_ => {}
}
}
Ok(XmlEvent::EndElement { name, .. }) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
if e == XMLElement::TableBorders {
return Ok(borders);
}
}
Err(_) => return Err(ReaderError::XMLReadError),
_ => {}
}
}
}
}

View File

@ -0,0 +1,40 @@
use std::io::Read;
use std::str::FromStr;
use xml::attribute::OwnedAttribute;
use xml::reader::{EventReader, XmlEvent};
use super::*;
impl ElementReader for TableProperty {
fn read<R: Read>(
r: &mut EventReader<R>,
_attrs: &[OwnedAttribute],
) -> Result<Self, ReaderError> {
let mut tp = TableProperty::new();
tp = tp.set_borders(TableBorders::with_empty());
loop {
let e = r.next();
match e {
Ok(XmlEvent::StartElement {
attributes, name, ..
}) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
if let XMLElement::TableBorders = e {
if let Ok(borders) = TableBorders::read(r, &attributes) {
tp = tp.set_borders(borders);
}
}
}
Ok(XmlEvent::EndElement { name, .. }) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
if e == XMLElement::TableProperty {
return Ok(tp);
}
}
Err(_) => return Err(ReaderError::XMLReadError),
_ => {}
}
}
}
}

View File

@ -31,6 +31,7 @@ pub enum XMLElement {
RunPropertyChange,
Indent,
Name,
BasedOn,
Alignment,
NumberingProperty,
IndentLevel,
@ -57,6 +58,7 @@ pub enum XMLElement {
TableWidth,
TableIndent,
TableBorders,
TableStyle,
// Change
TableGridChange,
TablePropertyChange,
@ -74,7 +76,6 @@ pub enum XMLElement {
GridAfter,
WidthAfter,
Style,
BasedOn,
Next,
VertAlign,
Styles,
@ -212,6 +213,7 @@ impl FromStr for XMLElement {
"tblW" => Ok(XMLElement::TableWidth),
"tblInd" => Ok(XMLElement::TableIndent),
"tblBorders" => Ok(XMLElement::TableBorders),
"tblStyle" => Ok(XMLElement::TableStyle),
"top" => Ok(XMLElement::Top),
"right" => Ok(XMLElement::Right),
"left" => Ok(XMLElement::Left),

View File

@ -182,6 +182,7 @@ impl XMLBuilder {
open!(open_table_borders, "w:tblBorders");
open!(open_table_cell_margins, "w:tblCellMar");
closed_with_str!(table_style, "w:tblStyle");
closed_w_with_type_el!(table_width, "w:tblW");
closed_w_with_type_el!(table_indent, "w:tblInd");
closed_w_with_type_el!(grid_column, "w:gridCol");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
import { RunPropertyJSON } from "./run";
import { ParagraphPropertyJSON } from "./paragraph";
import { TablePropertyJSON } from "./table";
export type StyleJSON = {
styleId: string;
@ -7,6 +8,8 @@ export type StyleJSON = {
styleType: string;
runProperty: RunPropertyJSON;
paragraphProperty: ParagraphPropertyJSON;
tableProperty: TablePropertyJSON;
basedOn: string | null;
};
export type StylesJSON = {

View File

@ -57,6 +57,7 @@ export type TablePropertyJSON = {
width: number;
widthType: WidthType;
};
style: string | null;
};
export type TableJSON = {

View File

@ -1,6 +1,6 @@
{
"name": "docx-wasm",
"version": "0.0.115",
"version": "0.0.116",
"main": "dist/node/index.js",
"browser": "dist/web/index.js",
"author": "bokuweb <bokuweb12@gmail.com>",

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,12 @@ describe("reader", () => {
const json = w.readDocx(buf);
expect(json).toMatchSnapshot();
});
test("should read table style docx", () => {
const buf = readFileSync("../fixtures/table_style/table_style.docx");
const json = w.readDocx(buf);
expect(json).toMatchSnapshot();
});
});
describe("writer", () => {

Binary file not shown.