docxdriver 0.4.3__tar.gz → 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docxdriver-0.4.3 → docxdriver-0.4.4}/Cargo.lock +6 -6
- {docxdriver-0.4.3 → docxdriver-0.4.4}/Cargo.toml +1 -1
- {docxdriver-0.4.3 → docxdriver-0.4.4}/PKG-INFO +1 -1
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/api.rs +74 -2
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/mod.rs +3 -2
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/table_structure.rs +47 -2
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/typed.rs +58 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4/crates/docxdriver-python}/python/docxdriver/plan.py +3 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/tests/test_document.py +23 -1
- {docxdriver-0.4.3/crates/docxdriver-python → docxdriver-0.4.4}/python/docxdriver/plan.py +3 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/README.md +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/Cargo.toml +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/LICENSE +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/README.md +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/assets/blank.docx +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/bookmarks.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/comments.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/equations.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/headers.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/layout.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/lists.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/reading.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/readonly.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/revisions.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/sections.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/tables.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/document.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/facade.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/assets.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/build.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/chrome.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/compact.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/ctx.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/input.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/mathml_omml.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/mod.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/omml_latex.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/omml_mathml.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/parse.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/render.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/styles.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/html/surface.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/integrity/mod.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/integrity/schema.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/integrity/tree.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/integrity/wml-model.json +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/lib.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/media.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/outcome.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/package.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/segments.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/.gitignore +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/Cargo.toml +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/README.md +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/python/docxdriver/__init__.py +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/python/docxdriver/document.py +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/src/lib.rs +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/tests/test_plan.py +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/pyproject.toml +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/python/docxdriver/__init__.py +0 -0
- {docxdriver-0.4.3 → docxdriver-0.4.4}/python/docxdriver/document.py +0 -0
|
@@ -416,7 +416,7 @@ dependencies = [
|
|
|
416
416
|
|
|
417
417
|
[[package]]
|
|
418
418
|
name = "docxdriver-cli"
|
|
419
|
-
version = "0.4.
|
|
419
|
+
version = "0.4.4"
|
|
420
420
|
dependencies = [
|
|
421
421
|
"clap",
|
|
422
422
|
"docxdriver-core",
|
|
@@ -427,7 +427,7 @@ dependencies = [
|
|
|
427
427
|
|
|
428
428
|
[[package]]
|
|
429
429
|
name = "docxdriver-core"
|
|
430
|
-
version = "0.4.
|
|
430
|
+
version = "0.4.4"
|
|
431
431
|
dependencies = [
|
|
432
432
|
"hex",
|
|
433
433
|
"latex2mathml",
|
|
@@ -445,7 +445,7 @@ dependencies = [
|
|
|
445
445
|
|
|
446
446
|
[[package]]
|
|
447
447
|
name = "docxdriver-mcp"
|
|
448
|
-
version = "0.4.
|
|
448
|
+
version = "0.4.4"
|
|
449
449
|
dependencies = [
|
|
450
450
|
"axum",
|
|
451
451
|
"cap-std",
|
|
@@ -463,14 +463,14 @@ dependencies = [
|
|
|
463
463
|
|
|
464
464
|
[[package]]
|
|
465
465
|
name = "docxdriver-monty-repl"
|
|
466
|
-
version = "0.4.
|
|
466
|
+
version = "0.4.4"
|
|
467
467
|
dependencies = [
|
|
468
468
|
"docxdriver-core",
|
|
469
469
|
]
|
|
470
470
|
|
|
471
471
|
[[package]]
|
|
472
472
|
name = "docxdriver-python"
|
|
473
|
-
version = "0.4.
|
|
473
|
+
version = "0.4.4"
|
|
474
474
|
dependencies = [
|
|
475
475
|
"docxdriver-core",
|
|
476
476
|
"pyo3",
|
|
@@ -480,7 +480,7 @@ dependencies = [
|
|
|
480
480
|
|
|
481
481
|
[[package]]
|
|
482
482
|
name = "docxdriver-wasm"
|
|
483
|
-
version = "0.4.
|
|
483
|
+
version = "0.4.4"
|
|
484
484
|
dependencies = [
|
|
485
485
|
"docxdriver-core",
|
|
486
486
|
"js-sys",
|
|
@@ -564,7 +564,20 @@ pub enum EditOp {
|
|
|
564
564
|
with: String,
|
|
565
565
|
},
|
|
566
566
|
EditTable { at: String, action: TableEdit },
|
|
567
|
-
|
|
567
|
+
/// A `rows` × `columns` grid of new cells. `cells` fills them row by
|
|
568
|
+
/// row with the inline content `insert_paragraph`'s `with` accepts;
|
|
569
|
+
/// missing trailing rows and cells stay empty. With `as`, cell row N
|
|
570
|
+
/// column M is the paragraph alias `$<as>_rNcM` for later operations.
|
|
571
|
+
InsertTable {
|
|
572
|
+
at: ParagraphAddress,
|
|
573
|
+
position: InsertPosition,
|
|
574
|
+
rows: u32,
|
|
575
|
+
columns: u32,
|
|
576
|
+
#[serde(default)]
|
|
577
|
+
cells: Option<Vec<Vec<String>>>,
|
|
578
|
+
#[serde(rename = "as", default)]
|
|
579
|
+
alias: Option<String>,
|
|
580
|
+
},
|
|
568
581
|
FormatTable { at: String, format: TableFormat },
|
|
569
582
|
FormatRow { at: String, format: RowFormat },
|
|
570
583
|
FormatCell { at: String, format: CellFormat },
|
|
@@ -1394,9 +1407,31 @@ fn normalize_op(op: &mut EditOp) -> Result<(), String> {
|
|
|
1394
1407
|
}
|
|
1395
1408
|
}
|
|
1396
1409
|
EditOp::EditTable { at, action } => crate::commands::table_structure::validate(at, action)?,
|
|
1397
|
-
EditOp::InsertTable { rows, columns, alias, .. } => {
|
|
1410
|
+
EditOp::InsertTable { rows, columns, cells, alias, .. } => {
|
|
1398
1411
|
if *rows == 0 || *columns == 0 || *rows > 100 || *columns > 100 { return Err("insert_table dimensions must be 1..100".into()); }
|
|
1399
1412
|
if alias.as_ref().is_some_and(|a| !valid_alias(a)) { return Err("invalid alias".into()); }
|
|
1413
|
+
let cells = cells.as_deref().unwrap_or_default();
|
|
1414
|
+
if cells.len() > *rows as usize {
|
|
1415
|
+
let extra = cells.len() - *rows as usize;
|
|
1416
|
+
return Err(format!(
|
|
1417
|
+
"cells has {} rows but the table has {rows}: remove cells[{rows}..] (row {}{})",
|
|
1418
|
+
cells.len(),
|
|
1419
|
+
*rows + 1,
|
|
1420
|
+
if extra > 1 { format!(" to row {}", cells.len()) } else { String::new() },
|
|
1421
|
+
));
|
|
1422
|
+
}
|
|
1423
|
+
for (index, row) in cells.iter().enumerate() {
|
|
1424
|
+
if row.len() > *columns as usize {
|
|
1425
|
+
let extra = row.len() - *columns as usize;
|
|
1426
|
+
return Err(format!(
|
|
1427
|
+
"cells[{index}] (row {}) has {} cells but the table has {columns} columns: remove cells[{index}][{columns}..] (column {}{})",
|
|
1428
|
+
index + 1,
|
|
1429
|
+
row.len(),
|
|
1430
|
+
*columns + 1,
|
|
1431
|
+
if extra > 1 { format!(" to column {}", row.len()) } else { String::new() },
|
|
1432
|
+
));
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1400
1435
|
}
|
|
1401
1436
|
EditOp::FormatTable { at, format } => crate::commands::tables::validate_table(at, format)?,
|
|
1402
1437
|
EditOp::FormatRow { at, format } => crate::commands::tables::validate_row(at, format)?,
|
|
@@ -2002,6 +2037,37 @@ fn fold_typed(
|
|
|
2002
2037
|
if matches!(op, EditOp::InsertTable { .. } | EditOp::EditTable { .. }) {
|
|
2003
2038
|
aliases.insert(alias.clone(), Value::String(format!("${alias}")));
|
|
2004
2039
|
}
|
|
2040
|
+
if let Some(rows) = outcome.result.as_ref().and_then(|r| r.get("cells")).and_then(Value::as_array) {
|
|
2041
|
+
let cells = rows.iter().enumerate().flat_map(|(r, row)| {
|
|
2042
|
+
row.as_array().into_iter().flatten().enumerate().map(move |(c, id)| (format!("{alias}_r{}c{}", r + 1, c + 1), id.clone()))
|
|
2043
|
+
});
|
|
2044
|
+
for (name, id) in cells {
|
|
2045
|
+
if aliases.contains_key(&name) {
|
|
2046
|
+
attach_affected(&mut state, &mut reports, &affected_ids);
|
|
2047
|
+
let report = PlanReport {
|
|
2048
|
+
completed: index,
|
|
2049
|
+
stopped_at: Some(index),
|
|
2050
|
+
ops: reports,
|
|
2051
|
+
repaired_ids: repaired_ids.clone(),
|
|
2052
|
+
allocated_ids: allocated_ids.clone(),
|
|
2053
|
+
aliases: aliases.clone().into_iter().collect(),
|
|
2054
|
+
};
|
|
2055
|
+
return (
|
|
2056
|
+
report.clone(),
|
|
2057
|
+
None,
|
|
2058
|
+
Some((
|
|
2059
|
+
diag(
|
|
2060
|
+
"duplicate_alias",
|
|
2061
|
+
format!("cell alias {name:?} of table alias {alias:?} is already defined"),
|
|
2062
|
+
Some(format!("ops[{index}].as")),
|
|
2063
|
+
),
|
|
2064
|
+
report,
|
|
2065
|
+
)),
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
aliases.insert(name, id);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2005
2071
|
if let Some(id) = outcome
|
|
2006
2072
|
.result
|
|
2007
2073
|
.as_ref()
|
|
@@ -2012,6 +2078,12 @@ fn fold_typed(
|
|
|
2012
2078
|
aliases.insert(alias.clone(), id);
|
|
2013
2079
|
}
|
|
2014
2080
|
}
|
|
2081
|
+
if let EditOp::InsertTable { .. } = op {
|
|
2082
|
+
let rows = outcome.result.as_ref().and_then(|r| r.get("cells")).and_then(Value::as_array);
|
|
2083
|
+
for id in rows.into_iter().flatten().filter_map(Value::as_array).flatten() {
|
|
2084
|
+
allocated_ids.push(id.as_str().unwrap_or_default().into());
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2015
2087
|
}
|
|
2016
2088
|
attach_affected(&mut state, &mut reports, &affected_ids);
|
|
2017
2089
|
let report = PlanReport {
|
|
@@ -328,9 +328,10 @@ fn run_typed_edit_inner(
|
|
|
328
328
|
EditOp::EditTable { at, action } => {
|
|
329
329
|
mutate(state, |pkg, xml, _source| table_structure::edit(pkg, xml, at, action, tracked))
|
|
330
330
|
}
|
|
331
|
-
EditOp::InsertTable { at, position, rows, columns, .. } => {
|
|
331
|
+
EditOp::InsertTable { at, position, rows, columns, cells, .. } => {
|
|
332
332
|
let at = para_id!(at);
|
|
333
|
-
|
|
333
|
+
let cells = cells.as_deref().unwrap_or_default();
|
|
334
|
+
mutate(state, |pkg, xml, source| table_structure::insert(pkg, xml, source, &at, *position, *rows, *columns, cells, tracked, &plan_seed, index))
|
|
334
335
|
}
|
|
335
336
|
EditOp::FormatTable { at, format } => mutate(state, |pkg, xml, _| tables::format_table(Some(pkg), xml, at, format, tracked, author, ctx)),
|
|
336
337
|
EditOp::FormatRow { at, format } => mutate(state, |_, xml, _| tables::format_row(xml, at, format, tracked, author, ctx)),
|
{docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-core/src/commands/table_structure.rs
RENAMED
|
@@ -497,7 +497,10 @@ pub fn insert(
|
|
|
497
497
|
position: InsertPosition,
|
|
498
498
|
row_count: u32,
|
|
499
499
|
column_count: u32,
|
|
500
|
+
cell_content: &[Vec<String>],
|
|
500
501
|
tracked: bool,
|
|
502
|
+
plan_seed: &[u8; 32],
|
|
503
|
+
op_index: usize,
|
|
501
504
|
) -> Result<(String, Value), Outcome> {
|
|
502
505
|
if tracked {
|
|
503
506
|
return Err(error(
|
|
@@ -535,10 +538,12 @@ pub fn insert(
|
|
|
535
538
|
x.doc.append_child(g, c);
|
|
536
539
|
}
|
|
537
540
|
x.doc.append_child(t, g);
|
|
541
|
+
let mut cell_paragraphs = Vec::new();
|
|
538
542
|
for _ in 0..row_count {
|
|
539
543
|
let r = x.doc.create_element("w:tr");
|
|
540
544
|
for _ in 0..column_count {
|
|
541
545
|
let c = empty_cell(x);
|
|
546
|
+
cell_paragraphs.push(x.doc.children(c).next().unwrap());
|
|
542
547
|
x.doc.append_child(r, c);
|
|
543
548
|
}
|
|
544
549
|
x.doc.append_child(t, r);
|
|
@@ -547,6 +552,38 @@ pub fn insert(
|
|
|
547
552
|
InsertPosition::Before => x.doc.insert_before(p, t),
|
|
548
553
|
InsertPosition::After => x.doc.insert_after(p, t),
|
|
549
554
|
};
|
|
555
|
+
// Every cell paragraph gets a durable id now, so later operations in the
|
|
556
|
+
// same plan can address the cells (as `$alias_rNcM` or by id).
|
|
557
|
+
let ids = super::typed::allocate_table_cell_para_ids(
|
|
558
|
+
x,
|
|
559
|
+
source,
|
|
560
|
+
plan_seed,
|
|
561
|
+
op_index,
|
|
562
|
+
cell_paragraphs.len(),
|
|
563
|
+
)
|
|
564
|
+
.map_err(|message| Outcome::error(message))?;
|
|
565
|
+
for (paragraph, id) in cell_paragraphs.iter().zip(&ids) {
|
|
566
|
+
x.doc.set_attribute(*paragraph, "w14:paraId", id);
|
|
567
|
+
}
|
|
568
|
+
x.ensure_w14_declared();
|
|
569
|
+
let mut filled = 0;
|
|
570
|
+
for (row, contents) in cell_content.iter().enumerate() {
|
|
571
|
+
for (column, with) in contents.iter().enumerate() {
|
|
572
|
+
if with.is_empty() {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
let paragraph = cell_paragraphs[row * column_count as usize + column];
|
|
576
|
+
super::typed::fill_paragraph_direct(package, x, paragraph, with).map_err(|mut outcome| {
|
|
577
|
+
outcome.summary = format!("cells[{row}][{column}]: {}", outcome.summary);
|
|
578
|
+
outcome
|
|
579
|
+
})?;
|
|
580
|
+
filled += 1;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
let cell_ids = ids
|
|
584
|
+
.chunks(column_count as usize)
|
|
585
|
+
.map(|row| json!(row))
|
|
586
|
+
.collect::<Vec<_>>();
|
|
550
587
|
if x.is_w(parent, "tc") && x.doc.children(parent).last() == Some(t) {
|
|
551
588
|
let end = x.doc.create_element("w:p");
|
|
552
589
|
x.doc.append_child(parent, end);
|
|
@@ -556,8 +593,16 @@ pub fn insert(
|
|
|
556
593
|
.position(|n| *n == t)
|
|
557
594
|
.unwrap()
|
|
558
595
|
+ 1;
|
|
596
|
+
let summary = if cell_content.is_empty() {
|
|
597
|
+
format!("inserted table table:{ordinal}")
|
|
598
|
+
} else {
|
|
599
|
+
format!(
|
|
600
|
+
"inserted table table:{ordinal} with {filled} filled cell{}",
|
|
601
|
+
if filled == 1 { "" } else { "s" }
|
|
602
|
+
)
|
|
603
|
+
};
|
|
559
604
|
Ok((
|
|
560
|
-
|
|
561
|
-
json!({"table":format!("table:{ordinal}"),"tracked":false}),
|
|
605
|
+
summary,
|
|
606
|
+
json!({"table":format!("table:{ordinal}"),"tracked":false,"cells":cell_ids,"filled":filled}),
|
|
562
607
|
))
|
|
563
608
|
}
|
|
@@ -1541,6 +1541,19 @@ fn insert_plain_pieces(
|
|
|
1541
1541
|
}
|
|
1542
1542
|
cursor = xml.doc.next_sibling(node);
|
|
1543
1543
|
}
|
|
1544
|
+
// A part without hyperlinks may not declare the relationships prefix.
|
|
1545
|
+
if let Some(root) = xml.doc.root_element() {
|
|
1546
|
+
let declared = xml.doc.attributes(root).iter().any(|a| {
|
|
1547
|
+
a.name == "xmlns:r" || (a.name == "r" && a.value.contains("relationships"))
|
|
1548
|
+
});
|
|
1549
|
+
if !declared {
|
|
1550
|
+
xml.doc.set_attribute(
|
|
1551
|
+
root,
|
|
1552
|
+
"xmlns:r",
|
|
1553
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1544
1557
|
let link = xml.doc.create_element("w:hyperlink");
|
|
1545
1558
|
xml.doc.set_attribute(link, "r:id", &rid);
|
|
1546
1559
|
attach_created(xml, paragraph, before, link);
|
|
@@ -2546,6 +2559,51 @@ fn allocate_insert_para_id_typed(
|
|
|
2546
2559
|
allocator.allocate(&seed).map(format_para_id)
|
|
2547
2560
|
}
|
|
2548
2561
|
|
|
2562
|
+
/// Unused `w14:paraId`s for the cell paragraphs of a table inserted by
|
|
2563
|
+
/// operation `op_index`, one per cell in row-major order. Cell `n` (from 1)
|
|
2564
|
+
/// seeds probe slot `n`; slot 0 is the one `insert_paragraph` uses.
|
|
2565
|
+
pub(crate) fn allocate_table_cell_para_ids(
|
|
2566
|
+
xml: &DocxXml,
|
|
2567
|
+
source_hash: Option<[u8; 32]>,
|
|
2568
|
+
plan_seed: &[u8; 32],
|
|
2569
|
+
op_index: usize,
|
|
2570
|
+
count: usize,
|
|
2571
|
+
) -> Result<Vec<String>, String> {
|
|
2572
|
+
let mut allocator = ParaIdAllocator::new();
|
|
2573
|
+
for node in xml.all_paragraphs() {
|
|
2574
|
+
if let Some(value) = xml.para_id(node).filter(|v| is_valid_para_id(v)) {
|
|
2575
|
+
allocator.reserve(u32::from_str_radix(value, 16).unwrap_or(0));
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
let mut prefix = Vec::with_capacity(96);
|
|
2579
|
+
prefix.extend_from_slice(&source_hash.unwrap_or([0; 32]));
|
|
2580
|
+
prefix.push(b'|');
|
|
2581
|
+
prefix.extend_from_slice(plan_seed);
|
|
2582
|
+
prefix.push(b'|');
|
|
2583
|
+
prefix.extend_from_slice(&(op_index as u64).to_le_bytes());
|
|
2584
|
+
prefix.push(b'|');
|
|
2585
|
+
(1..=count as u64)
|
|
2586
|
+
.map(|slot| {
|
|
2587
|
+
let mut seed = prefix.clone();
|
|
2588
|
+
seed.extend_from_slice(&slot.to_le_bytes());
|
|
2589
|
+
allocator.allocate(&seed).map(format_para_id)
|
|
2590
|
+
})
|
|
2591
|
+
.collect()
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/// Fill an empty paragraph with `with`, the inline content `insert_paragraph`
|
|
2595
|
+
/// accepts, as direct (untracked) runs.
|
|
2596
|
+
pub(crate) fn fill_paragraph_direct(
|
|
2597
|
+
package: &mut Package,
|
|
2598
|
+
xml: &mut DocxXml,
|
|
2599
|
+
paragraph: NodeId,
|
|
2600
|
+
with: &str,
|
|
2601
|
+
) -> Result<(), Outcome> {
|
|
2602
|
+
let (pieces, link_href) = parse_with(with)?;
|
|
2603
|
+
insert_plain_pieces(xml, package, paragraph, None, &pieces, link_href.as_deref())
|
|
2604
|
+
.map_err(Outcome::error)
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2549
2607
|
/// `insert_paragraph`: insert one paragraph before or after an existing
|
|
2550
2608
|
/// paragraph, assigning an unused `w14:paraId` in the same part. With
|
|
2551
2609
|
/// tracking enabled the engine records both the new content and the new
|
|
@@ -319,6 +319,7 @@ class InsertTable:
|
|
|
319
319
|
position: str
|
|
320
320
|
rows: int
|
|
321
321
|
as_: str | None = None
|
|
322
|
+
cells: list | None = None
|
|
322
323
|
def to_dict(self):
|
|
323
324
|
value = {"op": "insert_table"}
|
|
324
325
|
if self.at is not None:
|
|
@@ -331,6 +332,8 @@ class InsertTable:
|
|
|
331
332
|
value["rows"] = self.rows
|
|
332
333
|
if self.as_ is not None:
|
|
333
334
|
value["as"] = self.as_
|
|
335
|
+
if self.cells is not None:
|
|
336
|
+
value["cells"] = self.cells
|
|
334
337
|
return value
|
|
335
338
|
|
|
336
339
|
@dataclass
|
|
@@ -4,7 +4,10 @@ import re
|
|
|
4
4
|
import unittest
|
|
5
5
|
|
|
6
6
|
from docxdriver import Document, create
|
|
7
|
-
from docxdriver.plan import
|
|
7
|
+
from docxdriver.plan import (
|
|
8
|
+
BookmarkAdd, BookmarkDelete, BookmarkMove, BookmarkRename, CommentAdd, InsertTable, Plan, ReplaceParagraph,
|
|
9
|
+
ReplaceText,
|
|
10
|
+
)
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
def first_paragraph(markup):
|
|
@@ -75,6 +78,25 @@ class DocumentTests(unittest.TestCase):
|
|
|
75
78
|
with self.assertRaisesRegex(ValueError, 'unknown read kind'):
|
|
76
79
|
doc.read(kind='marks')
|
|
77
80
|
|
|
81
|
+
def test_insert_table_fills_cells_in_one_plan(self):
|
|
82
|
+
doc = create(paragraphs=['Team'])
|
|
83
|
+
at = first_paragraph(doc.markup())
|
|
84
|
+
review = doc.review(Plan(author='T', change_mode='direct', ops=[
|
|
85
|
+
InsertTable(at=at, position='after', rows=2, columns=2, as_='t',
|
|
86
|
+
cells=[['<b>Name</b>', '<b>Role</b>'], ['Ada']]),
|
|
87
|
+
ReplaceParagraph(at='$t_r2c2', with_='Analyst'),
|
|
88
|
+
]))
|
|
89
|
+
self.assertEqual(review.report['ops'][0]['summary'], 'inserted table table:1 with 3 filled cells')
|
|
90
|
+
self.assertEqual(len(review.report['allocated_ids']), 4)
|
|
91
|
+
self.assertEqual(review.report['aliases']['t_r1c1'], review.report['allocated_ids'][0])
|
|
92
|
+
markup = doc.commit(review.key).markup()
|
|
93
|
+
for text in ('<b>Name</b>', '<b>Role</b>', '>Ada<', '>Analyst<'):
|
|
94
|
+
self.assertIn(text, markup)
|
|
95
|
+
with self.assertRaisesRegex(ValueError, r'remove cells\[0\]\[2\.\.\]'):
|
|
96
|
+
doc.review(Plan(author='T', change_mode='direct', ops=[
|
|
97
|
+
InsertTable(at=at, position='after', rows=1, columns=2, cells=[['a', 'b', 'c']]),
|
|
98
|
+
]))
|
|
99
|
+
|
|
78
100
|
def test_commit_needs_a_reviewed_key(self):
|
|
79
101
|
doc = create(paragraphs=['Alpha'])
|
|
80
102
|
with self.assertRaises(ValueError):
|
|
@@ -319,6 +319,7 @@ class InsertTable:
|
|
|
319
319
|
position: str
|
|
320
320
|
rows: int
|
|
321
321
|
as_: str | None = None
|
|
322
|
+
cells: list | None = None
|
|
322
323
|
def to_dict(self):
|
|
323
324
|
value = {"op": "insert_table"}
|
|
324
325
|
if self.at is not None:
|
|
@@ -331,6 +332,8 @@ class InsertTable:
|
|
|
331
332
|
value["rows"] = self.rows
|
|
332
333
|
if self.as_ is not None:
|
|
333
334
|
value["as"] = self.as_
|
|
335
|
+
if self.cells is not None:
|
|
336
|
+
value["cells"] = self.cells
|
|
334
337
|
return value
|
|
335
338
|
|
|
336
339
|
@dataclass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/python/docxdriver/__init__.py
RENAMED
|
File without changes
|
{docxdriver-0.4.3 → docxdriver-0.4.4}/crates/docxdriver-python/python/docxdriver/document.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|