Zum Inhalt

DocxFormFlattener

Der DocxFormFlattener wandelt ein strukturiertes flexiDocument in ein flaches JavaScript-Objekt um. Dieses Objekt dient als Template-Kontext für docx-templates — d.h. jede {variable} im Word-Template greift direkt auf dieses Objekt zu.

Quellcode: src/app/_helpers/docxFormFlattener.ts


Verarbeitungsreihenfolge

flexiDocument
└── chapters[]
    └── flexiChapter
        ├── multiChapter = true  →  subChapters[] (rekursiv)
        ├── multiSection = true  →  subSections[] (Sections-Array)
        └── normal               →  sections[]
            └── flexiSection
                └── components[]
                    └── FieldConfig  →  getypter Wert

Einstiegspunkt ist flattenForm(document). Das Ergebnis ist ein FlattenedForm-Objekt ({ [key: string]: any }), das direkt als Kontext an docx-templates übergeben wird.


Kapitel-Typen

Normales Kapitel (multiChapter=false, multiSection=false)

Jeder Abschnitt wird direkt unter dem Kapitelnamen abgelegt:

flattened[kapitelName][abschnittName][komponentenName] = { value, rulesResult, ... }

Zugriff im Template:

+++INS kapitelName.abschnittName.komponentenName.value+++

Zusätzlich werden am Kapitelobjekt folgende Metadaten gesetzt:

Eigenschaft Typ Beschreibung
Valid boolean true wenn chapter.state == 2
Title string Übersetzter Kapiteltitel
Name string Übersetzter Kapitelname
ChapterCount number 1-basierter Kapitelindex
ChapterTypes string[] Kapitel-Typ-Array aus der Konfiguration
SectionTypes string[] Abschnitts-Typ-Array aus der Konfiguration
+++INS kapitelName.Valid+++
+++INS kapitelName.Title+++

MultiSection-Kapitel (multiSection=true)

Abschnitte landen in einem Sections-Array. Jeder Abschnitt enthält ein Components-Array, in dem jede Komponente als eigenes Objekt gespeichert ist:

flattened[kapitelName].Sections = [
  {
    Components: [
      { komponentenName: { value, rulesResult, coordinate, name, ChapterCount } },
      ...
    ],
    Valid:       boolean,
    Title:       string,
    Name:        string,
    Coordinate:  string,   // z.B. "kapitel.abschnitt"
    SectionType: string
  },
  ...
]

Iteration im Template:

+++FOR section IN kapitelName.Sections+++
Abschnitt: +++INS $section.Title+++  —  Gültig: +++INS $section.Valid+++
+++FOR cmp IN $section.Components+++
+++INS $cmp.komponentenName.value+++
+++END-FOR cmp+++
+++END-FOR section+++

MultiChapter (multiChapter=true)

Das Kapitel wird zu einem Array von Unterkapiteln. Jedes Element enthält die Inhalte des jeweiligen Unterkapitels (ohne den Unterkapitelnamen als Wrapper):

flattened[kapitelName] = [
  { abschnittName: { komponentenName: { value, ... } }, ... },  // Unterkapitel 1
  { abschnittName: { komponentenName: { value, ... } }, ... },  // Unterkapitel 2
  ...
]
// Das Array hat zusätzlich benannte Eigenschaften:
// .Valid, .Title, .Name, .ChapterCount, .ChapterTypes, .SectionTypes

Iteration im Template:

+++FOR unterkapitel IN kapitelName+++
+++INS $unterkapitel.abschnittName.komponentenName.value+++
+++END-FOR unterkapitel+++

Array-Metadaten

Valid, Title etc. werden als benannte Eigenschaften auf dem Array gesetzt (JavaScript-spezifisch). Im Template sind sie über +++INS kapitelName.Valid+++ erreichbar — außerhalb der Schleife.


Komponenten-Typen und Zugriffsmuster

flexiInput

Zahlen (input_type='number') werden automatisch mit dem aktuellen Locale formatiert.

{
  value:       string,
  rulesResult: boolean,  // Validierungsergebnis
  coordinate:  string,   // z.B. "kapitel.abschnitt.feld"
  name:        string,
  ChapterCount: number
}

Template: +++INS kapitel.abschnitt.feld.value+++


flexiCalculatedInput

Berechnete Felder. Wenn decimalPlaces in den Settings gesetzt ist, wird der Wert entsprechend gerundet/formatiert.

{
  value:       string,   // bereits formatierter Anzeigewert
  rulesResult: boolean,
  coordinate:  string,
  name:        string,
  ChapterCount: number
}

Template: +++INS kapitel.abschnitt.feld.value+++


flexiSelect / flexiSwitch

{
  value:       string | boolean,
  rulesResult: boolean,
  coordinate:  string,
  name:        string,
  ChapterCount: number
}

Template: +++INS kapitel.abschnitt.feld.value+++


flexiMultiselect

Wenn keine Auswahl getroffen wurde, ist der Wert ein leeres Array [].
Mit Auswahl:

{
  selectedCount: number,
  value:         any[],   // Array der ausgewählten Optionen
  rulesResult:   boolean,
  coordinate:    string,
  name:          string,
  ChapterCount:  number
}

Anzahl ausgewählter Werte: +++INS kapitel.abschnitt.feld.selectedCount+++

Iteration über Werte:

+++FOR v IN kapitel.abschnitt.feld.value+++
+++INS $v+++
+++END-FOR v+++


flexiTextArea

Der Text wird zeilenweise aufgeteilt — das Ergebnis ist ein String-Array (eine Zeile pro Eintrag):

["Zeile 1", "Zeile 2", "Zeile 3"]

Iteration im Template (eine Zeile pro Absatz):

+++FOR zeile IN kapitel.abschnitt.feld+++
+++INS $zeile+++
+++END-FOR zeile+++


flexiPictureCollection

Der value-String wird als JSON geparst. Das Ergebnis ist ein Array von Bild-Objekten mit coordinate und ChapterCount als benannte Array-Eigenschaften.

[
  { /* Bildobjekt aus dem IndexedDB/Storage */ },
  ...
]

Iteration:

+++FOR bild IN kapitel.abschnitt.feld+++
+++INS $bild.fileUrl+++
+++END-FOR bild+++


flexiPdfCollection / flexiPdfStore

Wie flexiPictureCollectionvalue kann entweder ein JSON-String oder bereits ein Array/Objekt sein (beide Fälle werden behandelt). Ergebnis ist ein Array mit coordinate und ChapterCount.

Iteration:

+++FOR pdf IN kapitel.abschnitt.feld+++
+++INS $pdf.name+++  —  +++INS $pdf.fileUrl+++
+++END-FOR pdf+++


flexiDynamicTable

Eine dynamische Tabelle erzeugt drei Schlüssel im flattened-Objekt:

Schlüssel Typ Inhalt
feld object[] Rohdaten: [{col1: val1, col2: val2}, ...]
feld_rows any[][] Zeilen inkl. Header-Zeile: [[h1, h2], [v1, v2], ...]
feld_columns any[][] Spalten inkl. Header: [[h1, v1, v2], [h2, v1, v2], ...]

Rohzugriff (Objekte iterieren):

+++FOR row IN kapitel.abschnitt.feld+++
+++INS $row.spalte1+++ — +++INS $row.spalte2+++
+++END-FOR row+++

Zeilenweise (mit Header):

+++FOR row IN kapitel.abschnitt.feld_rows+++
+++FOR zelle IN $row+++
+++INS $zelle+++
+++END-FOR zelle+++
+++END-FOR row+++

Spaltenweise:

+++FOR col IN kapitel.abschnitt.feld_columns+++
+++FOR zelle IN $col+++
+++INS $zelle+++
+++END-FOR zelle+++
+++END-FOR col+++

ChapterCount bei DynamicTable

Der Zähler wird unter feld_ChapterCount abgelegt (nicht als Eigenschaft des Arrays), da das Array-Objekt selbst die Rohdaten enthält.


flexiKunden

Ein Sondertyp — alle Eigenschaften des value-Objekts werden direkt in den übergeordneten Abschnitt geflattent (kein Wrapper-Objekt):

// component.value = { firma: "ACME", ansprechpartner: "Max" }
// wird zu:
flattened[abschnittName]["firma"] = "ACME"
flattened[abschnittName]["ansprechpartner"] = "Max"

Direktzugriff im Template: +++INS kapitel.abschnitt.firma+++


Ignorierte Typen

Folgende Komponenten erzeugen keinen Eintrag im flattened-Objekt:

  • flexiLine
  • flexiHeader
  • flexiHTML
  • flexiLabel

Sonderfelder auf oberster Ebene

Revisionsverlauf

Wenn document.revisioningEnabled == true und document.revision gesetzt ist, wird revisionHistory als sortiertes Array auf der obersten Ebene abgelegt:

flattenedData["revisionHistory"] = [
  { date, author, comment, ... },  // älteste zuerst
  ...
]

Template:

+++FOR rev IN revisionHistory+++
+++INS $rev.date+++ — +++INS $rev.author+++: +++INS $rev.comment+++
+++END-FOR rev+++


Abschnitts-Metadaten

Jeder Abschnitt erhält folgende Metadaten, unabhängig vom Typ:

Eigenschaft Typ Beschreibung
Valid boolean true wenn section.state == 2
Title string Übersetzter Abschnittstitel
Name string Übersetzter Abschnittsname
Coordinate string Pfad im Format "kapitelName.abschnittName"

Zugriff bei normalem Abschnitt: +++INS kapitel.abschnitt.Valid+++
Zugriff im MultiSection-Loop: +++INS $section.Valid+++ (innerhalb von +++FOR section IN kapitel.Sections+++)


Vollständiges Beispiel

Gegeben folgende Dokumentstruktur:

Kapitel: "pruefbericht"  (normal)
  Abschnitt: "allgemein"
    flexiInput:     "auftragsnummer"
    flexiSelect:    "status"
    flexiTextArea:  "bemerkungen"
  Abschnitt: "messungen"  (multiSectionType)
    flexiInput:     "messwert"
    flexiInput:     "einheit"

Ergebnis in flattenedData:

{
  pruefbericht: {
    allgemein: {
      auftragsnummer: { value: "A-2024-001", rulesResult: true, ... },
      status:         { value: "abgeschlossen", rulesResult: true, ... },
      bemerkungen:    ["Zeile 1", "Zeile 2"],
      Valid: true,
      Title: "Allgemeine Angaben",
      Name:  "allgemein",
      Coordinate: "pruefbericht.allgemein"
    },
    Sections: [
      {
        Components: [
          { messwert: { value: "12.5", ... } },
          { einheit:  { value: "mm", ... } }
        ],
        Valid: true,
        Title: "Messung 1",
        Coordinate: "pruefbericht.messung_1"
      }
    ],
    Valid: true,
    Title: "Prüfbericht"
  }
}

Word-Template-Ausschnitt:

Auftragsnummer: +++INS pruefbericht.allgemein.auftragsnummer.value+++
Status: +++INS pruefbericht.allgemein.status.value+++

Bemerkungen:
+++FOR zeile IN pruefbericht.allgemein.bemerkungen+++
+++INS $zeile+++
+++END-FOR zeile+++

Messungen:
+++FOR section IN pruefbericht.Sections+++
+++INS $section.Title+++
+++FOR cmp IN $section.Components+++
+++INS $cmp.messwert.value+++ +++INS $cmp.einheit.value+++
+++END-FOR cmp+++
+++END-FOR section+++