---
title: DocxFormFlattener
description: Wie flexiDok-Dokumente für DOCX-Templates aufbereitet werden und wie man auf die Daten im Template zugreift
---

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+++
```

:::note[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.

```js
{
  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.

```js
{
  value:       string,   // bereits formatierter Anzeigewert
  rulesResult: boolean,
  coordinate:  string,
  name:        string,
  ChapterCount: number
}
```

Template: `+++INS kapitel.abschnitt.feld.value+++`

---

### flexiSelect / flexiSwitch [#flexiselect-flexiswitch]

```js
{
  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:

```js
{
  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):

```js
["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.

```js
[
  { /* Bildobjekt aus dem IndexedDB/Storage */ },
  ...
]
```

Iteration:
```
+++FOR bild IN kapitel.abschnitt.feld+++
+++INS $bild.fileUrl+++
+++END-FOR bild+++
```

---

### flexiPdfCollection / flexiPdfStore [#flexipdfcollection-flexipdfstore]

Wie `flexiPictureCollection` - `value` 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+++
```

:::tip[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):

```js
// 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:

```js
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 [#vollstandiges-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`:

```js
{
  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+++
```
