mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-10 12:20:12 -06:00
ODT form controls (ODF 1.3 §13: controls in `office:forms`, each placed in the text by a `draw:control`) were dropped on open, values and all. They now open as WordCraft content controls, each with its value, entries and selection; its name becomes the tag and its label the title: - text fields and text areas: plain text controls; - check boxes: check boxes; - radio buttons: check boxes with round symbols; - list boxes: drop-down lists; - combo boxes: combo boxes; - date fields: date controls. Saved as DOCX they become Word content controls. Content controls of those kinds in the body are written back as form controls with a `draw:control` anchor; in headers and footers they stay their text. LibreOffice shows the round-tripped controls with their values, and the content validates against the ODF 1.3 schema. Not read: buttons, images, hidden and other controls. Controls' sizes and looks aren't kept. Flow model: new `Inline::Control`, kept only by flows that ask for boxes (ODT); other formats get its text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBVQLyDnrG8Tn1yDQcUabC
51 lines
3.1 KiB
Rust
51 lines
3.1 KiB
Rust
//! ODT form controls (ODF 1.3 §13) open as content controls: a drop-down list and a check box
|
|
//! stay what they are when the document is saved as DOCX (`w:sdt`) and as ODT again. The ODT is
|
|
//! original and synthetic, shaped like LibreOffice's.
|
|
|
|
use std::io::{Read, Write};
|
|
|
|
use wordcraft_doc::control::ControlKind;
|
|
use wordcraft_doc::para::InlineObject;
|
|
use wordcraft_engine::io;
|
|
|
|
fn odt() -> Vec<u8> {
|
|
let content = r#"<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"><office:body><office:text><office:forms><form:form form:name="Form"><form:listbox form:name="Colour" form:id="c1" form:dropdown="true"><form:option form:label="Red"/><form:option form:label="Green" form:current-selected="true"/></form:listbox><form:checkbox form:name="Agree" form:id="c2" form:current-state="unchecked"/></form:form></office:forms><text:p>Colour: <draw:control draw:control="c1"/> Agree: <draw:control draw:control="c2"/></text:p></office:text></office:body></office:document-content>"#;
|
|
let mut z = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
|
|
let o = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
|
for (name, data) in [("mimetype", "application/vnd.oasis.opendocument.text"), ("content.xml", content)] {
|
|
z.start_file(name, o).unwrap();
|
|
z.write_all(data.as_bytes()).unwrap();
|
|
}
|
|
z.finish().unwrap().into_inner()
|
|
}
|
|
|
|
fn kinds(doc: &wordcraft_doc::Document) -> Vec<&'static str> {
|
|
doc.body
|
|
.iter()
|
|
.filter_map(|b| b.as_para())
|
|
.flat_map(|p| p.objects.iter())
|
|
.filter_map(|o| if let InlineObject::ControlStart { control } = o { Some(control.kind.name()) } else { None })
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn odt_form_controls_become_content_controls_everywhere() {
|
|
let doc = io::open_bytes("form.odt", &odt()).unwrap();
|
|
assert_eq!(kinds(&doc), ["dropDown", "checkBox"]);
|
|
let text: String = doc.body.iter().filter_map(|b| b.as_para()).map(|p| p.text.replace(wordcraft_doc::para::OBJ, "")).collect();
|
|
assert_eq!(text, "Colour: Green Agree: \u{2610}");
|
|
// DOCX: Word content controls.
|
|
let docx = io::save_bytes("form.docx", &doc).unwrap();
|
|
let mut body = String::new();
|
|
zip::ZipArchive::new(std::io::Cursor::new(&docx)).unwrap().by_name("word/document.xml").unwrap().read_to_string(&mut body).unwrap();
|
|
assert!(body.contains("<w:dropDownList") && body.contains("w14:checkbox"), "{body}");
|
|
let again = io::open_bytes("form.docx", &docx).unwrap();
|
|
assert_eq!(kinds(&again), ["dropDown", "checkBox"]);
|
|
// ODT again: form controls.
|
|
let odt2 = io::open_bytes("form.odt", &io::save_bytes("form.odt", &again).unwrap()).unwrap();
|
|
assert_eq!(kinds(&odt2), ["dropDown", "checkBox"]);
|
|
assert!(odt2.body.iter().filter_map(|b| b.as_para()).flat_map(|p| p.objects.iter()).any(
|
|
|o| matches!(o, InlineObject::ControlStart { control } if matches!(&control.kind, ControlKind::DropDown { items, .. } if items.len() == 2))
|
|
));
|
|
}
|