Zum Inhalt

Excel-Datei auslesen

Der Service Task „Excel-Datei auslesen (xls/xlsx)“ liest ein Blatt einer Excel-Datei und gibt die Zeilen als Liste von Datensätzen (records) zurück. Unterstützt werden .xls (Excel 97–2003), .xlsx und .xlsm. Das Format wird am Inhalt der Datei erkannt, nicht an der Dateiendung.

Der Task eignet sich für zwei Fälle:

  • Saubere Listen mit einer Kopfzeile, z. B. Artikel- oder Preislisten. Hier genügt header_row.
  • Excel im Drucklayout, z. B. Rechnungen oder Leistungsverzeichnisse mit Seitenköpfen, Zwischensummen, Titeln und Texten über mehrere Zeilen. Hier beschreiben Regeln, was eine Zeile ist.

Werte kommen so, wie Excel sie anzeigt: Formeln liefern ihr in der Datei gespeichertes Ergebnis, Zahlen werden auf Wunsch nach ihrem Zahlenformat gerundet (aus 113,9396 mit Format 0,00 wird 113,94).

Input-Parameter

{
  "document": {
    "referenceId": "string",
    "filename": "rechnung.xls"
  },
  "options": {
    "sheet": 0,
    "header_row": 1
  }
}
  • document: Referenz auf die hochgeladene Excel-Datei (fileReference). Pflicht.
  • options: Optional. Beschreibt, welches Blatt und welche Zeilen gelesen werden und wie. Darf auch als JSON-Text übergeben werden. Ohne options wird das erste Blatt gelesen; jede nicht leere Zeile wird ein Datensatz mit den Feldern A, B, C, …

Einfache Liste

{
  "document": { "referenceId": "..." },
  "options": { "sheet": "Artikel", "header_row": 1 }
}

Ergebnis: je Zeile ab Zeile 2 ein Datensatz, Feldnamen aus der Kopfzeile, z. B. { "Artikel": "A-1", "Bezeichnung": "Schraube", "Preis": 0.15, "Datum": "2026-02-15" }.

Excel im Drucklayout

Beispiel: Rechnungspositionen ab der Kopfzeile „Bezeichnung“ bis „Nettosumme“, Seitenköpfe und Summen überspringen, Titel („3.0 Betonarbeiten“) und Zwischentexte („Regiebericht vom …“) an die folgenden Positionen hängen.

{
  "sheet": 0,
  "start_after": { "column": "B", "matches": "^(Artikel)?bezeichnung$", "flags": "i" },
  "stop_at": { "any_column": "^Nettosumme$" },
  "scan_columns": "A:F",
  "columns": {
    "oz":       { "col": "A", "as": "text" },
    "text":     { "col": "B", "as": "text" },
    "quantity": { "col": "C", "as": "number", "round": "display" },
    "unit":     { "col": "D", "as": "text" },
    "price":    { "col": "E", "as": "number" }
  },
  "rules": [
    { "name": "Seitenkopf", "type": "skip", "if": [{ "any_column": "Seite\\s*\\d+" }] },
    { "name": "Summe",      "type": "skip", "if": [{ "any_column": "^(Summe|Übertrag)\\b", "flags": "i" }] },
    { "name": "Position",   "type": "position",
      "if": [{ "column": "quantity", "is": "numeric" }, { "column": "unit", "is": "filled" }] },
    { "name": "Titel",      "type": "title", "if": [{ "column": "oz", "matches": "^\\d+\\.0$" }] },
    { "name": "Text",       "type": "text",  "if": [{ "column": "text", "is": "filled" }] }
  ],
  "carry": [
    { "name": "title",   "from_type": "title", "value": "{oz} {text}" },
    { "name": "context", "from_type": "text", "accumulate": true, "reset_on": ["empty_row", "type:title"] }
  ]
}

Ablauf

  1. Lesen: Für jede Zelle werden Typ, Wert, Zahlenformat und angezeigter Text ermittelt.
  2. Bereich: Wo die Tabelle beginnt und endet (start_row, start_after, end_row, stop_at). Wiederkehrende Blöcke wie Seitenköpfe lassen sich mit skip_blocks überspringen.
  3. Spalten: columns vergibt Namen und legt Typ, Rundung und Ersetzungen fest.
  4. Regeln: Für jede Zeile werden die rules der Reihe nach geprüft; die erste passende bestimmt den Typ der Zeile. Typen sind frei benennbar. Zeilen vom Typ skip werden verworfen; passt keine Regel, gilt der Typ unclear.
  5. Übertragen: carry hängt Werte an die folgenden Zeilen an (z. B. den aktuellen Titel).
  6. Zusammenführen: merge hängt Textzeilen an die nächste oder vorige Zeile eines Typs an (z. B. Langtexte über mehrere Zeilen).
  7. Ergebnis: records mit allen Datensätzen (ohne skip).

Leere Zeilen werden nie zu Datensätzen. Sie können aber Übertragungen zurücksetzen und Zusammenführungen beenden (empty_row). Leerzeilen direkt nach übersprungenen Zeilen zählen dabei nicht, damit Seitenumbrüche mitten in einer Position nichts unterbrechen.

Optionen

Option Bedeutung
sheet Blatt als Index (0 = erstes), Name ("Rechnung") oder Muster ({"matches": "^Angebot"}). Standard 0.
header_row Zeilennummer der Kopfzeile. Die Spaltennamen werden zu Feldnamen, gelesen wird ab der Zeile darunter.
start_row / end_row Feste erste / letzte Zeile (1-basiert wie in Excel).
start_after Bedingung: Gelesen wird ab der Zeile nach dem ersten Treffer. Kein Treffer: keine Datensätze, Warnung.
stop_at Bedingung: Gelesen wird bis vor den ersten Treffer.
scan_columns Spaltenbereich für any_column und die Erkennung leerer Zeilen, z. B. "A:F". Standard: alle.
columns Feldname → Spalte. Kurzform "B" oder Objekt { "col": "B", "as": …, "round": …, "replace": … }.
rules Liste von Regeln { "name", "type", "if": [Bedingungen], "any": [Bedingungen], "extract": [...] }.
default_type Typ, wenn keine Regel passt. Standard "unclear"; diese Zeilen stehen zusätzlich in unclear.
skip_types Typen, die verworfen werden. Standard ["skip"].
skip_blocks Blöcke überspringen: { "start": Bedingung, "rows": 4 } oder { "start": Bedingung, "end": Bedingung }.
carry Werte an folgende Zeilen hängen, siehe unten.
lookups Nachschlagetabellen aus einem Zeilenbereich, z. B. Titel aus einem Deckblatt.
merge Textzeilen zusammenführen, siehe unten.
output_types Nur diese Typen in records, z. B. ["position"].
locale "de" (Standard) oder "en": Dezimal- und Tausendertrennzeichen für angezeigte Texte.
include_rows true: zusätzlich rows mit jeder gelesenen Zeile und ihrer Einstufung (zur Kontrolle).
detect_missing_formula_values Standard true: Formeln ohne gespeichertes Ergebnis erkennen (nur .xlsx).
strict true: Task bricht ab, wenn unklare Zeilen oder Formeln ohne gespeichertes Ergebnis vorkommen.

Spalten

Eigenschaft Bedeutung
col Spaltenbuchstabe, z. B. "C".
as text: angezeigter Text (z. B. Positionsnummer „3.091“, auch wenn Excel sie als Zahl gespeichert hat). number: Zahl, auch aus Text wie „1.234,56“. date: Datum als YYYY-MM-DD, auch aus Text wie „15.02.2026“. auto (Standard): Wert, wie er in der Zelle steht.
round "display": runden wie angezeigt (Standard bei number). Zahl: feste Nachkommastellen. Der ungerundete Wert steht dann in _raw.
replace Liste [[Regex, Ersatz], …] für Texte, z. B. [["^-$", ""]], damit ein Aufzählungsstrich nicht als Positionsnummer gilt.
trim Standard true: Leerraum am Rand entfernen und mehrfachen Leerraum zusammenfassen.

Bedingungen

Bedingungen stehen in if (alle müssen passen), any (eine muss passen), start_after, stop_at, skip_blocks, only_if und only_if_target. Sie lassen sich mit all, any und not verschachteln.

Form prüft
{ "column": "quantity", "is": "numeric" } Typ: empty, filled, number, numeric (Zahl oder Zahl als Text), text, date, error. Auch als Liste.
{ "column": "oz", "matches": "^\\d+\\.0$" } Regulärer Ausdruck auf den angezeigten Text. flags: "i" ignoriert Groß-/Kleinschreibung. Ebenso not_matches, equals.
{ "column": "C", "gt": 0 } Zahlvergleich gt / lt.
{ "any_column": "Seite\\s*\\d+" } Irgendeine Spalte passt (optional eingeschränkt mit columns: "A:F").
{ "row": "114-200" } Zeilennummer oder Bereich.
{ "not": { … } } Verneinung.

column nimmt einen Feldnamen aus columns oder einen Spaltenbuchstaben.

extract zerlegt einen Wert per Regex mit benannten Gruppen in Felder, z. B. wird aus „A4.01Alternativposition zu 4.01“ mit { "field": "oz", "pattern": "^(?P<oz>A[\\d.]*\\d)(?P<text>.*)$" } die Positionsnummer A4.01 und der Text „Alternativposition zu 4.01“.

Übertragen (carry)

{ "name": "context", "from_type": "text", "value": "{text}", "accumulate": true, "reset_on": ["empty_row", "type:title"] }
  • from_type: Zeilen dieses Typs setzen den Wert.
  • value: Vorlage aus Feldern der Zeile, z. B. "{oz} {text}".
  • accumulate: true sammelt alle Werte in einer Liste, sonst gilt der letzte.
  • reset_on: empty_row und/oder type:<typ> setzen den Wert zurück.
  • fallback_lookup / fallback_key: Fehlt der Wert, wird er in einer Nachschlagetabelle aus lookups gesucht.

Jeder Datensatz bekommt den aktuellen Wert unter name.

Zusammenführen (merge)

{ "from_type": "text", "into_type": "position", "direction": "next", "take_first": ["oz"], "break_on": ["empty_row", "type:title"], "dehyphenate": true }
  • direction: "next": Textzeilen werden vorgemerkt und der nächsten Zeile vom Typ into_type vorangestellt (Langtext über der Mengenzeile).
  • direction: "previous": Textzeilen direkt nach einer Zeile vom Typ into_type werden angehängt.
  • fields: welche Felder verbunden werden. Standard ["text"]. separator: Standard Leerzeichen.
  • take_first: Felder, die aus der ersten vorgemerkten Zeile übernommen werden, wenn die Zielzeile sie nicht hat.
  • only_if (Bedingung an die Textzeile), only_if_target (Bedingung an die Zielzeile).
  • break_on: empty_row und/oder type:<typ> beenden das Vormerken.
  • dehyphenate: true verbindet getrennte Wörter („Hoch-“ + „lochziegeln“ → „Hochlochziegeln“), lässt „Beton- und“ aber stehen.

Vorgemerkte Zeilen, die nicht zusammengeführt werden, bleiben eigene Datensätze.

Output

{
  "status": 200,
  "format": "xls",
  "sheet": "Rechnung",
  "sheets": ["Rechnung", "Aufmaß"],
  "start_row": 117,
  "end_row": 325,
  "stopped_at": null,
  "records": [
    {
      "_row": 125, "_rows": [125], "_type": "position", "_rule": "Position",
      "oz": "3.01", "text": "Frostriegel", "quantity": 3.97, "unit": "m³", "price": 85.5,
      "title": "3.0 Beton- und Stahlbetonarbeiten", "context": [],
      "_raw": { "quantity": 3.9699999999999998 }
    }
  ],
  "total": 1,
  "counts": { "position": 1, "skip": 10 },
  "unclear": [],
  "warnings": [],
  "metadata": { "filename": "rechnung.xls", "inputBytes": 173056, "processingTimeMs": 120, "engine": "xlrd" }
}
  • records: die Datensätze. _row ist die Zeile in Excel, _rows alle Zeilen, aus denen der Datensatz besteht, _type und _rule zeigen, welche Regel gegriffen hat. _raw enthält ungerundete Werte, _notes Hinweise zur Zeile.
  • counts: Anzahl Zeilen je Typ; zusammengeführte Zeilen erscheinen als „… (angehängt)“.
  • unclear: Zeilen, auf die keine Regel passt, mit ihren Zellinhalten.
  • warnings: z. B. „Startmarke nicht gefunden“ oder Formeln ohne gespeichertes Ergebnis.

In Rechnungspositionen übernehmen (JSONata)

{
  "app_invoice_items_custom": [records[_type = "position"].{
    "app_article_no": oz,
    "app_description": text,
    "app_amount": quantity,
    "app_unit": unit,
    "app_item_price_net": price,
    "app_vat_percentage": 19
  }]
}

Hinweise

  • Formelergebnisse: Der Task rechnet keine Formeln nach, sondern liest das Ergebnis, das Excel beim Speichern in der Datei ablegt. In .xls-Dateien ist es immer vorhanden. Eine .xlsx, die nicht mit Excel gespeichert wurde (z. B. aus einem anderen Programm exportiert), kann Formeln ohne Ergebnis enthalten: Der Wert ist dann null, dazu gibt es eine Warnung. Abhilfe: Datei einmal in Excel öffnen und speichern.
  • Rundung: Mit round: "display" stimmt die Menge mit dem überein, was im Ausdruck steht. Rechnet die Excel-Datei intern mit dem ungerundeten Wert, kann eine daraus erstellte Rechnung um Cent-Beträge von der Excel-Summe abweichen.
  • Nicht unterstützt: .xlsb, .ods und passwortgeschützte Dateien (Fehlermeldung „passwortgeschützt“). CSV-Dateien liest CSV-Datei strukturiert auslesen.
  • Bei einem Fehler in den Optionen (unbekanntes Blatt, unbekannte Spalte, ungültige Bedingung) bricht der Task mit einer Meldung ab, ohne erneuten Versuch.
  • Zum Einrichten neuer Vorlagen include_rows: true setzen: rows zeigt jede Zeile mit ihrer Einstufung.