PDFs zusammenführen

Es gibt viele Wege, mehrere Dokumente in einem PDF zusammenzuführen. Eine davon ist im Kontextmenü des enaio®-Clients direkt und praktisch für alle User verfügbar.

In einem Kundenprojekt der letzten Tage gab es die Herausforderung, die Ausgabe mit Scripting-Mitteln des Rich-Clients zu beeinflussen. Dies kann z. B. sinnvoll sein, wenn die Reihenfolge angepasst werden soll, bestimmte Dokumente nicht oder anders exportiert werden soll etc.

Hier für spätere Referenz und interessierte kurz notiert, wie wir dies umgesetzt haben:

Der RichClient von enaio® unterstützt externe Anwendungen. Um hier keine kompilierte Anwendung hinterlegen zu müssen, ist diese komplett in VBScript notiert und nutzt die freundliche REST-API von enaio®.

Natürlich gäbe es hier viele weitere Lösungswege. An anderer Stelle haben wir beispielsweise spezielle Converter-Services eingebunden oder Export-APIs als Java- oder Python-Service entwickelt. Es kommt einfach immer darauf an, wo und in mit welchen Schwerpunkten eine Erweiterung zum Einsatz kommt. Und natürlich wäre es besonders schön, wenn dieser Kunde auch noch die vielen Vorteile von Embedded Documents for enaio® kennenlernt. :wink:

Konfiguration der externen Anwendung

  • Die externe Anwendung muss ohne Warten im Client konfiguriert werden, da sonst Zeile 6 CreateObject("Optimal_AS.Application") auf sich selbst wartet.
  • Die externe Anwendung muss bei einem 32-bit-Client die WScript.exe im SysWOW64-Verzeichnis nutzen.
    Konfiguration der externen Anwendung

Das Script

Option Explicit
Dim ArgObj: Set ArgObj = WScript.Arguments
Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
Dim WShell: Set WShell = CreateObject("Wscript.Shell")
Dim AX: Set AX = CreateObject("Optimal_AS.Application")
Dim DrtSession: Set DrtSession = AX.GetDrtSession()

Main(): Function Main()
	Dim IniFilePath: IniFilePath = ArgObj(0)
	Dim osParamFile: Set osParamFile = FSO.OpenTextFile(IniFilePath)
	Dim content: content = osParamFile.ReadAll
	osParamFile.Close
	FSO.DeleteFile IniFilePath, True
    Dim DocIDs: DocIDs = Split(content, vbCrLf)

	If UBound(DocIDs) = 0 Then
		MsgBox "Es wurde nur ein Dokument zur Zusamemnfassung übergeben.", 48, "Dokumente Zusammenfassen"
		Exit Function
	End If

    ' TODO: Hier können nun weitere Filter, Verbesserungen/Anpassungen an der übergebenen Dokumentliste implementiert werden.

    Dim i: For i = 0 To UBound(DocIDs)
        DocIDs(i) = Split(DocIDs(i), ",")(0)
    Next

	Dim result: Set result = SendMergeRequest(DocIDs)

	If result.status <> 200 Then
		MsgBox "Die Dokumente konnten nicht zusammgefasst werden:" & vbCrLf & vbCrLf & "Fehler " & result.status & " " & result.ResponseText, 48, "Dokumente Zusammenfassen"
		Exit Function
	End If

	Dim stream: Set stream = CreateObject("Adodb.Stream")
	Dim tempFilepath: tempFilepath = FSO.GetSpecialFolder(2) & "\" & fso.GetTempName & ".pdf"

	With stream
		.Type = 1
		.Open
		.Write result.responseBody
		.SaveToFile tempFilepath, 2
	End With
	WShell.Run tempFilepath
End Function 

Function ReadINI(FileContent, Section, Key) 'As String
	Dim RE: Set RE = New RegExp: RE.Pattern = "\[" & Section & "\][^\[\]]*\r\n" & Key & "=(.*)\r\n"
	Dim MC: Set MC = RE.Execute(FileContent)
	If MC.Count > 0 Then ReadINI = MC(0).SubMatches(0)
End Function

Function SendMergeRequest(DocIds)
	Dim o: Set o = CreateObject("MSXML2.XMLHTTP")
    Dim url: url = Replace(GetRegValue("Services\Gateway\API") & "/osrest/api/documentfiles/pdf?sessionguid=" & DrtSession.SessionGuid, "//osrest", "/osrest")
    Dim body: body = "{""pdfname"": ""merge.pdf"", ""ids"": [ " + Join(DocIDs, ", ") + " ]}"
	o.Open "POST", url, False
    o.SetRequestHeader "Content-Type", "application/json"
	o.Send body
	Set SendMergeRequest = o
End Function

Function GetRegValue(byVal Name)
	Dim Job: Set Job = DrtSession.CreateJob("krn", "REGetRegValue")
	Job.AddInParam "Name", Name, 1
	Job.AddInParam "Flags", "0", 2
	Job.Execute
	GetRegValue = job.GetOutParamString("Value")
End Function
  • An der Stelle 'TODO können nun weitere Filter, Verbesserungen/Anpassungen an der übergebenen Dokumentliste oder was auch immer im spezifischen Fall gewünscht ist, implementiert werden.
  • Die Methode GetRegValue wird genutzt, um die für den Client (hoffentlich korrekt konfigurierte und erreichbare) API-URL direkt vom enaio-Server zu erfahren.
  • Als Session-Token wird der API das der laufenden Client-Session übergeben. Dadurch stellt enaio sicher, dass User nur Dokumente zum PDF zusammenfassen können, auf welche sie auch zugreifen dürfen.

Demo

Aufruf im Client

4 „Gefällt mir“

Das ist echt auch ein cooler Weg! Ich hab solche Anforderungen bisher immer über Python umgesetzt, was auch gut funktioniert hat, aber ich werde im Hinterkopf behalten, dass es auch so ginge.

Ja, mit einem eigenen (Python-) Backend kann man halt noch mehr machen, z. B. Inhaltsverzeichnisse generieren, Paginieren etc.

Hallo zusammen,
ich habe versucht diese spannenden Funktion bei uns zu implementieren.
Leider läuft es immer auf eine Fehlermeldung raus:

Habt ihr vielleicht einen Lösungsansatz für mich?

Danke und beste Grüße,
Christian

Hallo @cschulze ,

ich war über den Fehler etwas erstaunt, aber es sieht so aus, als ob der enaio RichClient in den neueren Versionen am Ende der Datei einen zusätzlichen Zeilenumbruch hinzugefügt hat. Um dieses Problem zu umgehen, habe ich den Code von @rk etwas umgebaut:

Option Explicit
Dim ArgObj: Set ArgObj = WScript.Arguments
Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
Dim WShell: Set WShell = CreateObject("Wscript.Shell")
Dim AX: Set AX = CreateObject("Optimal_AS.Application")
Dim DrtSession: Set DrtSession = AX.GetDrtSession()

Main(): Function Main()
	Dim DocIDs: DocIDs = Array()
	Dim IniFilePath: IniFilePath = ArgObj(0)
	Dim osParamFile: Set osParamFile = FSO.OpenTextFile(IniFilePath)
	
    ' TODO: Hier können nun weitere Filter, Verbesserungen/Anpassungen an der übergebenen Dokumentliste implementiert werden.
	Do Until osParamFile.AtEndOfStream
      Dim Line: Line = osParamFile.ReadLine
      If Line <> "" Then
		ReDim Preserve DocIDs(UBound(DocIDs) + 1)
		DocIDs(UBound(DocIDs)) = Split(Line, ",")(0)
	  End If
    Loop
	osParamFile.Close
	FSO.DeleteFile IniFilePath, True
	
	If UBound(DocIDs) = 0 Then
		MsgBox "Es wurde nur ein Dokument zur Zusamemnfassung übergeben.", 48, "Dokumente Zusammenfassen"
		Exit Function
	End If

	Dim result: Set result = SendMergeRequest(DocIDs)

	If result.status <> 200 Then
		MsgBox "Die Dokumente konnten nicht zusammgefasst werden:" & vbCrLf & vbCrLf & "Fehler " & result.status & " " & result.ResponseText, 48, "Dokumente Zusammenfassen"
		Exit Function
	End If

	Dim stream: Set stream = CreateObject("Adodb.Stream")
	Dim tempFilepath: tempFilepath = FSO.GetSpecialFolder(2) & "\" & fso.GetTempName & ".pdf"

	With stream
		.Type = 1
		.Open
		.Write result.responseBody
		.SaveToFile tempFilepath, 2
	End With
	WShell.Run tempFilepath
End Function 

Function ReadINI(FileContent, Section, Key) 'As String
	Dim RE: Set RE = New RegExp: RE.Pattern = "\[" & Section & "\][^\[\]]*\r\n" & Key & "=(.*)\r\n"
	Dim MC: Set MC = RE.Execute(FileContent)
	If MC.Count > 0 Then ReadINI = MC(0).SubMatches(0)
End Function

Function SendMergeRequest(DocIds)
	Dim o: Set o = CreateObject("MSXML2.XMLHTTP")
    Dim url: url = Replace(GetRegValue("Services\Gateway\API") & "/osrest/api/documentfiles/pdf?sessionguid=" & DrtSession.SessionGuid, "//osrest", "/osrest")
    Dim body: body = "{""pdfname"": ""merge.pdf"", ""ids"": [ " + Join(DocIDs, ", ") + " ]}"
	o.Open "POST", url, False
    o.SetRequestHeader "Content-Type", "application/json"
	o.Send body
	Set SendMergeRequest = o
End Function

Function GetRegValue(byVal Name)
	Dim Job: Set Job = DrtSession.CreateJob("krn", "REGetRegValue")
	Job.AddInParam "Name", Name, 1
	Job.AddInParam "Flags", "0", 2
	Job.Execute
	GetRegValue = job.GetOutParamString("Value")
End Function

In Zeile 24 muß im Schleifenkopf hinter UBound(DocIDs) ein -1 noch geschrieben werden.

Dim i: For i = 0 To UBound(DocIDs)-1

Ich hatte den gleichen Fehler.

VG

2 „Gefällt mir“

Hi @uw

besten Dank für die schnelle Anpassung.
Jetzt läuft es einwandfrei!

Danke und viele Grüße aus Heinsberg,
Christian

Sehr gerne.

Danke auch an @Warnower für den Hinweis. Wusste doch, dass wir das Thema schon mal hatten :).
Die neue Version ist etwas sauberer.

Der Workaround mit Dim i: For i = 0 To UBound(DocIDs)-1 löst nicht das Thema mit der folgenden Meldung:

	If UBound(DocIDs) = 0 Then
		MsgBox "Es wurde nur ein Dokument zur Zusamemnfassung übergeben.", 48, "Dokumente Zusammenfassen"
		Exit Function
	End If

Hallo zusammen,

auch wir haben probiert die Funktion bei uns zu hinterlegen, da diese Anforderung schon des Öfteren bei uns aufgetaucht ist. Beim Ausführen des Tools erhalten wir jedoch folgende Fehlermeldung:

Hat jemand eine Idee, wie dieser Fehler behoben werden kann?

Vielen Dank im Voraus!

Hallo @hesslinl, die Stelle, an der der Fehler auftritt, sieht für mich so aus, als würde dem Script selbst kein Parameter übergeben. Ist das %i hinter dem Script-Pfad im Feld Parameter angegeben?

image

(Sonst wäre ggf. ein Screenshot der Anwendungskonfiguration hilfreich.)

Hallo zusammen, die Lösung sieht sehr gut aus, nur leider bekomme ich folgenden Fehler. Hätte hier jemand eine Idee? Danke!

Hallo @seho, das sieht mir am ehesten nach einem Fehler mit MSXML aus: eventuell Konnektivität oder der Login in enaio.

Funktioniert denn dieser Testaufruf von MSXML?

Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP")
URL = "https://ecm.community"

objXMLHTTP.Open "GET", URL, False
objXMLHTTP.Send

If objXMLHTTP.Status = 200 Then
    ResponseText = objXMLHTTP.ResponseText
    MsgBox ResponseText, vbInformation, "Website Content"
Else
    MsgBox "Request failed with status: " & objXMLHTTP.Status, vbExclamation, "Connection Error"
End If

Set objXMLHTTP = Nothing

Danke für die schnelle Unterstützung @rk .
Das hat bei mir auch funktioniert. Auch als ich es als Anwendung aus Enaio heraus gestartet habe

Hallo @seho, dann muss es am enaio-Aufruf liegen, was für mich entweder auf die Anmeldung (sessionguid=...) oder einen Fehler im Web-Service hindeutet.

Ist im Gateway- oder OSRest-Log allenfalls passend zur Uhrzeit der Aufruf eine Fehler zu sehen?

Hallo @rk, also wir haben das jetzt etwas versucht, aber leider keine Lösung gefunden. In den Logs steht es nicht und auf anderen Systemen bekommen wir den gleichen Fehler

Als Nächstes würde ich versuchen, den Aufruf mit einem Tool wie Postman nachzustellen. Dazu müsste die URL sowie der Body wie in den ursprünglichen Zeilen 56 zusammengebaut werden und dann z. B. mittels…

InputBox "url", , url
InputBox "body", ,body

… geholt und in Postman übertragen werden. Wenn der Aufruf dort auch Fehler meldet, wüssten wir zuverlässig, dass es an enaio liegt und würden eventuell auch eine klarere Fehlermeldung erhalten als aus dem VBScript/MSXML heraus.

Moin @rk ,
wir haben es hin bekommen, mussten nur die URL für den Restzugriff hart mit angeben.

Vielen Dank für die Unterstützung!

1 „Gefällt mir“

Hallo @seho, danke für Deine Rückmeldung. Freut mich, wenn es so geht.

Wenn das nötig war, könnte die URL der Gateway-API im Enterprise-Manager falsch/ungünstig hinterlegt sein.