DoculaDevelopersDezvoltatori
API 0.8.0 · BuildVersiune produs 0.33.0
Documentation navigationNavigare documentație

Guide 2 of 7Ghidul 2 din 7 · API 0.8.0

DOCX templatesȘabloane DOCX

Authoring rules for the DOCX templates that the document worker fills from transformed JSON, derived from the validator limits in internal/templates/docx.go.Regulile de autorare pentru șabloanele DOCX pe care workerul de documente le completează din JSON-ul transformat, derivate din limitele validatorului din internal/templates/docx.go.

Derived from:Derivat din: internal/templates/docx.go

Bindings are JSON PointersLegăturile sunt pointeri JSON

A binding is a double-brace token whose body is a non-root RFC 6901 JSON Pointer into the transformed JSON, for example {{/customer/name}} or {{/lines/0/amount}}. The pointer is resolved against the output of the release's transform version, never against the raw submission, so the template only sees data the transform has already shaped.O legătură este un token între acolade duble al cărui corp este un pointer RFC 6901 diferit de rădăcină în JSON-ul transformat, de exemplu {{/customer/name}} sau {{/lines/0/amount}}. Pointerul se rezolvă față de ieșirea versiunii de transformare a release-ului, niciodată față de solicitarea brută, astfel încât șablonul vede doar date deja modelate de transformare.

Tokens are scanned per paragraph in the main document, headers, footers, footnotes and endnotes. Word may split a token across several runs while you edit; the scanner reassembles the paragraph text before matching, so character styling inside a token is tolerated. A token that is opened and not closed within its paragraph fails validation with invalid_binding, and a { inside a token body is rejected for the same reason.Token-urile sunt scanate per paragraf în documentul principal, antete, subsoluri, note de subsol și note de final. Word poate împărți un token în mai multe run-uri în timpul editării; scannerul reasamblează textul paragrafului înainte de potrivire, așa că stilizarea caracterelor din interiorul unui token este tolerată. Un token deschis și neînchis în paragraful său pică validarea cu invalid_binding, iar un { în corpul token-ului este respins din același motiv.

The root pointer (an empty token body) is not a valid binding. Use the transform to place scalar values at named paths, then bind those paths.Pointerul rădăcină (un corp gol) nu este o legătură validă. Folosește transformarea pentru a plasa valori scalare la căi denumite, apoi leagă acele căi.

Bounded validationValidare limitată

The limits are enforced before any content is parsed, so an oversized or hostile archive is rejected without being extracted. Validation errors carry a stable code and a message that never includes archive names, XML text or parser internals, which makes them safe to show to an authenticated user.Limitele sunt aplicate înainte ca vreun conținut să fie parsat, așa că o arhivă supradimensionată sau ostilă este respinsă fără a fi extrasă. Erorile de validare poartă un code stabil și un mesaj care nu include niciodată nume din arhivă, text XML sau detalii interne ale parserului, ceea ce le face sigure de afișat unui utilizator autentificat.

  • Upload size: at most 10 MiB compressed, code compressed_limit.Dimensiunea încărcării: cel mult 10 MiB comprimat, cod compressed_limit.
  • Expanded archive: at most 64 MiB across all entries, code expanded_limit.Arhiva expandată: cel mult 64 MiB pentru toate intrările, cod expanded_limit.
  • Archive entries: at most 2048, with at most 1 MiB of entry-name metadata, code entry_limit.Intrări în arhivă: cel mult 2048, cu cel mult 1 MiB de metadate pentru numele intrărilor, cod entry_limit.
  • Bindings: at most 256 distinct pointers per template and at most 256 bytes per binding, code binding_limit.Legături: cel mult 256 de pointeri distincți per șablon și cel mult 256 de octeți per legătură, cod binding_limit.
  • Relationship metadata: at most 8192 relationships and 2 MiB of relationship XML.Metadate de relații: cel mult 8192 de relații și 2 MiB de XML de relații.
  • Package metadata parts such as the content types and relationship files are read up to 1 MiB each.Părțile de metadate ale pachetului, precum tipurile de conținut și fișierele de relații, sunt citite până la 1 MiB fiecare.
docx-template/authoring-limits.json · Limits enforced by internal/templates/docx.go before any content is parsed.Limitele impuse de internal/templates/docx.go înainte ca vreun conținut să fie parsat.
{
  "media_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "compressed_bytes": 10485760,
  "expanded_bytes": 67108864,
  "archive_entries": 2048,
  "bindings": 256,
  "binding_bytes": 256,
  "binding_syntax": "{{/rfc6901/json-pointer}}",
  "binding_rules": [
    "A binding is {{ followed by a non-root RFC 6901 JSON Pointer and }}; the pointer must start with / and may only use ~0 and ~1 escapes.",
    "Bindings may be split across runs inside one paragraph (Word does this while you type) but never across paragraphs or table cells.",
    "Bindings are collected from the main document and from every header, footer, footnotes and endnotes part it references; they are returned sorted and unique.",
    "At release time every binding must resolve to a string, number or boolean in the transformed output; null, missing, objects and arrays are rejected."
  ],
  "text_parts": ["word/document.xml", "word/header*.xml", "word/footer*.xml", "word/footnotes.xml", "word/endnotes.xml"],
  "allowed_parts": ["[Content_Types].xml", "*.rels", "word/styles*.xml", "word/numbering.xml", "word/settings.xml", "word/webSettings.xml", "word/fontTable.xml", "word/comments*.xml", "word/people.xml", "word/theme/theme*.xml", "docProps/*.xml", "word/media/* (png, jpg, jpeg, gif, bmp, tif, tiff, emf, wmf, webp)"],
  "rejected": ["macros and vbaProject parts", "ActiveX controls", "OLE embeddings and .bin parts", "AltChunk imports", "encrypted or non-deflate archive entries", "duplicate or path-traversing entry names", "XML with DOCTYPE or external entities"],
  "validation_codes": ["compressed_limit", "expanded_limit", "entry_limit", "unsafe_archive", "invalid_package", "unsafe_content", "invalid_binding", "binding_limit"]
}

What a valid package looks likeCum arată un pachet valid

The validator requires a well-formed OOXML package: valid content types without duplicate entries, package relationships, exactly one main document with the WordprocessingML main content type at a supported location, and relationship parts that resolve to existing targets. Both the transitional and the strict WordprocessingML namespaces are accepted.Validatorul cere un pachet OOXML bine format: tipuri de conținut valide fără intrări duplicate, relații de pachet, exact un document principal cu tipul de conținut WordprocessingML principal într-o locație acceptată și părți de relații care se rezolvă la ținte existente. Sunt acceptate atât spațiul de nume WordprocessingML tranzițional, cât și cel strict.

Every referenced part must have a matching content type, well-formed XML with a single root element and no text outside the root. Unsafe archive entries such as traversal paths, absolute names or duplicate names are refused with unsafe_archive; disallowed content is refused with unsafe_content.Fiecare parte referită trebuie să aibă un tip de conținut potrivit, XML bine format cu un singur element rădăcină și fără text în afara rădăcinii. Intrările nesigure din arhivă, precum căile de traversare, numele absolute sau numele duplicate, sunt refuzate cu unsafe_archive; conținutul nepermis este refuzat cu unsafe_content.

The stable codes are compressed_limit, expanded_limit, entry_limit, unsafe_archive, invalid_package, unsafe_content, invalid_binding and binding_limit.Codurile stabile sunt compressed_limit, expanded_limit, entry_limit, unsafe_archive, invalid_package, unsafe_content, invalid_binding și binding_limit.

docx-template/template.manifest.json · What the validator extracts from the fixture template: bytes, SHA-256 and the sorted bindings.Ce extrage validatorul din șablonul fixture: octeți, SHA-256 și bindingurile sortate.
{
  "bindings": [
    "/display_name",
    "/first_name",
    "/preferred_contact",
    "/source"
  ],
  "byte_size": 3539,
  "content_hash": "302f1d78302d094df37225b1b9e0e463a5516937da347874d24373187f00ef1e",
  "media_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
}

Authoring checklistLista de verificare pentru autorare

  • Author in Word or LibreOffice and save as .docx; the upload media type is application/vnd.openxmlformats-officedocument.wordprocessingml.document.Redactează în Word sau LibreOffice și salvează ca .docx; tipul media la încărcare este application/vnd.openxmlformats-officedocument.wordprocessingml.document.
  • Keep every binding inside one paragraph; do not break a token across a table cell boundary or a manual line break.Păstrează fiecare legătură într-un singur paragraf; nu rupe un token peste marginea unei celule de tabel sau peste o întrerupere manuală de linie.
  • Bind only scalar values: strings, numbers and booleans. Objects and arrays are not rendered; flatten them in the transform.Leagă doar valori scalare: șiruri, numere și valori booleene. Obiectele și tablourile nu sunt randate; aplatizează-le în transformare.
  • Do not rely on macros, embedded objects or external links. A template is content, not code.Nu te baza pe macro-uri, obiecte încorporate sau linkuri externe. Un șablon este conținut, nu cod.
  • Name pointers after the transform output, not after the source system, so a new source contract does not force a template change.Denumește pointerii după ieșirea transformării, nu după sistemul sursă, astfel încât un contract sursă nou să nu impună o modificare a șablonului.
  • Keep the binding count and the archive small; images and styles count toward the expanded-size limit.Păstrează numărul de legături și arhiva mici; imaginile și stilurile contează în limita de dimensiune expandată.

Validate, test, version, publishValidează, testează, versionează, publică

Create the family with JSON, upload the DOCX bytes as the next draft under If-Match (request limit 11,534,336 bytes, the 10 MiB file plus multipart overhead), validate to extract the binding set, test the draft against a transformed case through the document worker, freeze a version and publish it with effective_from.Creezi familia cu JSON, încarci octeții DOCX ca următoarea ciornă sub If-Match (limita cererii este 11.534.336 de octeți, fișierul de 10 MiB plus supraîncărcarea multipart), validezi pentru a extrage setul de legături, testezi ciorna față de un caz transformat prin workerul de documente, îngheți o versiune și o publici cu effective_from.

previewTemplateVersion resolves the bindings of a published version against a chosen transform version and case. It returns every pointer with its resolved value plus template_hash, transform_hash, case_hash, bindings_hash and preview_hash, so a reviewer can compare two previews without opening the document.previewTemplateVersion rezolvă legăturile unei versiuni publicate față de o versiune de transformare și un caz alese. Întoarce fiecare pointer cu valoarea rezolvată plus template_hash, transform_hash, case_hash, bindings_hash și preview_hash, astfel încât un recenzor poate compara două previzualizări fără să deschidă documentul.

docx-template/curl.sh · Upload the template, compare the stored manifest, preview the bindings.Încarcă șablonul, compară manifestul stocat, previzualizează bindingurile.
#!/bin/sh
# Docula example: DOCX template authoring.
# login -> upload template.docx (createTemplate, multipart) -> read the extracted bindings -> preview against a transform version and case.
# Requires curl and jq. Environment: DOCULA_API, DOCULA_ORIGIN (defaults to DOCULA_API), DOCULA_USERNAME, DOCULA_PASSWORD,
# DOCULA_TRANSFORM_VERSION_ID (published; see the declarative-transform example) and DOCULA_CASE_ID (valid or boundary case).
set -eu
API=${DOCULA_API:?set DOCULA_API to the API origin, for example http://127.0.0.1:18081}
ORIGIN=${DOCULA_ORIGIN:-$API}
HERE=$(cd "$(dirname "$0")" && pwd)
JAR=$(mktemp)
trap 'rm -f "$JAR" "$JAR.body"' EXIT

CSRF=$(jq -n --arg u "${DOCULA_USERNAME:?}" --arg p "${DOCULA_PASSWORD:?}" '{username: $u, password: $p}' |
  curl --fail --silent --show-error -c "$JAR" -H "Origin: $ORIGIN" -H 'Content-Type: application/json' \
    --data-binary @- "$API/api/v1/auth/login" | jq -r '.csrf_token')

# 1. Upload (createTemplate, multipart/form-data, at most 10 MiB). The API validates the package, rejects active content,
#    extracts the {{/pointer}} bindings and stores version 1 with its SHA-256.
TEMPLATE=$(curl --fail --silent --show-error -b "$JAR" -X POST -H "Origin: $ORIGIN" -H "X-CSRF-Token: $CSRF" \
  -F 'name=Person welcome letter' -F 'description=Docs example: DOCX template with four bindings.' \
  -F "file=@$HERE/template.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \
  "$API/api/v1/templates")
TEMPLATE_ID=$(printf '%s' "$TEMPLATE" | jq -r '.template.id')
VERSION_NUMBER=$(printf '%s' "$TEMPLATE" | jq -r '.template.versions[0].number')

# 2. The stored manifest must match template.manifest.json (same bytes, same bindings).
printf '%s' "$TEMPLATE" | jq '.template.versions[0] | {number, byte_size, content_hash, bindings}'
[ "$(printf '%s' "$TEMPLATE" | jq -r '.template.versions[0].content_hash')" = "$(jq -r '.content_hash' "$HERE/template.manifest.json")" ] ||
  { echo "uploaded content_hash differs from template.manifest.json" >&2; exit 1; }

# 3. Resolve every binding against a published transform version and a case (previewTemplateVersion).
jq --arg t "${DOCULA_TRANSFORM_VERSION_ID:?}" --arg k "${DOCULA_CASE_ID:?}" '.transform_version_id = $t | .case_id = $k' \
  "$HERE/preview-template.request.json" > "$JAR.body"
curl --fail --silent --show-error -b "$JAR" -X POST -H "Origin: $ORIGIN" -H "X-CSRF-Token: $CSRF" \
  -H 'Content-Type: application/json' --data-binary @"$JAR.body" \
  "$API/api/v1/templates/$TEMPLATE_ID/versions/$VERSION_NUMBER/preview" | jq '{bindings, bindings_hash, preview_hash}'

curl sequence sketchSchiță de secvență curl

  1. Step 1 · copy onlyPasul 1 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/templates" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
      --data-binary @template-family.json
  2. Step 2 · copy onlyPasul 2 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/templates/$TEMPLATE_ID/draft-content" --cookie "$COOKIE_JAR" \
      --header "Origin: $DOCULA_URL" --header "X-CSRF-Token: $CSRF_TOKEN" \
      --header "Idempotency-Key: $IDEMPOTENCY_KEY" --header "If-Match: $ETAG" \
      --form "[email protected]"
  3. Step 3 · copy onlyPasul 3 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/templates/$TEMPLATE_ID/validate" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" --header "X-CSRF-Token: $CSRF_TOKEN" \
      --data-binary '{}'
  4. Step 4 · copy onlyPasul 4 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/templates/$TEMPLATE_ID/versions" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "If-Match: $ETAG" \
      --data-binary '{}'
  5. Step 5 · copy onlyPasul 5 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/templates/$TEMPLATE_ID/versions/$VERSION_NUMBER/preview" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" --header "X-CSRF-Token: $CSRF_TOKEN" \
      --data-binary @template-preview.json
  6. Step 6 · copy onlyPasul 6 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/templates/$TEMPLATE_ID/versions/$VERSION_NUMBER/publish" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
      --data-binary @effective-from.json