XML und Webservices¶
Viele Fremdsysteme tauschen Daten als XML aus, ältere als SOAP-Dienst, neuere als REST-Dienst mit JSON. In KCForms hilft dir dabei:
xml— XML lesen, durchsuchen und selbst bauen,ws.rest— REST-Dienste aufrufen,ws.soap— SOAP-Dienste aufrufen.
XML von Grund auf — Aufbau, Suchen, Ändern, Bauen, Namensräume und die
Umsteigertabelle für E4X — erklärt das Kapitel
XML verstehen und verarbeiten.
Ausführliches zu den Diensten: ws — WebService-Client.
Für einfache HTTP-Aufrufe (Download, Upload, Cookies) gibt es zusätzlich den
http-Client.
Zum Üben: die eingebauten Echo-Dienste
Die Beispiele rufen zwei Dienste auf, die KCForms selbst mitbringt. Sie schicken einfach zurück, was sie bekommen:
http://localhost:8099/api/demo/ws-echo— REST, antwortet mit JSONhttp://localhost:8099/api/demo/ws-soap— SOAP, spiegelt das gesendete Element
localhost ist ein internes Ziel. Damit die Aufrufe erlaubt sind, muss
localhost in der Allowlist stehen (siehe
Sicherheit) — auf
Testsystemen üblich.
Einsteiger¶
1. XML-Text einlesen¶
xml.parse macht aus einem Text einen Knoten, in dem du dich bewegen
kannst. name ist der Name des Elements.
var doc = xml.parse("<kunde><name>Meyer</name><ort>Hagen</ort></kunde>");
log.info("Wurzel: " + doc.name);
2. Den Text eines Elements lesen¶
find("ort") sucht ein direktes Kind-Element mit diesem Namen.
var doc = xml.parse("<kunde><name>Meyer</name><ort>Hagen</ort></kunde>");
log.info("Ort: " + doc.find("ort").text);
3. Ein Attribut lesen¶
Attribute sind die name="wert"-Angaben im Start-Tag.
var doc = xml.parse("<kunde nr='K-1001' aktiv='ja'><name>Meyer</name></kunde>");
log.info("Kundennummer: " + doc.attr("nr"));
log.info("Aktiv: " + doc.attr("aktiv"));
Gibt es das Attribut nicht, liefert attr den Wert null.
4. Alle gleichnamigen Elemente durchlaufen¶
var doc = xml.parse(
"<bestellung>" +
" <artikel menge='10'>Stift</artikel>" +
" <artikel menge='2'>Block</artikel>" +
"</bestellung>");
doc.findAll("artikel").forEach(function (a) {
log.info(a.text + ": " + a.attr("menge"));
});
5. Tief verschachtelte Elemente finden¶
find schaut nur eine Ebene tief. findDeep durchsucht alles darunter.
var doc = xml.parse(
"<kunde><adresse><strasse>Hauptstr. 1</strasse><plz>58095</plz></adresse></kunde>");
log.info("PLZ: " + doc.findDeep("plz").text);
6. Selbst XML bauen¶
xml.create legt ein Element an, addChild hängt ein Kind-Element an.
var kunde = xml.create("kunde");
kunde.addChild("name", "Meyer");
kunde.addChild("ort", "Hagen");
log.info(xml.stringify(kunde));
7. Attribute setzen und schön eingerückt ausgeben¶
xml.stringify(knoten, true) rückt das XML ein — gut lesbar für Menschen.
var kunde = xml.create("kunde");
kunde.attr("nr", "K-1001");
kunde.addChild("name", "Meyer");
log.info(xml.stringify(kunde, true));
8. Sonderzeichen erledigen sich von selbst¶
Zeichen wie & und < haben in XML eine besondere Bedeutung. Mit
xml.create werden sie automatisch richtig umgewandelt. Baust du XML
dagegen als Text zusammen, musst du das selbst tun.
var firma = xml.create("firma", "Müller & Söhne <GmbH>");
log.info(xml.stringify(firma));
// Beim Zurücklesen kommt der Originaltext wieder heraus:
log.info(xml.parse(xml.stringify(firma)).text);
Fortgeschritten¶
9. Formularwerte als XML¶
var antrag = xml.create("antrag");
antrag.attr("datum", new Date().format("yyyy-MM-dd"));
antrag.addChild("vorname", ctx.form.vorname);
antrag.addChild("name", ctx.form.name);
antrag.addChild("email", ctx.form.email);
antrag.addChild("betrag", String(ctx.form.betrag));
log.info(xml.stringify(antrag, true));
10. Eine Tabelle als XML-Liste¶
Jede Zeile einer Detail-Tabelle wird ein eigenes Element.
var bestellung = xml.create("bestellung");
var liste = bestellung.addChild("positionen");
var posten = ctx.form.positionen || [];
// Indizierte Schleife: das Attribut nr braucht den Index, und eine Liste AUS
// DEM FORMULAR gibt ihn per forEach nicht her.
for (var i = 0; i < posten.length; i++) {
var pos = liste.addChild("position");
pos.attr("nr", String(i + 1));
pos.addChild("artikel", posten[i].artikel);
pos.addChild("menge", String(posten[i].menge));
}
log.info(liste.children.length + " Positionen");
log.info(xml.stringify(bestellung, true));
11. XML in Formularfelder übernehmen¶
Ein Fremdsystem liefert Kundendaten als XML — du verteilst sie auf die Felder.
var doc = xml.parse(
"<kunde nr='K-1001'><vorname>Anna</vorname><name>Meyer</name>" +
"<kontakt><mail>anna@beispiel.example</mail></kontakt><ort>Hagen</ort></kunde>");
ctx.form.kundennr = doc.attr("nr");
ctx.form.vorname = doc.find("vorname").text;
ctx.form.name = doc.find("name").text;
ctx.form.email = doc.findDeep("mail").text;
ctx.form.land = "DE";
log.info(ctx.form.vorname + " " + ctx.form.name + ", " + doc.find("ort").text);
12. Fehlende Elemente sicher behandeln¶
Gibt es ein Element nicht, liefert find den Wert null. Ohne Prüfung
bricht das Skript mit „Cannot read property 'text' of null" ab.
var doc = xml.parse("<kunde><name>Meyer</name></kunde>");
function text(knoten, tag, ersatz) {
var k = knoten.find(tag);
return k ? k.text : ersatz;
}
log.info("Name: " + text(doc, "name", "?"));
log.info("Telefon: " + text(doc, "telefon", "(keine Angabe)"));
13. Elemente ändern und entfernen¶
text kannst du auch setzen. removeChild entfernt ein Kind-Element.
var doc = xml.parse("<artikel><name>Stift</name><preis>9.90</preis><intern>A-7</intern></artikel>");
doc.find("preis").text = "12.50"; // Preis ändern
doc.removeChild(doc.find("intern")); // interne Angabe entfernen
doc.attr("geaendert", "ja");
log.info(xml.stringify(doc));
14. Kaputtes XML abfangen¶
Ist der Text kein gültiges XML, wirft xml.parse einen Fehler.
try {
xml.parse("<kunde><name>Meyer</kunde>");
} catch (e) {
log.info("Kein gültiges XML: " + e.message);
}
15. XML-Datei speichern und wieder einlesen¶
var kunde = xml.create("kunde");
kunde.addChild("name", "Meyer");
fs["/beispiele/xml/kunde.xml"].writeText(xml.stringify(kunde, true));
var wieder = xml.parse(fs["/beispiele/xml/kunde.xml"].readText());
log.info("Gelesen: " + wieder.find("name").text);
fs["/beispiele/xml"].remove(true);
16. Einen REST-Dienst abfragen¶
ws.rest.get liefert eine Antwort mit status, ok und text. json()
macht aus dem Text ein Objekt.
var r = ws.rest.get("http://localhost:8099/api/demo/ws-echo");
if (r.ok) {
log.info("Status " + r.status + ", Dienst: " + r.json().service);
}
17. Daten an einen REST-Dienst schicken¶
Ein JavaScript-Objekt geht automatisch als JSON hinaus.
var r = ws.rest.post("http://localhost:8099/api/demo/ws-echo", {
kunde: ctx.form.name,
betrag: ctx.form.betrag
});
var echo = r.json();
log.info("angekommen: " + echo.body.kunde + " / " + echo.body.betrag);
18. HTTP-Fehler erkennen¶
Antwortet der Dienst mit einem Fehlercode (z. B. 404 „nicht gefunden"),
gibt es keine Ausnahme — du prüfst r.ok.
var r = ws.rest.get("http://localhost:8099/api/demo/http/status/404");
if (!r.ok) {
log.info("Fehler " + r.status + " – bitte später erneut versuchen");
}
Profi¶
19. Suchparameter, eigene Header und Token¶
Mit ws.rest.request gibst du alles in einem Objekt an. Umlaute und
Leerzeichen in query werden automatisch richtig kodiert.
var r = ws.rest.request({
method: "GET",
url: "http://localhost:8099/api/demo/ws-echo",
query: { suche: "Müller & Co", seite: 1 },
headers: { "X-Mandant": "4711" },
auth: "bearer",
token: "mein-zugangs-token",
timeout: 5000
});
var echo = r.json();
log.info("Suche nach: " + echo.query.suche);
log.info("Anmeldung: " + echo.headers.authorization); // vom Echo maskiert
20. Einen SOAP-Dienst aufrufen¶
Du lieferst nur den Inhalt, den SOAP-Umschlag („Envelope") baut KCForms selbst. Die Antwort ist wieder ein XML-Knoten.
var antwort = ws.soap.sendReceive(
"http://localhost:8099/api/demo/ws-soap",
"urn:Echo", // die SOAPAction
"<Ping xmlns='urn:demo'><wert>test</wert></Ping>"
);
log.info("Antwort-Element: " + antwort.name);
log.info("Wert zurück: " + antwort.findDeep("wert").text);
21. Die SOAP-Anfrage mit xml.create bauen¶
Sicherer als Text-Stückelei: Sonderzeichen in Eingaben können das XML nicht kaputtmachen.
var anfrage = xml.create("KundeSuchen");
anfrage.attr("xmlns", "urn:demo");
anfrage.addChild("name", ctx.form.name);
var antwort = ws.soap.sendReceive("http://localhost:8099/api/demo/ws-soap", "urn:KundeSuchen", anfrage);
log.info("zurück: " + antwort.findDeep("name").text);
22. Elemente mit Präfix¶
In SOAP-Antworten tragen Elemente oft ein Präfix wie tns:. Beim Suchen
schreibst du es genauso mit.
var antwort = ws.soap.sendReceive("http://localhost:8099/api/demo/ws-soap", "urn:Echo", "<Hallo/>");
log.info("Antwort heißt: " + antwort.name); // tns:echoResponse
log.info("Dienst: " + antwort.find("tns:service").text);
23. SOAP 1.2 und fehlgeschlagene Aufrufe¶
SOAP gibt es in zwei Versionen; die Voreinstellung ist 1.1. Anders als bei
REST wirft ein SOAP-Fehler (etwa ein Fault oder eine Antwort, die gar
kein SOAP ist) eine Ausnahme — deshalb gehört try … catch dazu.
var v12 = ws.soap.sendReceive("http://localhost:8099/api/demo/ws-soap", "urn:Echo",
"<Ping><wert>1.2</wert></Ping>", { soapVersion: "1.2" });
log.info("SOAP 1.2: " + v12.findDeep("wert").text);
try {
// Der REST-Echo-Dienst versteht kein SOAP → Fehler
ws.soap.sendReceive("http://localhost:8099/api/demo/ws-echo", "urn:Echo", "<Ping/>");
} catch (e) {
log.info("Aufruf fehlgeschlagen: " + e.message);
}
24. Über einen benannten Konnektor¶
Adresse und Zugangsdaten pflegt der Admin zentral als Konnektor. Im Skript steht nur noch der Name — ändert sich die Adresse, bleibt das Skript gleich.
var dienst = ws.connect("demo-echo"); // Basis-URL: …/api/demo
var r = dienst.get("/ws-echo?quelle=konnektor");
log.info("über Konnektor: " + r.json().method + " " + r.json().query.quelle);
25. Abschicken, ohne auf die Antwort zu warten¶
Für Benachrichtigungen, deren Antwort dich nicht interessiert. Das Formular muss so nicht auf den Dienst warten.
ws.rest.fireAndForget({
method: "POST",
url: "http://localhost:8099/api/demo/ws-echo",
body: { ereignis: "antrag-eingegangen", zeit: new Date().toISOString() }
});
log.info("Benachrichtigung abgeschickt");
26. Alles zusammen: Kunde prüfen und Ergebnis anzeigen¶
Ein vollständiger Ablauf, wie er in einem Button-Skript stehen könnte: Eingabe prüfen, Dienst fragen, Antwort auswerten, Fehler freundlich melden.
var nr = String(ctx.form.kundennr || "").trim();
if (!/^K-\d{4}$/.test(nr)) {
ctx.addError("kundennr", "Bitte eine Kundennummer wie K-1234 eingeben.");
} else {
try {
var anfrage = xml.create("KundePruefen");
anfrage.addChild("nummer", nr);
var antwort = ws.soap.sendReceive("http://localhost:8099/api/demo/ws-soap", "urn:KundePruefen", anfrage);
var zurueck = antwort.findDeep("nummer");
ctx.form.ergebnis = zurueck
? "Kunde " + zurueck.text + " geprüft"
: "Keine Antwort vom Dienst";
} catch (e) {
ctx.form.ergebnis = "Dienst nicht erreichbar";
log.warn("Kundenprüfung fehlgeschlagen: " + e.message);
}
}
log.info("Ergebnis: " + ctx.form.ergebnis);