Read table style (#191)
* feat: Read table style * feat: reader and json types * spec: update snaps * fix: lint error * publishmain
parent
a365d75a8d
commit
8d78be9ceb
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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
|
@ -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 = {
|
||||
|
|
|
@ -57,6 +57,7 @@ export type TablePropertyJSON = {
|
|||
width: number;
|
||||
widthType: WidthType;
|
||||
};
|
||||
style: string | null;
|
||||
};
|
||||
|
||||
export type TableJSON = {
|
||||
|
|
|
@ -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
|
@ -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.
Loading…
Reference in New Issue