Added a section explaining the limitations of the `ValidatePdfAAsync` method, highlighting that it performs heuristic PDF/A detection rather than full ISO-19005 compliance checks. Listed specific requirements not validated and clarified the meaning of `IsValid = true`. Updated the API endpoint table to reflect the heuristic nature of the `ValidatePdfAAsync()` method.
1179 lines
42 KiB
Markdown
1179 lines
42 KiB
Markdown
# DocumentService.Client
|
||
|
||
.NET-Clientbibliothek für die DocumentService-API – unterstützt .NET Framework 4.6.2, 4.8 und .NET 8.0.
|
||
|
||
## Funktionsumfang
|
||
|
||
- **Multi-Target-Unterstützung**: .NET Framework 4.6.2, 4.8 und .NET 8.0
|
||
- **Statischer Einstiegspunkt**: `Client`-Klasse – kein DI-Container erforderlich
|
||
- **Separate Clients pro Fachbereich**: `Client.Validation`, `Client.Attachment`, `Client.Operations`, `Client.SwissQrCode`, `Client.Zugferd`, `Client.Conversion`
|
||
- **Dualer Eingabe-Support**: Multipart (Stream) und Base64 (byte[]) für alle Endpunkte
|
||
- **Stark typisierte Modelle**: Gemeinsame Request/Response-DTOs mit XML-Dokumentation
|
||
- **Workflows**: `Client.Workflows` – kombinierte Operationen mit vorgelagerter Validierung (z. B. Swiss QR Code mit automatischer PDF-Prüfung)
|
||
|
||
## Installation
|
||
|
||
Das Paket wird über einen internen NuGet-Feed bereitgestellt und ist **nicht** auf nuget.org verfügbar.
|
||
|
||
**NuGet-Paket:** `http://172.24.12.35:5000/packages/documentservice.client/1.1.0`
|
||
**API-Basis-URL:** `http://172.24.12.39:9393/`
|
||
**Swagger-Dokumentation:** [http://172.24.12.39:9393/swagger/index.html](http://172.24.12.39:9393/swagger/index.html)
|
||
|
||
### .NET-Projekt (Visual Studio)
|
||
|
||
Paket über den NuGet Package Manager installieren. Dabei sicherstellen, dass der interne NuGet-Server (`http://172.24.12.35:5000/nuget`) als Paketquelle eingetragen und ausgewählt ist.
|
||
|
||
### PowerShell (ohne .NET-Projekt)
|
||
|
||
Für PowerShell-Skripte ohne zugehöriges .NET-Projekt muss die Assembly manuell heruntergeladen und eingebunden werden.
|
||
|
||
**Schritt 1 – Paket herunterladen und entpacken:**
|
||
|
||
```powershell
|
||
# NuGet-Paket herunterladen (.nupkg ist ein ZIP-Archiv)
|
||
$nupkgUrl = "http://172.24.12.35:5000/packages/documentservice.client/1.1.0"
|
||
$nupkgPath = "$env:TEMP\DocumentService.Client.1.1.0.nupkg"
|
||
$extractDir = "$env:TEMP\DocumentService.Client"
|
||
|
||
Invoke-WebRequest -Uri $nupkgUrl -OutFile $nupkgPath
|
||
|
||
# Entpacken (nupkg = ZIP)
|
||
Expand-Archive -Path $nupkgPath -DestinationPath $extractDir -Force
|
||
```
|
||
|
||
**Schritt 2 – Assembly für die Zielplattform laden:**
|
||
|
||
```powershell
|
||
# Für PowerShell 7+ (.NET 8 / net8.0)
|
||
$dllPath = Join-Path $extractDir "lib\net8.0\DocumentService.Client.dll"
|
||
|
||
# Für Windows PowerShell 5.1 (.NET Framework 4.8 / net48)
|
||
# $dllPath = Join-Path $extractDir "lib\net48\DocumentService.Client.dll"
|
||
|
||
Add-Type -Path $dllPath
|
||
```
|
||
|
||
**Schritt 3 – Abhängigkeiten laden** (falls nicht bereits im GAC / PowerShell-Runspace vorhanden):
|
||
|
||
```powershell
|
||
# Abhängige Assemblies aus demselben lib-Verzeichnis laden
|
||
$libDir = Split-Path $dllPath -Parent
|
||
Get-ChildItem -Path $libDir -Filter "*.dll" | ForEach-Object {
|
||
try { Add-Type -Path $_.FullName } catch { <# bereits geladen #> }
|
||
}
|
||
```
|
||
|
||
> **Hinweis:** In der Praxis empfiehlt es sich, die extrahierten DLLs einmalig in ein festes Verzeichnis (z. B. `C:\Tools\DocumentService.Client\`) zu kopieren und `Add-Type` mit einem absoluten Pfad aufzurufen. Das verhindert wiederholte Downloads und Versionskonflikte zwischen Skript-Ausführungen.
|
||
|
||
---
|
||
|
||
## Konfiguration
|
||
|
||
Diese Dokumentation beschreibt die Nutzung über den statischen `Client`-Einstiegspunkt, der ohne DI-Container auskommt. Die Library unterstützt darüber hinaus vollständige `IServiceCollection`-Integration via `AddDocumentServiceClients()` – dies ist jedoch nicht Gegenstand dieser Dokumentation.
|
||
|
||
### Einmalige Initialisierung (z. B. in `Program.cs` oder `App_Start`)
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Kurzform – nur Base-URL
|
||
Client.Configure("http://172.24.12.39:9393/");
|
||
|
||
// Vollständige Konfiguration
|
||
Client.Configure(options =>
|
||
{
|
||
options.BaseUrl = "http://172.24.12.39:9393/";
|
||
options.Timeout = TimeSpan.FromMinutes(10);
|
||
options.MaxRetries = 3;
|
||
options.ThrowOnError = true;
|
||
});
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
' Kurzform – nur Base-URL
|
||
Client.Configure("http://172.24.12.39:9393/")
|
||
|
||
' Vollständige Konfiguration
|
||
Client.Configure(Sub(options)
|
||
options.BaseUrl = "http://172.24.12.39:9393/"
|
||
options.Timeout = TimeSpan.FromMinutes(10)
|
||
options.MaxRetries = 3
|
||
options.ThrowOnError = True
|
||
End Sub)
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
# Kurzform – nur Base-URL
|
||
[DocumentService.Client.Client]::Configure("http://172.24.12.39:9393/")
|
||
|
||
# Vollständige Konfiguration
|
||
[DocumentService.Client.Client]::Configure([Action[DocumentService.Client.Configuration.DocumentServiceClientOptions]] {
|
||
param($options)
|
||
$options.BaseUrl = "http://172.24.12.39:9393/"
|
||
$options.Timeout = [TimeSpan]::FromMinutes(10)
|
||
$options.MaxRetries = 3
|
||
$options.ThrowOnError = $true
|
||
})
|
||
```
|
||
|
||
`Client.Configure()` ist **idempotent im Sinne der Initialisierung**: Der interne Service Provider wird beim ersten Zugriff lazy gebaut und danach nicht mehr verändert. Ein zweiter `Configure()`-Aufruf wird standardmäßig mit einer `InvalidOperationException` abgelehnt. Dieses Verhalten ist über `Client.OnReconfigure` steuerbar:
|
||
|
||
```csharp
|
||
// Standard – wirft bei erneutem Configure()-Aufruf
|
||
Client.OnReconfigure = OnReconfigure.ThrowException;
|
||
|
||
// Zweiten Aufruf stillschweigend ignorieren (z. B. in Bibliotheks-Code)
|
||
Client.OnReconfigure = OnReconfigure.Ignore;
|
||
```
|
||
|
||
```vbnet
|
||
' Standard – wirft bei erneutem Configure()-Aufruf
|
||
Client.OnReconfigure = OnReconfigure.ThrowException
|
||
|
||
' Zweiten Aufruf stillschweigend ignorieren
|
||
Client.OnReconfigure = OnReconfigure.Ignore
|
||
```
|
||
|
||
```powershell
|
||
# Standard – wirft bei erneutem Configure()-Aufruf
|
||
[DocumentService.Client.Client]::OnReconfigure = [DocumentService.Client.Models.ValueObjects.OnReconfigure]::ThrowException
|
||
|
||
# Zweiten Aufruf stillschweigend ignorieren
|
||
[DocumentService.Client.Client]::OnReconfigure = [DocumentService.Client.Models.ValueObjects.OnReconfigure]::Ignore
|
||
```
|
||
|
||
### Verfügbare Client-Eigenschaften
|
||
|
||
| Eigenschaft | Interface | Fachbereich |
|
||
|---|---|---|
|
||
| `Client.Validation` | `IPdfValidationClient` | PDF-Validierung |
|
||
| `Client.Attachment` | `IPdfAttachmentClient` | PDF-Anhänge |
|
||
| `Client.Operations` | `IPdfOperationsClient` | PDF-Operationen |
|
||
| `Client.SwissQrCode` | `ISwissQrCodeClient` | Schweizer QR-Code |
|
||
| `Client.Zugferd` | `IZugferdClient` | ZUGFeRD-Rechnungen |
|
||
| `Client.Conversion` | `IPdfConversionClient` | PDF-Konvertierung *(geplant)* |
|
||
| `Client.Workflows` | `IWorkflowsClient` | Kombinierte Operationen |
|
||
|
||
Jeder Zugriff auf eine dieser Eigenschaften erstellt einen neuen DI-Scope. Clients sind **nicht** für die dauerhafte Speicherung als Felder gedacht – jeder Aufruf holt sich eine frische Instanz.
|
||
|
||
> **PowerShell-Hinweis:** PowerShell unterstützt `async/await` nicht nativ. Alle asynchronen Methoden werden über `.GetAwaiter().GetResult()` synchron blockierend aufgerufen. Für lang laufende Operationen (z. B. große PDFs) empfiehlt sich die Ausführung in einem eigenen Thread via `[System.Threading.Tasks.Task]::Run({...}).GetAwaiter().GetResult()`.
|
||
|
||
---
|
||
|
||
## Konfigurationsoptionen
|
||
|
||
| Eigenschaft | Typ | Standardwert | Beschreibung |
|
||
|---|---|---|---|
|
||
| `BaseUrl` | `string` | `"http://localhost:5000"` | Basis-URL der DocumentService-API |
|
||
| `Timeout` | `TimeSpan` | `TimeSpan.FromMinutes(5)` | HTTP-Anfrage-Timeout |
|
||
| `MaxRetries` | `int` | `3` | Maximale Wiederholungsversuche bei transienten Fehlern |
|
||
| `ThrowOnError` | `bool` | `true` | Exception bei HTTP 4xx/5xx-Antworten |
|
||
|
||
---
|
||
|
||
## Verwendungsbeispiele
|
||
|
||
Alle Beispiele nutzen ausschließlich den statischen `Client`-Einstiegspunkt. Voraussetzung ist eine einmalige Konfiguration wie oben beschrieben.
|
||
|
||
---
|
||
|
||
### Workflows – Kombinierte Operationen
|
||
|
||
`Client.Workflows` fasst häufig benötigte Abläufe zusammen, die mehrere Einzelendpunkte orchestrieren. Validierung und Extraktion laufen in einem einzigen Aufruf – das Ergebnis enthält immer beide Informationen.
|
||
|
||
> Für Anwendungsfälle wie den **eParser** (Swiss QR Bill aus Eingangsrechnungen lesen) ist dies der empfohlene Einstiegspunkt.
|
||
|
||
#### Swiss QR Code – Validierung und Extraktion in einem Schritt
|
||
|
||
`InspectSwissQrCodeAsync` gibt immer ein `SwissQrCodeResult` zurück, das sowohl das Validierungsergebnis als auch den extrahierten QR-Code enthält. Für den Zugriff auf den QR-Code stehen zwei gleichwertige Varianten zur Verfügung:
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
using DocumentService.Client.Models.Results;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("rechnung.pdf");
|
||
SwissQrCodeResult result = await Client.Workflows.InspectSwissQrCodeAsync(pdfBytes);
|
||
|
||
// Validierungsergebnis ist immer verfügbar
|
||
Console.WriteLine($"Gültig: {result.Validation.IsValid}");
|
||
Console.WriteLine($"Seiten: {result.Validation.PageCount}");
|
||
Console.WriteLine($"Verschlüsselt: {result.Validation.IsEncrypted}");
|
||
|
||
// Variante A: TryExtract – lesbarerer Code, kein expliziter Null-Check notwendig
|
||
// (optionale Hilfsmethode; Variante B ist gleichwertig)
|
||
if (result.TryExtract(out var qrCode))
|
||
{
|
||
Console.WriteLine($"Betrag: {qrCode.Bill?.Amount} {qrCode.Bill?.Currency}");
|
||
Console.WriteLine($"Empfänger: {qrCode.Bill?.Creditor?.Name}");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("Kein Swiss QR Code gefunden oder Dokument ungültig.");
|
||
}
|
||
|
||
// Variante B: direkter Null-Check auf QrCode
|
||
if (result.QrCode is not null)
|
||
{
|
||
Console.WriteLine($"Betrag: {result.QrCode.Bill?.Amount} {result.QrCode.Bill?.Currency}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
Imports DocumentService.Client.Models.Results
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("rechnung.pdf")
|
||
Dim result As SwissQrCodeResult = Await Client.Workflows.InspectSwissQrCodeAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Gültig: {result.Validation.IsValid}")
|
||
Console.WriteLine($"Seiten: {result.Validation.PageCount}")
|
||
Console.WriteLine($"Verschlüsselt: {result.Validation.IsEncrypted}")
|
||
|
||
' Variante A: TryExtract
|
||
Dim qrCode As DocumentService.Application.Common.DTOs.SwissQrCodeExtractionResult = Nothing
|
||
If result.TryExtract(qrCode) Then
|
||
Console.WriteLine($"Betrag: {qrCode.Bill?.Amount} {qrCode.Bill?.Currency}")
|
||
Console.WriteLine($"Empfänger: {qrCode.Bill?.Creditor?.Name}")
|
||
Else
|
||
Console.WriteLine("Kein Swiss QR Code gefunden oder Dokument ungültig.")
|
||
End If
|
||
|
||
' Variante B: direkter Null-Check
|
||
If result.QrCode IsNot Nothing Then
|
||
Console.WriteLine($"Betrag: {result.QrCode.Bill?.Amount} {result.QrCode.Bill?.Currency}")
|
||
End If
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::Workflows.InspectSwissQrCodeAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Gueltig: $($result.Validation.IsValid)"
|
||
Write-Host "Seiten: $($result.Validation.PageCount)"
|
||
Write-Host "Verschluesselt:$($result.Validation.IsEncrypted)"
|
||
|
||
# TryExtract ist in PowerShell nicht direkt nutzbar (out-Parameter).
|
||
# Direkter Null-Check ist hier die einzige Option:
|
||
if ($null -ne $result.QrCode) {
|
||
Write-Host "Betrag: $($result.QrCode.Bill.Amount) $($result.QrCode.Bill.Currency)"
|
||
Write-Host "Empfaenger:$($result.QrCode.Bill.Creditor.Name)"
|
||
} else {
|
||
Write-Host "Kein Swiss QR Code gefunden oder Dokument ungueltig."
|
||
}
|
||
```
|
||
|
||
> **`TryExtract` – Hinweis:** Die Methode ist eine optionale Lesbarkeits-Hilfe und nicht zwingend erforderlich. Der direkte Null-Check auf `result.QrCode` ist gleichwertig. Gibt `TryExtract` `false` zurück, ist der `out`-Parameter `qrCode` `null` – er darf in diesem Fall nicht verwendet werden.
|
||
|
||
---
|
||
|
||
### 1. PDF-Validierung
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Variante A: Stream (multipart/form-data – empfohlen für große Dateien)
|
||
using var stream = File.OpenRead("rechnung.pdf");
|
||
var result = await Client.Validation.ValidateAsync(stream);
|
||
|
||
Console.WriteLine($"Seiten: {result.PageCount}");
|
||
Console.WriteLine($"PDF-Version: {result.PdfVersion}");
|
||
Console.WriteLine($"Verschlüsselt: {result.IsEncrypted}");
|
||
Console.WriteLine($"Anhänge: {result.AttachmentCount}");
|
||
Console.WriteLine($"Dateigröße: {result.FileSizeMB:F2} MB");
|
||
|
||
// Variante B: byte[] (Base64 JSON)
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("rechnung.pdf");
|
||
var result2 = await Client.Validation.ValidateAsync(pdfBytes);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
' Variante A: Stream
|
||
Dim stream As New System.IO.FileStream("rechnung.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.Validation.ValidateAsync(stream)
|
||
stream.Dispose()
|
||
|
||
Console.WriteLine($"Seiten: {result.PageCount}")
|
||
Console.WriteLine($"PDF-Version: {result.PdfVersion}")
|
||
Console.WriteLine($"Verschlüsselt: {result.IsEncrypted}")
|
||
Console.WriteLine($"Anhänge: {result.AttachmentCount}")
|
||
Console.WriteLine($"Dateigröße: {result.FileSizeMB:F2} MB")
|
||
|
||
' Variante B: byte[]
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("rechnung.pdf")
|
||
Dim result2 = Await Client.Validation.ValidateAsync(pdfBytes)
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
# Variante A: Stream
|
||
$stream = [System.IO.File]::OpenRead("rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::Validation.ValidateAsync($stream).GetAwaiter().GetResult()
|
||
$stream.Dispose()
|
||
|
||
Write-Host "Seiten: $($result.PageCount)"
|
||
Write-Host "PDF-Version: $($result.PdfVersion)"
|
||
Write-Host "Verschluesselt:$($result.IsEncrypted)"
|
||
Write-Host "Anhaenge: $($result.AttachmentCount)"
|
||
Write-Host "Dateigroesse: $($result.FileSizeMB.ToString('F2')) MB"
|
||
|
||
# Variante B: byte[]
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("rechnung.pdf")
|
||
$result2 = [DocumentService.Client.Client]::Validation.ValidateAsync($pdfBytes).GetAwaiter().GetResult()
|
||
```
|
||
|
||
---
|
||
|
||
#### PDF/A-Konformitätsprüfung
|
||
|
||
> **⚠️ Hinweis zur Validierungstiefe:** `ValidatePdfAAsync` führt eine **heuristische PDF/A-Erkennung** durch, keine vollständige ISO-19005-Konformitätsprüfung. Die Implementierung erkennt das PDF/A-Konformitätslevel anhand der XMP-Metadaten (`pdfaid:part`, `pdfaid:conformance`) und prüft auf Verschlüsselung. Folgende Anforderungen der Norm werden **nicht** geprüft:
|
||
>
|
||
> - Einbettung aller verwendeten Schriftarten
|
||
> - Gültigkeit und Vollständigkeit der Farbprofile (ICC)
|
||
> - Fehlen von JavaScript, eingebetteten Multimedia-Inhalten und externen Referenzen
|
||
> - Strukturelle Barrierefreiheit (Level A)
|
||
>
|
||
> Das Ergebnis `IsValid = true` bedeutet daher: **„Keine erkannten Verstöße"**, nicht **„nachgewiesene ISO-19005-Konformität"**. Für eine zertifizierungsfähige Prüfung ist ein dedizierter Validator (z. B. veraPDF) erforderlich.
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
using var stream = File.OpenRead("archiv.pdf");
|
||
var result = await Client.Validation.ValidatePdfAAsync(stream);
|
||
|
||
Console.WriteLine($"PDF/A-konform: {result.IsValid}");
|
||
Console.WriteLine($"PDF/A-Version: {result.PdfAVersion}");
|
||
Console.WriteLine($"Seiten: {result.PageCount}");
|
||
|
||
if (result.Errors.Count > 0)
|
||
{
|
||
Console.WriteLine("Konformitätsfehler:");
|
||
foreach (var error in result.Errors)
|
||
Console.WriteLine($" - {error}");
|
||
}
|
||
|
||
if (result.Warnings.Count > 0)
|
||
{
|
||
Console.WriteLine("Warnungen:");
|
||
foreach (var warning in result.Warnings)
|
||
Console.WriteLine($" - {warning}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim stream As New System.IO.FileStream("archiv.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.Validation.ValidatePdfAAsync(stream)
|
||
stream.Dispose()
|
||
|
||
Console.WriteLine($"PDF/A-konform: {result.IsValid}")
|
||
Console.WriteLine($"PDF/A-Version: {result.PdfAVersion}")
|
||
Console.WriteLine($"Seiten: {result.PageCount}")
|
||
|
||
If result.Errors.Count > 0 Then
|
||
Console.WriteLine("Konformitätsfehler:")
|
||
For Each [error] In result.Errors
|
||
Console.WriteLine($" - {[error]}")
|
||
Next
|
||
End If
|
||
|
||
If result.Warnings.Count > 0 Then
|
||
Console.WriteLine("Warnungen:")
|
||
For Each warning In result.Warnings
|
||
Console.WriteLine($" - {warning}")
|
||
Next
|
||
End If
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$stream = [System.IO.File]::OpenRead("archiv.pdf")
|
||
$result = [DocumentService.Client.Client]::Validation.ValidatePdfAAsync($stream).GetAwaiter().GetResult()
|
||
$stream.Dispose()
|
||
|
||
Write-Host "PDF/A-konform: $($result.IsValid)"
|
||
Write-Host "PDF/A-Version: $($result.PdfAVersion)"
|
||
Write-Host "Seiten: $($result.PageCount)"
|
||
|
||
if ($result.Errors.Count -gt 0) {
|
||
Write-Host "Konformitaetsfehler:"
|
||
foreach ($err in $result.Errors) { Write-Host " - $err" }
|
||
}
|
||
|
||
if ($result.Warnings.Count -gt 0) {
|
||
Write-Host "Warnungen:"
|
||
foreach ($warn in $result.Warnings) { Write-Host " - $warn" }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. PDF-Anhänge
|
||
|
||
#### Anhänge erkennen
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("dokument.pdf");
|
||
var check = await Client.Attachment.CheckAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Hat Anhänge: {check.HasAttachments}");
|
||
Console.WriteLine($"Anzahl: {check.AttachmentCount}");
|
||
|
||
foreach (var attachment in check.Attachments)
|
||
Console.WriteLine($" {attachment.FileName} ({attachment.Size} Bytes, {attachment.MimeType})");
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("dokument.pdf")
|
||
Dim check = Await Client.Attachment.CheckAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Hat Anhänge: {check.HasAttachments}")
|
||
Console.WriteLine($"Anzahl: {check.AttachmentCount}")
|
||
|
||
For Each attachment In check.Attachments
|
||
Console.WriteLine($" {attachment.FileName} ({attachment.Size} Bytes, {attachment.MimeType})")
|
||
Next
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("dokument.pdf")
|
||
$check = [DocumentService.Client.Client]::Attachment.CheckAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Hat Anhaenge: $($check.HasAttachments)"
|
||
Write-Host "Anzahl: $($check.AttachmentCount)"
|
||
|
||
foreach ($att in $check.Attachments) {
|
||
Write-Host " $($att.FileName) ($($att.Size) Bytes, $($att.MimeType))"
|
||
}
|
||
```
|
||
|
||
#### Anhänge extrahieren
|
||
|
||
Rückgabe: `Dictionary<string, Stream>` – Schlüssel = ursprünglicher Dateiname im PDF, Wert = Dateiinhalt als `MemoryStream`. Der Aufrufer ist für die Freigabe der Streams verantwortlich.
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
using var pdfStream = File.OpenRead("dokument_mit_anhaengen.pdf");
|
||
var files = await Client.Attachment.ExtractAsync(pdfStream);
|
||
|
||
foreach (var (fileName, fileStream) in files)
|
||
{
|
||
await using var fs = fileStream;
|
||
var outputPath = Path.Combine("output", fileName);
|
||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
|
||
await using var output = File.Create(outputPath);
|
||
await fs.CopyToAsync(output);
|
||
Console.WriteLine($"Extrahiert: {outputPath}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfStream As New System.IO.FileStream("dokument_mit_anhaengen.pdf", System.IO.FileMode.Open)
|
||
Dim files = Await Client.Attachment.ExtractAsync(pdfStream)
|
||
pdfStream.Dispose()
|
||
|
||
For Each entry In files
|
||
Dim outputPath = System.IO.Path.Combine("output", entry.Key)
|
||
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outputPath))
|
||
Using fs = entry.Value
|
||
Using output = System.IO.File.Create(outputPath)
|
||
Await fs.CopyToAsync(output)
|
||
End Using
|
||
End Using
|
||
Console.WriteLine($"Extrahiert: {outputPath}")
|
||
Next
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfStream = [System.IO.File]::OpenRead("dokument_mit_anhaengen.pdf")
|
||
$files = [DocumentService.Client.Client]::Attachment.ExtractAsync($pdfStream).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
New-Item -ItemType Directory -Force -Path "output" | Out-Null
|
||
|
||
foreach ($entry in $files.GetEnumerator()) {
|
||
$outputPath = Join-Path "output" $entry.Key
|
||
$dir = Split-Path $outputPath -Parent
|
||
if ($dir) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
||
|
||
$outFile = [System.IO.File]::Create($outputPath)
|
||
$entry.Value.CopyTo($outFile)
|
||
$outFile.Dispose()
|
||
$entry.Value.Dispose()
|
||
|
||
Write-Host "Extrahiert: $outputPath"
|
||
}
|
||
```
|
||
|
||
> **Hinweis – `AddAsync`:** Das Einbetten von Anhängen in PDF/A-3-Dokumente ist für eine zukünftige Version geplant. Die Methode ist bereits im Interface definiert, jedoch als `[Obsolete]` markiert und wirft zur Laufzeit eine `NotImplementedException`. Der API-Endpunkt `POST /api/pdf/attachments/add` ist noch nicht implementiert.
|
||
|
||
---
|
||
|
||
### 3. PDF-Operationen
|
||
|
||
#### Zusammenführen
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// pageRanges ist optional – null bedeutet "alle Seiten dieses Dokuments"
|
||
// Seitenbereich-Syntax: "1-3" = Seiten 1 bis 3, "2,4,6" = einzelne Seiten
|
||
var streams = new List<Stream>
|
||
{
|
||
File.OpenRead("teil1.pdf"),
|
||
File.OpenRead("teil2.pdf"),
|
||
File.OpenRead("teil3.pdf")
|
||
};
|
||
|
||
using var mergedStream = await Client.Operations.MergeAsync(
|
||
streams,
|
||
pageRanges: new List<string?> { "1-2", null, "3,5" }
|
||
);
|
||
|
||
foreach (var s in streams) await s.DisposeAsync();
|
||
|
||
await using var output = File.Create("zusammengefuehrt.pdf");
|
||
await mergedStream.CopyToAsync(output);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim streams As New List(Of System.IO.Stream) From {
|
||
System.IO.File.OpenRead("teil1.pdf"),
|
||
System.IO.File.OpenRead("teil2.pdf"),
|
||
System.IO.File.OpenRead("teil3.pdf")
|
||
}
|
||
Dim pageRanges As New List(Of String) From {"1-2", Nothing, "3,5"}
|
||
|
||
Dim mergedStream = Await Client.Operations.MergeAsync(streams, pageRanges)
|
||
|
||
For Each s In streams
|
||
s.Dispose()
|
||
Next
|
||
|
||
Using output = System.IO.File.Create("zusammengefuehrt.pdf")
|
||
Await mergedStream.CopyToAsync(output)
|
||
End Using
|
||
mergedStream.Dispose()
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$streams = [System.Collections.Generic.List[System.IO.Stream]]::new()
|
||
$streams.Add([System.IO.File]::OpenRead("teil1.pdf"))
|
||
$streams.Add([System.IO.File]::OpenRead("teil2.pdf"))
|
||
$streams.Add([System.IO.File]::OpenRead("teil3.pdf"))
|
||
|
||
$pageRanges = [System.Collections.Generic.List[string]]::new()
|
||
$pageRanges.Add("1-2")
|
||
$pageRanges.Add($null)
|
||
$pageRanges.Add("3,5")
|
||
|
||
$mergedStream = [DocumentService.Client.Client]::Operations.MergeAsync($streams, $pageRanges).GetAwaiter().GetResult()
|
||
|
||
foreach ($s in $streams) { $s.Dispose() }
|
||
|
||
$output = [System.IO.File]::Create("zusammengefuehrt.pdf")
|
||
$mergedStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$mergedStream.Dispose()
|
||
```
|
||
|
||
#### Annotieren
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
using DocumentService.Application.Common.DTOs.Requests;
|
||
using DocumentService.Domain.Models.ValueObjects;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("dokument.pdf");
|
||
|
||
var request = new AddAnnotationBase64Request
|
||
{
|
||
Base64Pdf = string.Empty, // wird intern vom Client befüllt
|
||
AnnotationType = AnnotationType.TextMarkup,
|
||
PageNumber = 1,
|
||
X1 = 100,
|
||
Y1 = 200,
|
||
Width = 300,
|
||
Height = 20,
|
||
Color = "FFFF00", // Gelb (RGB-Hex)
|
||
TextMarkupStyle = TextMarkupStyle.Highlight,
|
||
Origin = AnnotationOrigin.TopLeft // Y-Achse von oben (UI-Koordinaten)
|
||
};
|
||
|
||
using var annotatedStream = await Client.Operations.AnnotateAsync(pdfBytes, request);
|
||
await using var output = File.Create("annotiert.pdf");
|
||
await annotatedStream.CopyToAsync(output);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
Imports DocumentService.Application.Common.DTOs.Requests
|
||
Imports DocumentService.Domain.Models.ValueObjects
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("dokument.pdf")
|
||
|
||
Dim request As New AddAnnotationBase64Request With {
|
||
.Base64Pdf = String.Empty,
|
||
.AnnotationType = AnnotationType.TextMarkup,
|
||
.PageNumber = 1,
|
||
.X1 = 100,
|
||
.Y1 = 200,
|
||
.Width = 300,
|
||
.Height = 20,
|
||
.Color = "FFFF00",
|
||
.TextMarkupStyle = TextMarkupStyle.Highlight,
|
||
.Origin = AnnotationOrigin.TopLeft
|
||
}
|
||
|
||
Dim annotatedStream = Await Client.Operations.AnnotateAsync(pdfBytes, request)
|
||
Using output = System.IO.File.Create("annotiert.pdf")
|
||
Await annotatedStream.CopyToAsync(output)
|
||
End Using
|
||
annotatedStream.Dispose()
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("dokument.pdf")
|
||
|
||
$request = [DocumentService.Application.Common.DTOs.Requests.AddAnnotationBase64Request]::new()
|
||
$request.Base64Pdf = ""
|
||
$request.AnnotationType = [DocumentService.Domain.Models.ValueObjects.AnnotationType]::TextMarkup
|
||
$request.PageNumber = 1
|
||
$request.X1 = 100
|
||
$request.Y1 = 200
|
||
$request.Width = 300
|
||
$request.Height = 20
|
||
$request.Color = "FFFF00"
|
||
$request.TextMarkupStyle = [DocumentService.Domain.Models.ValueObjects.TextMarkupStyle]::Highlight
|
||
$request.Origin = [DocumentService.Domain.Models.ValueObjects.AnnotationOrigin]::TopLeft
|
||
|
||
$annotatedStream = [DocumentService.Client.Client]::Operations.AnnotateAsync($pdfBytes, $request).GetAwaiter().GetResult()
|
||
$output = [System.IO.File]::Create("annotiert.pdf")
|
||
$annotatedStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$annotatedStream.Dispose()
|
||
```
|
||
|
||
**Verfügbare Annotationstypen:**
|
||
|
||
| `AnnotationType` | Beschreibung |
|
||
|---|---|
|
||
| `TextMarkup` | Hervorhebung, Unterstreichung oder Durchstreichung (`TextMarkupStyle`) |
|
||
| `FreeText` | Freitextkommentar direkt auf der Seite |
|
||
| `StickyNote` | Klebezettel (Popup-Kommentar) |
|
||
| `Circle` | Kreismarkierung |
|
||
| `Square` | Rechteckmarkierung |
|
||
|
||
**Koordinatenursprung (`AnnotationOrigin`):**
|
||
|
||
| Wert | Bedeutung |
|
||
|---|---|
|
||
| `TopLeft` | Y-Achse von oben – entspricht UI-Koordinaten (z. B. aus einem Viewer) |
|
||
| `BottomLeft` | Y-Achse von unten – natives PDF-Koordinatensystem |
|
||
|
||
#### Stempel
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
using DocumentService.Application.Common.DTOs.Requests;
|
||
using DocumentService.Domain.Models.ValueObjects;
|
||
|
||
// Textstempel diagonal auf alle Seiten
|
||
using var pdfStream = File.OpenRead("dokument.pdf");
|
||
|
||
var request = new AddStampBase64Request
|
||
{
|
||
Base64Pdf = string.Empty, // wird intern vom Client befüllt
|
||
StampType = StampType.Text,
|
||
Text = "VERTRAULICH",
|
||
FontName = "Arial",
|
||
FontSize = 36,
|
||
Color = "FF0000", // Rot
|
||
Opacity = 0.4, // 40% Transparenz
|
||
Rotation = 45, // Grad gegen den Uhrzeigersinn
|
||
X = 150,
|
||
Y = 400,
|
||
Placement = StampPlacement.Foreground, // über dem Inhalt
|
||
Origin = AnnotationOrigin.BottomLeft
|
||
// PageNumbers = null → alle Seiten
|
||
};
|
||
|
||
using var stampedStream = await Client.Operations.StampAsync(pdfStream, request);
|
||
await using var output = File.Create("gestempelt.pdf");
|
||
await stampedStream.CopyToAsync(output);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
Imports DocumentService.Application.Common.DTOs.Requests
|
||
Imports DocumentService.Domain.Models.ValueObjects
|
||
|
||
Dim pdfStream As New System.IO.FileStream("dokument.pdf", System.IO.FileMode.Open)
|
||
|
||
Dim request As New AddStampBase64Request With {
|
||
.Base64Pdf = String.Empty,
|
||
.StampType = StampType.Text,
|
||
.Text = "VERTRAULICH",
|
||
.FontName = "Arial",
|
||
.FontSize = 36,
|
||
.Color = "FF0000",
|
||
.Opacity = 0.4,
|
||
.Rotation = 45,
|
||
.X = 150,
|
||
.Y = 400,
|
||
.Placement = StampPlacement.Foreground,
|
||
.Origin = AnnotationOrigin.BottomLeft
|
||
}
|
||
|
||
Dim stampedStream = Await Client.Operations.StampAsync(pdfStream, request)
|
||
pdfStream.Dispose()
|
||
|
||
Using output = System.IO.File.Create("gestempelt.pdf")
|
||
Await stampedStream.CopyToAsync(output)
|
||
End Using
|
||
stampedStream.Dispose()
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfStream = [System.IO.File]::OpenRead("dokument.pdf")
|
||
|
||
$request = [DocumentService.Application.Common.DTOs.Requests.AddStampBase64Request]::new()
|
||
$request.Base64Pdf = ""
|
||
$request.StampType = [DocumentService.Domain.Models.ValueObjects.StampType]::Text
|
||
$request.Text = "VERTRAULICH"
|
||
$request.FontName = "Arial"
|
||
$request.FontSize = 36
|
||
$request.Color = "FF0000"
|
||
$request.Opacity = 0.4
|
||
$request.Rotation = 45
|
||
$request.X = 150
|
||
$request.Y = 400
|
||
$request.Placement = [DocumentService.Domain.Models.ValueObjects.StampPlacement]::Foreground
|
||
$request.Origin = [DocumentService.Domain.Models.ValueObjects.AnnotationOrigin]::BottomLeft
|
||
|
||
$stampedStream = [DocumentService.Client.Client]::Operations.StampAsync($pdfStream, $request).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
$output = [System.IO.File]::Create("gestempelt.pdf")
|
||
$stampedStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$stampedStream.Dispose()
|
||
```
|
||
|
||
```csharp
|
||
// Vordefinierter Stempel
|
||
var request = new AddStampBase64Request
|
||
{
|
||
Base64Pdf = string.Empty,
|
||
StampType = StampType.Predefined,
|
||
PredefinedStamp = PredefinedStampType.Approved,
|
||
X = 400,
|
||
Y = 700,
|
||
Placement = StampPlacement.Foreground,
|
||
Origin = AnnotationOrigin.BottomLeft
|
||
};
|
||
```
|
||
|
||
```vbnet
|
||
' Vordefinierter Stempel
|
||
Dim request As New AddStampBase64Request With {
|
||
.Base64Pdf = String.Empty,
|
||
.StampType = StampType.Predefined,
|
||
.PredefinedStamp = PredefinedStampType.Approved,
|
||
.X = 400,
|
||
.Y = 700,
|
||
.Placement = StampPlacement.Foreground,
|
||
.Origin = AnnotationOrigin.BottomLeft
|
||
}
|
||
```
|
||
|
||
```powershell
|
||
# Vordefinierter Stempel
|
||
$request = [DocumentService.Application.Common.DTOs.Requests.AddStampBase64Request]::new()
|
||
$request.Base64Pdf = ""
|
||
$request.StampType = [DocumentService.Domain.Models.ValueObjects.StampType]::Predefined
|
||
$request.PredefinedStamp = [DocumentService.Domain.Models.ValueObjects.PredefinedStampType]::Approved
|
||
$request.X = 400
|
||
$request.Y = 700
|
||
$request.Placement = [DocumentService.Domain.Models.ValueObjects.StampPlacement]::Foreground
|
||
$request.Origin = [DocumentService.Domain.Models.ValueObjects.AnnotationOrigin]::BottomLeft
|
||
```
|
||
|
||
**Verfügbare Stempeltypen:**
|
||
|
||
| `StampType` | Beschreibung |
|
||
|---|---|
|
||
| `Text` | Eigener Textstempel mit freier Formatierung |
|
||
| `Image` | Bildstempel – `Base64Image` als PNG/JPEG in Base64 |
|
||
| `Predefined` | Vorgefertigter Stempel: `Confidential`, `Approved`, `Draft`, `Void`, `ForReview` |
|
||
|
||
---
|
||
|
||
### 4. Schweizer QR-Code-Extraktion
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Geparste Rechnungsstruktur gemäß Swiss QR Bill Standard 2.0
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("qr-rechnung.pdf");
|
||
var result = await Client.SwissQrCode.ExtractAsync(pdfBytes, raw: false);
|
||
|
||
Console.WriteLine($"Betrag: {result.Bill?.Amount} {result.Bill?.Currency}");
|
||
Console.WriteLine($"Konto: {result.Bill?.Account}");
|
||
Console.WriteLine($"Empfänger: {result.Bill?.Creditor?.Name}, {result.Bill?.Creditor?.Town}");
|
||
Console.WriteLine($"Referenz: {result.Bill?.Reference}");
|
||
Console.WriteLine($"Mitteilung:{result.Bill?.UnstructuredMessage}");
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("qr-rechnung.pdf")
|
||
Dim result = Await Client.SwissQrCode.ExtractAsync(pdfBytes, raw:=False)
|
||
|
||
Console.WriteLine($"Betrag: {result.Bill?.Amount} {result.Bill?.Currency}")
|
||
Console.WriteLine($"Konto: {result.Bill?.Account}")
|
||
Console.WriteLine($"Empfänger: {result.Bill?.Creditor?.Name}, {result.Bill?.Creditor?.Town}")
|
||
Console.WriteLine($"Referenz: {result.Bill?.Reference}")
|
||
Console.WriteLine($"Mitteilung:{result.Bill?.UnstructuredMessage}")
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("qr-rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::SwissQrCode.ExtractAsync($pdfBytes, $false).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Betrag: $($result.Bill.Amount) $($result.Bill.Currency)"
|
||
Write-Host "Konto: $($result.Bill.Account)"
|
||
Write-Host "Empfaenger:$($result.Bill.Creditor.Name), $($result.Bill.Creditor.Town)"
|
||
Write-Host "Referenz: $($result.Bill.Reference)"
|
||
Write-Host "Mitteilung:$($result.Bill.UnstructuredMessage)"
|
||
```
|
||
|
||
---
|
||
|
||
#### Rohe QR-Textzeilen
|
||
|
||
```csharp
|
||
// Ohne Parsing – direkt aus dem QR-Code-Inhalt
|
||
using var pdfStream = File.OpenRead("qr-rechnung.pdf");
|
||
var result = await Client.SwissQrCode.ExtractAsync(pdfStream, raw: true);
|
||
|
||
foreach (var line in result.RawLines)
|
||
Console.WriteLine(line);
|
||
```
|
||
|
||
```vbnet
|
||
Dim pdfStream As New System.IO.FileStream("qr-rechnung.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.SwissQrCode.ExtractAsync(pdfStream, raw:=True)
|
||
pdfStream.Dispose()
|
||
|
||
For Each line In result.RawLines
|
||
Console.WriteLine(line)
|
||
Next
|
||
```
|
||
|
||
```powershell
|
||
$pdfStream = [System.IO.File]::OpenRead("qr-rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::SwissQrCode.ExtractAsync($pdfStream, $true).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
foreach ($line in $result.RawLines) { Write-Host $line }
|
||
```
|
||
|
||
> **Pflichtanforderung laut Standard:** Der QR-Code muss sich auf der **letzten Seite** des PDFs befinden. PDFs, bei denen der QR-Code auf einer anderen Seite liegt, werden nicht erkannt.
|
||
|
||
---
|
||
|
||
### 5. ZUGFeRD-Rechnungen
|
||
|
||
#### ZUGFeRD-Einbettung prüfen
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("eingangsrechnung.pdf");
|
||
var check = await Client.Zugferd.CheckAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Enthält ZUGFeRD: {check.HasZugferd}");
|
||
|
||
if (check.HasZugferd)
|
||
{
|
||
Console.WriteLine($"XML-Dateiname: {check.ZugferdFileName}");
|
||
Console.WriteLine($"Dateigröße: {check.ZugferdFileSize} Bytes");
|
||
Console.WriteLine($"MIME-Typ: {check.ZugferdMimeType}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("eingangsrechnung.pdf")
|
||
Dim check = Await Client.Zugferd.CheckAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Enthält ZUGFeRD: {check.HasZugferd}")
|
||
|
||
If check.HasZugferd Then
|
||
Console.WriteLine($"XML-Dateiname: {check.ZugferdFileName}")
|
||
Console.WriteLine($"Dateigröße: {check.ZugferdFileSize} Bytes")
|
||
Console.WriteLine($"MIME-Typ: {check.ZugferdMimeType}")
|
||
End If
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("eingangsrechnung.pdf")
|
||
$check = [DocumentService.Client.Client]::Zugferd.CheckAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Enthaelt ZUGFeRD: $($check.HasZugferd)"
|
||
|
||
if ($check.HasZugferd) {
|
||
Write-Host "XML-Dateiname: $($check.ZugferdFileName)"
|
||
Write-Host "Dateigroesse: $($check.ZugferdFileSize) Bytes"
|
||
Write-Host "MIME-Typ: $($check.ZugferdMimeType)"
|
||
}
|
||
```
|
||
|
||
#### ZUGFeRD-XML als Stream extrahieren
|
||
|
||
```csharp
|
||
using var pdfStream = File.OpenRead("eingangsrechnung.pdf");
|
||
using var xmlStream = await Client.Zugferd.ExtractAsync(pdfStream);
|
||
|
||
await using var output = File.Create("factur-x.xml");
|
||
await xmlStream.CopyToAsync(output);
|
||
Console.WriteLine("ZUGFeRD-XML gespeichert.");
|
||
```
|
||
|
||
```vbnet
|
||
Dim pdfStream As New System.IO.FileStream("eingangsrechnung.pdf", System.IO.FileMode.Open)
|
||
Dim xmlStream = Await Client.Zugferd.ExtractAsync(pdfStream)
|
||
pdfStream.Dispose()
|
||
|
||
Using output = System.IO.File.Create("factur-x.xml")
|
||
Await xmlStream.CopyToAsync(output)
|
||
End Using
|
||
xmlStream.Dispose()
|
||
Console.WriteLine("ZUGFeRD-XML gespeichert.")
|
||
```
|
||
|
||
```powershell
|
||
$pdfStream = [System.IO.File]::OpenRead("eingangsrechnung.pdf")
|
||
$xmlStream = [DocumentService.Client.Client]::Zugferd.ExtractAsync($pdfStream).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
$output = [System.IO.File]::Create("factur-x.xml")
|
||
$xmlStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$xmlStream.Dispose()
|
||
Write-Host "ZUGFeRD-XML gespeichert."
|
||
```
|
||
|
||
#### ZUGFeRD-XML als strukturiertes Ergebnis
|
||
|
||
```csharp
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("eingangsrechnung.pdf");
|
||
var result = await Client.Zugferd.ExtractAsResultAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Dateiname: {result.FileName}");
|
||
Console.WriteLine($"Größe: {result.FileSize} Bytes");
|
||
Console.WriteLine($"XML-Inhalt: {result.XmlContent[..500]}...");
|
||
|
||
// XML weiterverarbeiten, z. B. mit System.Xml.Linq
|
||
var doc = XDocument.Parse(result.XmlContent);
|
||
```
|
||
|
||
```vbnet
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("eingangsrechnung.pdf")
|
||
Dim result = Await Client.Zugferd.ExtractAsResultAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Dateiname: {result.FileName}")
|
||
Console.WriteLine($"Größe: {result.FileSize} Bytes")
|
||
Console.WriteLine($"XML-Inhalt: {result.XmlContent.Substring(0, 500)}...")
|
||
|
||
' XML weiterverarbeiten
|
||
Dim doc = System.Xml.Linq.XDocument.Parse(result.XmlContent)
|
||
```
|
||
|
||
```powershell
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("eingangsrechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::Zugferd.ExtractAsResultAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Dateiname: $($result.FileName)"
|
||
Write-Host "Groesse: $($result.FileSize) Bytes"
|
||
Write-Host "XML-Inhalt: $($result.XmlContent.Substring(0, 500))..."
|
||
|
||
# XML weiterverarbeiten
|
||
$doc = [System.Xml.Linq.XDocument]::Parse($result.XmlContent)
|
||
```
|
||
|
||
---
|
||
|
||
### 6. PDF-Konvertierung *(geplant – noch nicht verfügbar)*
|
||
|
||
`IPdfConversionClient` ist vollständig definiert und über `Client.Conversion` erreichbar. Alle Methoden sind jedoch als `[Obsolete]` markiert und werfen zur Laufzeit `NotImplementedException`, da die API-Endpunkte serverseitig noch nicht implementiert sind.
|
||
|
||
**Geplante Endpunkte (Phase 3):**
|
||
- `POST /api/pdf/conversion/to-pdfa` – beliebiges PDF in PDF/A konvertieren
|
||
- `POST /api/pdf/conversion/from-pdfa` – PDF/A-Einschränkungen entfernen
|
||
|
||
```csharp
|
||
// NICHT VERWENDEN – wirft NotImplementedException
|
||
#pragma warning disable CS0618
|
||
using var pdfAStream = await Client.Conversion.ToPdfAAsync(pdfStream, pdfALevel: "PDF/A-3b");
|
||
#pragma warning restore CS0618
|
||
```
|
||
|
||
```vbnet
|
||
' NICHT VERWENDEN – wirft NotImplementedException
|
||
#Disable Warning BC40000
|
||
Dim pdfAStream = Await Client.Conversion.ToPdfAAsync(pdfStream, pdfALevel:="PDF/A-3b")
|
||
#Enable Warning BC40000
|
||
```
|
||
|
||
```powershell
|
||
# NICHT VERWENDEN – wirft NotImplementedException
|
||
$pdfAStream = [DocumentService.Client.Client]::Conversion.ToPdfAAsync($pdfStream, "PDF/A-3b").GetAwaiter().GetResult()
|
||
```
|
||
|
||
Sobald die Endpunkte auf dem Server bereitgestellt werden, entfällt das `[Obsolete]`-Attribut und die Methoden sind ohne weiteren Anpassungsbedarf auf Clientseite verwendbar.
|
||
|
||
---
|
||
|
||
## API-Endpunkte (Übersicht)
|
||
|
||
| **Client-Eigenschaft** | **Methode** | **API-Endpunkt** | **Status** | **Beschreibung** |
|
||
|---|---|---|---|---|
|
||
| `Client.Validation` | `ValidateAsync()` | `POST /api/pdf/validation/validate` | ✅ Verfügbar | PDF validieren, Metadaten abrufen |
|
||
| `Client.Validation` | `ValidatePdfAAsync()` | `POST /api/pdf/validation/validate-pdfa` | ✅ Verfügbar | PDF/A-Erkennung (heuristisch, keine vollständige ISO-19005-Prüfung) |
|
||
| `Client.Attachment` | `CheckAsync()` | `POST /api/pdf/attachments/check` | ✅ Verfügbar | Eingebettete Anhänge erkennen |
|
||
| `Client.Attachment` | `ExtractAsync()` | `POST /api/pdf/attachments/extract` | ✅ Verfügbar | Anhänge extrahieren als `Dictionary<string, Stream>` |
|
||
| `Client.Attachment` | `AddAsync()` | `POST /api/pdf/attachments/add` | ⏳ Geplant | Anhänge in PDF/A-3 einbetten |
|
||
| `Client.Operations` | `MergeAsync()` | `POST /api/pdf/operations/merge` | ✅ Verfügbar | Mehrere PDFs zusammenführen |
|
||
| `Client.Operations` | `AnnotateAsync()` | `POST /api/pdf/operations/annotate` | ✅ Verfügbar | Annotationen hinzufügen |
|
||
| `Client.Operations` | `StampAsync()` | `POST /api/pdf/operations/stamp` | ✅ Verfügbar | Text-/Bildstempel hinzufügen |
|
||
| `Client.SwissQrCode` | `ExtractAsync()` | `POST /api/pdf/qr-code/extract-swiss` | ✅ Verfügbar | Schweizer QR-Code-Daten extrahieren |
|
||
| `Client.Zugferd` | `CheckAsync()` | `POST /api/pdf/zugferd/has-zugferd` | ✅ Verfügbar | ZUGFeRD-Einbettung prüfen |
|
||
| `Client.Zugferd` | `ExtractAsync()` | `POST /api/pdf/zugferd/extract` | ✅ Verfügbar | ZUGFeRD-XML als Stream |
|
||
| `Client.Zugferd` | `ExtractAsResultAsync()` | `POST /api/pdf/zugferd/extract` | ✅ Verfügbar | ZUGFeRD-XML mit Metadaten als Objekt |
|
||
| `Client.Conversion` | `ToPdfAAsync()` | `POST /api/pdf/conversion/to-pdfa` | ⏳ Geplant | PDF zu PDF/A konvertieren |
|
||
| `Client.Conversion` | `FromPdfAAsync()` | `POST /api/pdf/conversion/from-pdfa` | ⏳ Geplant | PDF/A-Einschränkungen aufheben |
|
||
| `Client.Workflows` | `InspectSwissQrCodeAsync()` | — | ✅ Verfügbar | Validierung + Swiss QR Code Extraktion; gibt immer `SwissQrCodeResult` zurück, wirft nie |
|
||
|
||
> **⏳ Geplant:** Der Client-Code ist vorhanden und kompiliert fehlerfrei. Ein Aufruf zur Laufzeit wirft jedoch `NotImplementedException`, da der serverseitige Endpunkt noch nicht existiert. Die Methoden sind mit `[Obsolete]` markiert, sodass der Compiler eine Build-Warnung ausgibt.
|
||
|
||
---
|
||
|
||
## Fehlerbehandlung
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
try
|
||
{
|
||
using var stream = File.OpenRead("dokument.pdf");
|
||
var result = await Client.Validation.ValidateAsync(stream);
|
||
Console.WriteLine($"Seiten: {result.PageCount}");
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("not configured"))
|
||
{
|
||
// Client.Configure() wurde nicht aufgerufen
|
||
Console.WriteLine("Client ist nicht initialisiert.");
|
||
}
|
||
catch (HttpRequestException ex)
|
||
{
|
||
// Netzwerkfehler oder HTTP 4xx/5xx-Antwort vom Server
|
||
Console.WriteLine($"HTTP-Fehler: {ex.Message}");
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
// API hat null zurückgegeben (unerwartetes Serververhalten)
|
||
Console.WriteLine($"Unerwartete API-Antwort: {ex.Message}");
|
||
}
|
||
catch (NotImplementedException ex)
|
||
{
|
||
// Geplanter Endpunkt aufgerufen – noch nicht auf dem Server verfügbar
|
||
Console.WriteLine($"Endpunkt noch nicht implementiert: {ex.Message}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Try
|
||
Dim stream As New System.IO.FileStream("dokument.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.Validation.ValidateAsync(stream)
|
||
stream.Dispose()
|
||
Console.WriteLine($"Seiten: {result.PageCount}")
|
||
Catch ex As InvalidOperationException When ex.Message.Contains("not configured")
|
||
Console.WriteLine("Client ist nicht initialisiert.")
|
||
Catch ex As System.Net.Http.HttpRequestException
|
||
Console.WriteLine($"HTTP-Fehler: {ex.Message}")
|
||
Catch ex As InvalidOperationException
|
||
Console.WriteLine($"Unerwartete API-Antwort: {ex.Message}")
|
||
Catch ex As NotImplementedException
|
||
Console.WriteLine($"Endpunkt noch nicht implementiert: {ex.Message}")
|
||
End Try
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
try {
|
||
$stream = [System.IO.File]::OpenRead("dokument.pdf")
|
||
$result = [DocumentService.Client.Client]::Validation.ValidateAsync($stream).GetAwaiter().GetResult()
|
||
$stream.Dispose()
|
||
Write-Host "Seiten: $($result.PageCount)"
|
||
}
|
||
catch [System.InvalidOperationException] {
|
||
if ($_.Exception.Message -like "*not configured*") {
|
||
Write-Host "Client ist nicht initialisiert."
|
||
} else {
|
||
Write-Host "Unerwartete API-Antwort: $($_.Exception.Message)"
|
||
}
|
||
}
|
||
catch [System.Net.Http.HttpRequestException] {
|
||
Write-Host "HTTP-Fehler: $($_.Exception.Message)"
|
||
}
|
||
catch [System.NotImplementedException] {
|
||
Write-Host "Endpunkt noch nicht implementiert: $($_.Exception.Message)"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Lizenz
|
||
|
||
Copyright © 2026 Digital Data GmbH. Alle Rechte vorbehalten.
|