// L1 fragment builders: the byte fragments `docx annotate` splices into
// EXISTING documents. Per the L0 locked rule every fragment is
// namespace-SELF-CONTAINED — each top-level element declares its own
// xmlns:w — so correctness never depends on the destination document's
// bindings (which may be hostile or absent).
///|
/// The two anchor fragments for comment `id`: the range start (spliced
/// at the from-paragraph's content_start) and the range end plus the
/// reference run (spliced at the to-paragraph's close_tag_start) — the
/// same canonical shape the K1 writer emits, in self-contained form.
pub fn comment_anchor_fragments(
id~ : String,
wordprocessing_namespace? : String = WORDPROCESSINGML_NAMESPACE,
) -> (String, String) {
let namespaces = { "w": wordprocessing_namespace }
let start = @xml.write_xml_fragment(
@xml.xml_element("w:commentRangeStart", attributes={ "w:id": id }),
namespaces~,
)
let end = @xml.write_xml_fragment(
@xml.xml_element("w:commentRangeEnd", attributes={ "w:id": id }),
namespaces~,
) +
@xml.write_xml_fragment(
@xml.xml_element("w:r", children=[
XmlElement(
@xml.xml_element("w:commentReference", attributes={ "w:id": id }),
),
]),
namespaces~,
)
(start, end)
}
///|
/// The `w:comment` definition fragment for `spec` (whose anchored
/// range is ignored here — the splice layer owns placement). The body
/// is re-validated (the arrays stay caller-mutable) and serialized
/// through the same body writers as the packaging path, under the same
/// fail-closed guard against relationship-bearing content.
pub fn comment_definition_fragment(
spec : CommentSpec,
id~ : String,
para_ids? : Array[String] = [],
max_output_bytes? : Int = 8 * 1024 * 1024,
wordprocessing_namespace? : String = WORDPROCESSINGML_NAMESPACE,
) -> String raise DocxError {
check_comment_body_for_fragment(spec.body, max_output_bytes)
check_attribute_value("the comment id", id)
if para_ids.length() > 0 && para_ids.length() != spec.body.length() {
raise Unsupported(
message="paraId stamping needs exactly one id per body paragraph",
)
}
let ctx = WriteContext::{
used_styles: Set([]),
uses_lists: false,
next_relationship: 3,
document_relationships: [],
media: [],
footnote_count: 0,
endnote_count: 0,
}
let children : Array[XmlNode] = []
for index, paragraph in spec.body {
let element = write_block(paragraph, ctx)
if para_ids.length() > 0 {
element.attributes["w14:paraId"] = para_ids[index]
}
children.push(XmlElement(element))
}
if ctx.document_relationships.length() > 0 || ctx.media.length() > 0 {
raise Unsupported(
message="comment bodies are plain content: hyperlinks and images cannot be serialized into an annotate fragment",
)
}
// Styles and list numbering resolve against the DESTINATION
// document's parts, which annotate never rewrites — fail closed
// rather than referencing styles the document may not define.
if ctx.used_styles.length() > 0 || ctx.uses_lists {
raise Unsupported(
message="annotate comment bodies cannot use paragraph styles or lists (they would reference style and numbering definitions the existing document may not have)",
)
}
let attributes : Map[String, String] = { "w:id": id, "w:author": spec.author }
match spec.initials {
Some(initials) => attributes["w:initials"] = initials
None => ()
}
match spec.date {
Some(date) => attributes["w:date"] = date
None => ()
}
let namespaces : Map[String, String] = { "w": wordprocessing_namespace }
if para_ids.length() > 0 {
namespaces["w14"] = W14_NAMESPACE
}
@xml.write_xml_fragment_limited(
@xml.xml_element("w:comment", attributes~, children~),
max_output_bytes~,
namespaces~,
)
}
///|
/// Relationship types of annotation SIDECARS this tooling cannot keep
/// consistent when mutating comments (Word's modern comment identity
/// and people parts). Per the L0 matrix rule these are detected by
/// RELATIONSHIP TYPE from the main part — their presence makes
/// mutating commands fail closed. commentsExtended is NOT here: the
/// add operation never touches it (matrix row), and L2 owns it.
pub fn DocxAnnotatedResult::annotation_sidecars(
self : DocxAnnotatedResult,
) -> Array[String] {
self.sidecar_types.copy()
}