Support tblppr (#693)

* feat: Support table position

* fix: table pr

* fix: wasm for table position

* fix: reader for position property

* fix: snaps

* fix

* fix
main
bokuweb 2024-03-27 00:09:58 +09:00 committed by GitHub
parent 9cb9ea8215
commit 666c950484
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 438 additions and 6 deletions

View File

@ -107,6 +107,7 @@ mod table_indent;
mod table_layout; mod table_layout;
mod table_of_contents; mod table_of_contents;
mod table_of_contents_item; mod table_of_contents_item;
mod table_position_property;
mod table_property; mod table_property;
mod table_row; mod table_row;
mod table_row_property; mod table_row_property;
@ -238,6 +239,7 @@ pub use table_indent::*;
pub use table_layout::*; pub use table_layout::*;
pub use table_of_contents::*; pub use table_of_contents::*;
pub use table_of_contents_item::*; pub use table_of_contents_item::*;
pub use table_position_property::*;
pub use table_property::*; pub use table_property::*;
pub use table_row::*; pub use table_row::*;
pub use table_row_property::*; pub use table_row_property::*;

View File

@ -85,6 +85,11 @@ impl Table {
self self
} }
pub fn position(mut self, p: TablePositionProperty) -> Self {
self.property = self.property.position(p);
self
}
pub fn width(mut self, w: usize, t: WidthType) -> Table { pub fn width(mut self, w: usize, t: WidthType) -> Table {
self.property = self.property.width(w, t); self.property = self.property.width(w, t);
self self

View File

@ -0,0 +1,110 @@
use serde::Serialize;
use crate::documents::BuildXML;
use crate::xml_builder::*;
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.tablepositionproperties?view=openxml-3.0.1
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "wasm", derive(ts_rs::TS), ts(export))]
pub struct TablePositionProperty {
#[serde(skip_serializing_if = "Option::is_none")]
pub left_from_text: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub right_from_text: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vertical_anchor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub horizontal_anchor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position_x_alignment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position_y_alignment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position_x: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position_y: Option<i32>,
}
impl TablePositionProperty {
pub fn new() -> TablePositionProperty {
Default::default()
}
pub fn left_from_text(mut self, v: i32) -> Self {
self.left_from_text = Some(v);
self
}
pub fn right_from_text(mut self, v: i32) -> Self {
self.right_from_text = Some(v);
self
}
pub fn vertical_anchor(mut self, v: impl Into<String>) -> Self {
self.vertical_anchor = Some(v.into());
self
}
pub fn horizontal_anchor(mut self, v: impl Into<String>) -> Self {
self.horizontal_anchor = Some(v.into());
self
}
pub fn position_x_alignment(mut self, v: impl Into<String>) -> Self {
self.position_x_alignment = Some(v.into());
self
}
pub fn position_y_alignment(mut self, v: impl Into<String>) -> Self {
self.position_y_alignment = Some(v.into());
self
}
pub fn position_x(mut self, v: i32) -> Self {
self.position_x = Some(v);
self
}
pub fn position_y(mut self, v: i32) -> Self {
self.position_y = Some(v);
self
}
}
impl BuildXML for TablePositionProperty {
fn build(&self) -> Vec<u8> {
XMLBuilder::new().table_position_property(self).build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::str;
#[test]
fn test_default() {
let b = TablePositionProperty::new().build();
assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:tblpPr />"#);
}
#[test]
fn test_some_attrs() {
let b = TablePositionProperty::new()
.left_from_text(142)
.right_from_text(142)
.vertical_anchor("text")
.horizontal_anchor("margin")
.position_x_alignment("right")
.position_y(511)
.build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:tblpPr w:leftFromText="142" w:rightFromText="142" w:vertAnchor="text" w:horzAnchor="margin" w:tblpXSpec="right" w:tblpY="511" />"#
);
}
}

View File

@ -22,6 +22,8 @@ pub struct TableProperty {
style: Option<TableStyle>, style: Option<TableStyle>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
layout: Option<TableLayout>, layout: Option<TableLayout>,
#[serde(skip_serializing_if = "Option::is_none")]
position: Option<TablePositionProperty>,
} }
impl Default for TableProperty { impl Default for TableProperty {
@ -34,6 +36,7 @@ impl Default for TableProperty {
indent: None, indent: None,
style: None, style: None,
layout: None, layout: None,
position: None,
} }
} }
} }
@ -139,6 +142,11 @@ impl TableProperty {
self.layout = Some(TableLayout::new(t)); self.layout = Some(TableLayout::new(t));
self self
} }
pub fn position(mut self, p: TablePositionProperty) -> Self {
self.position = Some(p);
self
}
} }
impl BuildXML for TableProperty { impl BuildXML for TableProperty {
@ -152,6 +160,7 @@ impl BuildXML for TableProperty {
.add_optional_child(&self.indent) .add_optional_child(&self.indent)
.add_optional_child(&self.style) .add_optional_child(&self.style)
.add_optional_child(&self.layout) .add_optional_child(&self.layout)
.add_optional_child(&self.position)
.close() .close()
.build() .build()
} }

View File

@ -56,6 +56,7 @@ mod table_cell_borders;
mod table_cell_margins; mod table_cell_margins;
mod table_cell_property; mod table_cell_property;
mod table_property; mod table_property;
mod table_position_property;
mod table_row; mod table_row;
mod tabs; mod tabs;
mod text_box_content; mod text_box_content;

View File

@ -32,6 +32,11 @@ impl ElementReader for Table {
t = t.width(w as usize, width_type); t = t.width(w as usize, width_type);
continue; continue;
} }
XMLElement::TableProperty => {
if let Ok(p) = TableProperty::read(r, &attributes) {
t.property = p;
}
}
XMLElement::Justification => { XMLElement::Justification => {
t = t.align(TableAlignmentType::from_str(&attributes[0].value)?); t = t.align(TableAlignmentType::from_str(&attributes[0].value)?);
} }

View File

@ -0,0 +1,55 @@
use std::io::Read;
use std::str::FromStr;
use xml::attribute::OwnedAttribute;
use xml::reader::EventReader;
use super::*;
impl ElementReader for TablePositionProperty {
fn read<R: Read>(
_r: &mut EventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<Self, ReaderError> {
let mut property = TablePositionProperty::new();
for a in attrs {
let local_name = &a.name.local_name;
match local_name.as_str() {
"leftFromText" => {
if let Ok(v) = i32::from_str(&a.value) {
property = property.left_from_text(v);
}
}
"rightFromText" => {
if let Ok(v) = i32::from_str(&a.value) {
property = property.right_from_text(v);
}
}
"vertAnchor" => {
property = property.vertical_anchor(a.value.clone());
}
"horzAnchor" => {
property = property.horizontal_anchor(a.value.clone());
}
"tblpXSpec" => {
property = property.position_x_alignment(a.value.clone());
}
"tblpYSpec" => {
property = property.position_y_alignment(a.value.clone());
}
"tblpX" => {
if let Ok(v) = i32::from_str(&a.value) {
property = property.position_x(v);
}
}
"tblpY" => {
if let Ok(v) = i32::from_str(&a.value) {
property = property.position_y(v);
}
}
_ => {}
}
}
Ok(property)
}
}

View File

@ -4,8 +4,11 @@ use std::str::FromStr;
use xml::attribute::OwnedAttribute; use xml::attribute::OwnedAttribute;
use xml::reader::{EventReader, XmlEvent}; use xml::reader::{EventReader, XmlEvent};
use crate::TableAlignmentType;
use super::*; use super::*;
// TODO: layout: Option<TableLayout>,
impl ElementReader for TableProperty { impl ElementReader for TableProperty {
fn read<R: Read>( fn read<R: Read>(
r: &mut EventReader<R>, r: &mut EventReader<R>,
@ -31,6 +34,33 @@ impl ElementReader for TableProperty {
tp = tp.set_margins(margins); tp = tp.set_margins(margins);
} }
} }
XMLElement::TableWidth => {
if let Ok((w, width_type)) = read_width(&attributes) {
tp = tp.width(w as usize, width_type);
}
}
XMLElement::Justification => {
if let Ok(v) = TableAlignmentType::from_str(&attributes[0].value) {
tp = tp.align(v);
}
}
XMLElement::TableIndent => {
if let Ok((w, _)) = read_width(&attributes) {
if w != 0 {
tp = tp.indent(w as i32);
}
}
}
XMLElement::TableStyle => {
if let Some(s) = read_val(&attributes) {
tp = tp.style(s);
}
}
XMLElement::TablePositionProperty => {
if let Ok(p) = TablePositionProperty::read(r, &attributes) {
tp = tp.position(p);
}
}
_ => {} _ => {}
} }
} }

View File

@ -93,6 +93,7 @@ pub enum XMLElement {
TableIndent, TableIndent,
TableBorders, TableBorders,
TableCellMargin, TableCellMargin,
TablePositionProperty,
TableStyle, TableStyle,
// Change // Change
TableGridChange, TableGridChange,
@ -327,6 +328,7 @@ impl FromStr for XMLElement {
"tblBorders" => Ok(XMLElement::TableBorders), "tblBorders" => Ok(XMLElement::TableBorders),
"tblCellMar" => Ok(XMLElement::TableCellMargin), "tblCellMar" => Ok(XMLElement::TableCellMargin),
"tblStyle" => Ok(XMLElement::TableStyle), "tblStyle" => Ok(XMLElement::TableStyle),
"tblpPr" => Ok(XMLElement::TablePositionProperty),
"top" => Ok(XMLElement::Top), "top" => Ok(XMLElement::Top),
"right" => Ok(XMLElement::Right), "right" => Ok(XMLElement::Right),
"start" => Ok(XMLElement::Start), "start" => Ok(XMLElement::Start),

View File

@ -2,6 +2,7 @@ use super::XMLBuilder;
use super::XmlEvent; use super::XmlEvent;
use crate::types::*; use crate::types::*;
use crate::FrameProperty; use crate::FrameProperty;
use crate::TablePositionProperty;
const EXPECT_MESSAGE: &str = "should write buf"; const EXPECT_MESSAGE: &str = "should write buf";
@ -630,6 +631,53 @@ impl XMLBuilder {
self.close() self.close()
} }
pub(crate) fn table_position_property(mut self, prop: &TablePositionProperty) -> Self {
let mut w = XmlEvent::start_element("w:tblpPr");
let v: String = format!("{}", prop.left_from_text.unwrap_or_default());
if prop.left_from_text.is_some() {
w = w.attr("w:leftFromText", &v);
}
let v: String = format!("{}", prop.right_from_text.unwrap_or_default());
if prop.right_from_text.is_some() {
w = w.attr("w:rightFromText", &v);
}
let v: String = prop.vertical_anchor.iter().cloned().collect();
if prop.vertical_anchor.is_some() {
w = w.attr("w:vertAnchor", &v);
}
let v: String = prop.horizontal_anchor.iter().cloned().collect();
if prop.horizontal_anchor.is_some() {
w = w.attr("w:horzAnchor", &v);
}
let v: String = prop.position_x_alignment.iter().cloned().collect();
if prop.position_x_alignment.is_some() {
w = w.attr("w:tblpXSpec", &v);
}
let v: String = prop.position_y_alignment.iter().cloned().collect();
if prop.position_y_alignment.is_some() {
w = w.attr("w:tblpYSpec", &v);
}
let v: String = format!("{}", prop.position_x.unwrap_or_default());
if prop.position_x.is_some() {
w = w.attr("w:tblpX", &v);
}
let v: String = format!("{}", prop.position_y.unwrap_or_default());
if prop.position_y.is_some() {
w = w.attr("w:tblpY", &v);
}
self.writer.write(w).expect(EXPECT_MESSAGE);
self.close()
}
pub(crate) fn page_num_type(mut self, start: Option<u32>, chap_style: Option<String>) -> Self { pub(crate) fn page_num_type(mut self, start: Option<u32>, chap_style: Option<String>) -> Self {
let mut w = XmlEvent::start_element("w:pgNumType"); let mut w = XmlEvent::start_element("w:pgNumType");
let start_string = format!("{}", start.unwrap_or_default()); let start_string = format!("{}", start.unwrap_or_default());

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

@ -0,0 +1,2 @@
export interface TablePositionProperty { leftFromText?: number, rightFromText?: number, verticalAnchor?: string, horizontalAnchor?: string, positionXAlignment?: string, positionYAlignment?: string, positionX?: number, positionY?: number, }

View File

@ -7,6 +7,9 @@ import { TableLayoutType } from "../table";
import { DeleteJSONData, InsertJSONData, TableCellBordersJSON } from ".."; import { DeleteJSONData, InsertJSONData, TableCellBordersJSON } from "..";
import { StructuredTagJSON } from "./structured-data-tag"; import { StructuredTagJSON } from "./structured-data-tag";
import { TablePositionProperty as TablePositionPropertyJSON } from "./bindings/TablePositionProperty";
export { TablePositionProperty as TablePositionPropertyJSON } from "./bindings/TablePositionProperty";
export { TableCellBorder as TableCellBorderJSON } from "./bindings/TableCellBorder"; export { TableCellBorder as TableCellBorderJSON } from "./bindings/TableCellBorder";
export { TableCellBorders as TableCellBordersJSON } from "./bindings/TableCellBorders"; export { TableCellBorders as TableCellBordersJSON } from "./bindings/TableCellBorders";
@ -97,6 +100,7 @@ export type TablePropertyJSON = {
} | null; } | null;
style?: string | null; style?: string | null;
layout?: TableLayoutType | null; layout?: TableLayoutType | null;
position?: TablePositionPropertyJSON;
}; };
export type TableJSON = { export type TableJSON = {

View File

@ -3,9 +3,86 @@ import * as wasm from "./pkg";
import { WidthType } from "."; import { WidthType } from ".";
import { TableRow } from "./table-row"; import { TableRow } from "./table-row";
import { TablePositionProperty } from "./json/bindings/TablePositionProperty";
export { TablePositionProperty } from "./json/bindings/TablePositionProperty";
export type TableAlignmentType = "center" | "left" | "right"; export type TableAlignmentType = "center" | "left" | "right";
export type TableLayoutType = "fixed" | "autofit"; export type TableLayoutType = "fixed" | "autofit";
export class TablePosition {
property: TablePositionProperty;
leftFromText(n: number) {
this.property.leftFromText = n;
return this;
}
rightFromText(n: number) {
this.property.rightFromText = n;
return this;
}
verticalAnchor(n: string) {
this.property.verticalAnchor = n;
return this;
}
horizontalAnchor(n: string) {
this.property.horizontalAnchor = n;
return this;
}
positionXAlignment(n: string) {
this.property.positionXAlignment = n;
return this;
}
positionYAlignment(n: string) {
this.property.positionYAlignment = n;
return this;
}
positionX(n: number) {
this.property.positionX = n;
return this;
}
positionY(n: number) {
this.property.positionY = n;
return this;
}
build() {
let p = wasm.createTablePosition();
if (this.property.leftFromText != null) {
p = p.left_from_text(this.property.leftFromText);
}
if (this.property.rightFromText != null) {
p = p.left_from_text(this.property.rightFromText);
}
if (this.property.verticalAnchor != null) {
p = p.vertical_anchor(this.property.verticalAnchor);
}
if (this.property.horizontalAnchor != null) {
p = p.horizontal_anchor(this.property.horizontalAnchor);
}
if (this.property.positionXAlignment != null) {
p = p.position_x_alignment(this.property.positionXAlignment);
}
if (this.property.positionYAlignment != null) {
p = p.position_y_alignment(this.property.positionYAlignment);
}
if (this.property.positionX != null) {
p = p.position_x(this.property.positionX);
}
if (this.property.positionY != null) {
p = p.position_y(this.property.positionY);
}
return p;
}
}
export type TableProperty = { export type TableProperty = {
indent?: number; indent?: number;
align?: TableAlignmentType; align?: TableAlignmentType;
@ -18,6 +95,7 @@ export type TableProperty = {
right: { val: number; type: WidthType }; right: { val: number; type: WidthType };
}; };
layout?: TableLayoutType; layout?: TableLayoutType;
position?: TablePosition;
}; };
export const createDefaultTableCellMargins = () => { export const createDefaultTableCellMargins = () => {
@ -105,6 +183,11 @@ export class Table {
return this; return this;
} }
position(p: TablePosition) {
this.property.position = p;
return this;
}
build() { build() {
let table = wasm.createTable(); let table = wasm.createTable();
this.rows.forEach((r) => { this.rows.forEach((r) => {
@ -151,6 +234,10 @@ export class Table {
table = table.style(this.property.styleId); table = table.style(this.property.styleId);
} }
if (this.property.position) {
table = table.position(this.property.position.build());
}
table = setTableProperty(table, this.property); table = setTableProperty(table, this.property);
return table; return table;

View File

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

View File

@ -25,6 +25,7 @@ mod table_cell;
mod table_cell_border; mod table_cell_border;
mod table_of_contents; mod table_of_contents;
mod table_of_contents_item; mod table_of_contents_item;
mod table_position_property;
mod table_row; mod table_row;
mod web_extension; mod web_extension;
@ -55,5 +56,6 @@ pub use table_cell::*;
pub use table_cell_border::*; pub use table_cell_border::*;
pub use table_of_contents::*; pub use table_of_contents::*;
pub use table_of_contents_item::*; pub use table_of_contents_item::*;
pub use table_position_property::*;
pub use table_row::*; pub use table_row::*;
pub use web_extension::*; pub use web_extension::*;

View File

@ -54,6 +54,11 @@ impl Table {
self self
} }
pub fn position(mut self, p: TablePositionProperty) -> Table {
self.0 = self.0.position(p.take());
self
}
pub fn set_cell_margins( pub fn set_cell_margins(
mut self, mut self,
top: usize, top: usize,

View File

@ -0,0 +1,59 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug)]
pub struct TablePositionProperty(docx_rs::TablePositionProperty);
#[wasm_bindgen(js_name = createTablePosition)]
pub fn create_table_position() -> TablePositionProperty {
TablePositionProperty(docx_rs::TablePositionProperty::new())
}
#[wasm_bindgen]
impl TablePositionProperty {
pub fn left_from_text(mut self, v: i32) -> Self {
self.0.left_from_text = Some(v);
self
}
pub fn right_from_text(mut self, v: i32) -> Self {
self.0.right_from_text = Some(v);
self
}
pub fn vertical_anchor(mut self, v: &str) -> Self {
self.0.vertical_anchor = Some(v.into());
self
}
pub fn horizontal_anchor(mut self, v: &str) -> Self {
self.0.horizontal_anchor = Some(v.into());
self
}
pub fn position_x_alignment(mut self, v: &str) -> Self {
self.0.position_x_alignment = Some(v.into());
self
}
pub fn position_y_alignment(mut self, v: &str) -> Self {
self.0.position_y_alignment = Some(v.into());
self
}
pub fn position_x(mut self, v: i32) -> Self {
self.0.position_x = Some(v);
self
}
pub fn position_y(mut self, v: i32) -> Self {
self.0.position_y = Some(v);
self
}
}
impl TablePositionProperty {
pub fn take(self) -> docx_rs::TablePositionProperty {
self.0
}
}

View File

@ -12507,6 +12507,12 @@ Object {
}, },
}, },
"justification": "left", "justification": "left",
"position": Object {
"horizontalAnchor": "margin",
"leftFromText": 142,
"positionY": 1118,
"rightFromText": 142,
},
"width": Object { "width": Object {
"width": 9619, "width": 9619,
"widthType": "dxa", "widthType": "dxa",

View File

@ -1 +1 @@
1.70 1.73