XML-zu-YAML-Konverter
Wandelt nach YAML, mit den heiklen Werten in Anführungszeichen.
Alles läuft in diesem Tab. Nichts, was Sie einfügen, wird hochgeladen, protokolliert oder irgendwohin gesendet. Öffnen Sie Ihr Netzwerkpanel und prüfen Sie es.
Fügen Sie oben XML ein, und daneben erscheint YAML. Das Dokument wird auf Wohlgeformtheit geprüft, auf einen Baum abgebildet und dann von einem Emitter ausgeschrieben, dessen Hauptaufgabe darin besteht zu entscheiden, welche Werte in Anführungszeichen müssen. Es wird nichts hochgeladen: Scanner, Abbildung und Emitter laufen alle in diesem Tab.
Der übliche Grund, das zu wollen, ist, dass eine Konfigurationsdatei, ein Kubernetes-Manifest, eine CI-Pipeline oder ein Ansible-Inventar Daten braucht, die derzeit in XML liegen. Die Ausgabe geht in eine Datei, die eine Maschine wörtlich liest, und darum zählen die Anführungszeichen mehr als das Layout.
YAML sieht wie das freundliche Format aus und ist das, das Ihre Daten am ehesten still verändert. Ein Ländercode NO ohne Anführungszeichen wird in einem Großteil des Ökosystems der Boolean false. Eine Postleitzahl 01730 wird 1730. Eine Version 1.10 wird 1.1. Dieser Emitter setzt die Werte in Anführungszeichen, die sonst falsch gelesen würden, und diese Seite sagt genau, welche und warum.
Die Abbildung ist die von XML nach JSON
YAML 1.2 wurde als Obermenge von JSON entworfen, es gibt hier also keinen eigenen Baum. Das XML wird in dieselbe Struktur umgewandelt, die die Seite XML nach JSON erzeugt, und ein anderer Serialisierer schreibt sie aus. Jede Abbildungsentscheidung jener Seite gilt unverändert: Attribute werden Schlüssel mit Präfix, Text, der ein Element mit Attributen oder Kindern teilt, landet unter einem Textschlüssel, ein zweimal auftretendes Element wird eine Sequenz, und Kommentare fallen weg.
Eines wird schlimmer. In JSON sieht ein Verbraucher wenigstens Klammern; in YAML ist der Unterschied zwischen einem Eintrag und zwei ein eingerückter Skalar gegenüber einer Liste von Strichen, und das bemerkt niemand in einem Diff. Nehmen Sie das Feld „immer ein Array“ für alles, was begrifflich eine Liste ist, damit ein Dokument mit einem Eintrag und eines mit fünfzig dieselbe Gestalt erzeugen.
<order id="00042">
<total currency="GBP">19.90</total>
<line sku="0071">Widget</line>
<line sku="0072">Gasket</line>
<country>NO</country>
</order>
order:
attr_id: '00042'
total:
attr_currency: GBP
text: '19.90'
line:
- attr_sku: '0071'
text: Widget
- attr_sku: '0072'
text: Gasket
country: 'NO'Das Norwegen-Problem und die genaue Liste, die es abdeckt
YAML 1.1 definiert seinen Boolean-Typ durch Aufzählung, und die Aufzählung ist breiter, als irgendwer erwartet. Die veröffentlichte Typseite listet wörtlich: y, Y, yes, Yes, YES, n, N, no, No, NO, true, True, TRUE, false, False, FALSE, on, On, ON, off, Off, OFF. Jedes davon lädt ohne Anführungszeichen als Boolean.
Die Folge hat einen Namen. Ein Datenbestand mit ISO-Ländercodes bekommt NO für Norwegen, und der Parser reicht der Anwendung false. Dieselbe Liste verschlingt eine Ja/Nein-Spalte aus einer Tabelle und jeden Schalter, der on oder off heißt und Text sein sollte. YAML 1.2 hat das Kernschema auf nur true und false verengt, aber PyYAML, Rubys Psych, Ansible und ein großer Teil des Kubernetes-Werkzeugs lösen weiterhin die 1.1-Menge auf, gehen Sie also davon aus, dass sie ganz lebendig ist.
Der Emitter setzt jeden Skalar, der genau auf diese Liste passt, in einfache Anführungszeichen, die einbuchstabigen Formen eingeschlossen, dazu null, Null, NULL und die Tilde. Achten Sie auf die Groß- und Kleinschreibung: yES und nO stehen nicht in der 1.1-Liste und werden nicht gequotet, denn kein konformer Parser liest sie ebenfalls als Boolean.
Was sonst gequotet wird, und was durchrutscht
Die Boolean-Menge ist der berühmte Fall, nicht der häufige. Die meisten Werte, die brechen, sind Zahlen, die nie Zahlen waren, denn YAML leitet einen Typ aus der Schreibung eines einfachen Skalars ab, genau wie JSON das nicht tut. Ein Skalar wird in einfache Anführungszeichen gesetzt, wenn er auf die Boolean- oder Null-Menge passt, wenn er auf eine JSON-Zahlengrammatik passt (das deckt 42, 19.90 und 1.10 ab), wenn er eine führende Null mit weiteren Ziffern hat, wenn er leer ist, wenn er mit einem YAML-Indikatorzeichen wie einem Bindestrich oder einer Raute beginnt, oder wenn an einem Ende Leerraum steht.
Mehrzeiliger Text wird nicht gequotet. Er wird ein literaler Blockskalar, eingeleitet von einem senkrechten Strich mit Strip-Indikator. Literal wird absichtlich dem gefalteten vorgezogen: Ein gefalteter Block fließt einzelne Zeilenumbrüche zu Leerzeichen um und zerstört damit eingebetteten Code und Adressen. Der Strip-Indikator entfernt den abschließenden Zeilenumbruch, den ein Block sonst hinzufügen würde.
Manche Werte verlassen den Emitter dennoch ohne Anführungszeichen und können nachgelagert den Typ wechseln. Sie werden aufgeführt statt beschönigt, denn kein Emitter, der einfache Skalare benutzt, hat die Typermittlung von YAML gelöst:
- Sexagesimale Zahlen. YAML 1.1 liest 22:22 als Ganzzahl zur Basis 60, eine Dauer wird in PyYAML also 1342. Ein 1.2-Parser wie js-yaml liefert die Zeichenkette, das hängt also davon ab, welche Seite die Datei liest.
- Hexadezimale Schreibungen. 0x1F lädt sowohl in YAML 1.1 als auch im 1.2-Kernschema als 31, ein hexadezimaler Farbcode braucht also Anführungszeichen.
- Datumsangaben. 2024-01-05 passt auf den Timestamp-Typ von YAML, js-yaml und PyYAML reichen Ihnen also beide ein Datumsobjekt statt einer Zeichenkette.
- Blockskalare, deren erste Zeile weiter eingerückt ist als die folgenden, was passiert, wenn ein CDATA-Abschnitt führende Leerzeichen bewahrt. YAMLs Lösung ist ein ausdrücklicher Einrückungsindikator nach dem senkrechten Strich, den dieser Emitter nicht schreibt.
Setzen Sie Attributpräfix und Textschlüssel, bevor Sie umwandeln
Das ist die eine Vorbereitung, die sich lohnt. Die Voreinstellungen wurden für JSON gewählt, wo sie sicher sind, und YAML hat für Schlüssel eine strengere Grammatik als für Werte.
Das Standard-Attributpräfix ist @_ und der Standard-Textschlüssel ist #text. In YAML ist @ ein reservierter Indikator, mit dem ein einfacher Skalar nicht beginnen darf, ein Schlüssel @_id macht das Dokument also unparsbar: js-yaml meldet „bad indentation of a mapping entry“ und PyYAML meldet ein Zeichen, das kein Token beginnen kann. Eine führende # ist schlimmer, weil sie nicht scheitert. Eine Zeile mit #text: 19.90 ist ein Kommentar, die Datei lädt also, und der Wert ist einfach nicht da.
Beide Felder sitzen in der Steuerzeile über dem Editor. Setzen Sie das Präfix auf etwas Schlichtes wie attr_ und den Textschlüssel auf text, und jeder Schlüssel in der Ausgabe ist ein gewöhnlicher YAML-Name. Schlüssel, die aus anderen Gründen Anführungszeichen brauchen, etwa soap:Body, werden automatisch gequotet, denn ein Doppelpunkt ist in einem nackten Schlüssel nicht zulässig.
Das im Code tun
Zwei Schritte: das XML sicher parsen, dann mit einem Dumper serialisieren, dem Sie gesagt haben, richtig zu quoten. Die XML-Hälfte braucht die üblichen Entity-Flags, denn die Voreinstellungen in Java und .NET lösen ein DOCTYPE auf. Die YAML-Hälfte braucht Aufmerksamkeit, weil Dumper sich darin unterscheiden, wie entschlossen sie quoten.
import { XMLParser } from 'fast-xml-parser';
import yaml from 'js-yaml';
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: 'attr_', // not @_: YAML reserves a leading @
textNodeName: 'text', // not #text: a leading # is a comment
parseTagValue: false, // keep values as strings
parseAttributeValue: false,
processEntities: false, // do not expand DOCTYPE-declared entities
isArray: (name) => ['line', 'item', 'entry'].includes(name),
});
const out = yaml.dump(parser.parse(xmlSource), {
lineWidth: -1, // never fold long lines; folding rewrites your data
noRefs: true, // never emit anchors and aliases
quotingType: "'",
sortKeys: false,
});
// js-yaml's dumper is conservative: it quotes NO, 01730, 1.10, 22:22 and
// 0x1F on its own, and quotes keys that begin with @ or #. Add
// forceQuotes: true if you want every string quoted regardless.import xmltodict
import yaml
doc = xmltodict.parse(
xml_source,
disable_entities=True, # blocks the expat entity attacks
attr_prefix='attr_',
cdata_key='text',
force_list=('line', 'item', 'entry'),
)
print(yaml.safe_dump(
doc,
default_flow_style=False,
allow_unicode=True,
sort_keys=False,
width=10 ** 9, # effectively disable line folding
))
# PyYAML implements the YAML 1.1 resolver, so its dumper knows that NO,
# 01730 and 1.10 would load back as a bool, an int and a float, and quotes
# them. Use safe_dump, never dump: the full dumper emits Python-specific
# tags that only yaml.unsafe_load can read back.import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import javax.xml.stream.XMLInputFactory;
XMLInputFactory input = XMLInputFactory.newFactory();
input.setProperty(XMLInputFactory.SUPPORT_DTD, false);
input.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
JsonNode tree = new XmlMapper(new XmlFactory(input)).readTree(xmlSource);
YAMLMapper yaml = YAMLMapper.builder()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
.disable(YAMLGenerator.Feature.MINIMIZE_QUOTES) // off is the safe state
.enable(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE)
.build();
String out = yaml.writeValueAsString(tree);
// MINIMIZE_QUOTES is the setting to leave alone. It is off by default, and
// turning it on is how a value of NO ends up unquoted in a Jackson-generated
// file that a Python service then reads as false.using System.Xml;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersFromEntities = 1024 * 1024,
};
using var reader = XmlReader.Create(new StringReader(xmlSource), settings);
var document = new XmlDocument { XmlResolver = null };
document.Load(reader);
string json = JsonConvert.SerializeXmlNode(document);
object? tree = JsonConvert.DeserializeObject<JObject>(json)?.ToObject<object>();
var serialiser = new SerializerBuilder()
.WithDefaultScalarStyle(ScalarStyle.SingleQuoted) // quote everything
.Build();
Console.Write(serialiser.Serialize(tree));
// WithDefaultScalarStyle is blunt: every scalar comes out quoted, including
// the ones that did not need it. That is the right trade for generated data.
// Drop it only if you are hand-checking the output.# yq v4 (Mike Farah) converts directly and quotes ambiguous scalars.
yq -p=xml -o=yaml '.' document.xml
# Match the key convention used on this page:
yq -p=xml -o=yaml \
--xml-attribute-prefix='attr_' \
--xml-content-name='text' \
'.' document.xml > out.yaml
# Then load it back with the parser that will actually consume it. This is
# the only check that proves nothing changed type on the way through:
python -c "import yaml; print(yaml.safe_load(open('out.yaml'))['order']['country'])"
# expect: NO not: FalseDer Fehler, um den es auf dieser Seite geht, ist stumm. Eine YAML-Datei mit einem ungequoteten NO parst sauber, validiert sauber und wird sauber ausgerollt; das Land ist von da an einfach false. Die Prüfung, die das fängt, ist, die erzeugte Datei mit derselben Bibliothek zurückzuladen, die der Abnehmer benutzt, und einen bekannt heiklen Wert zu vergleichen – nicht das Diff zu lesen.
Häufige Fragen
Wird mein XML hochgeladen, wenn ich es nach YAML umwandle?
Nein. XML-Scanner, Baumabbildung und YAML-Emitter sind alle JavaScript in diesem Tab, und es gibt keinen Endpunkt, an den sie senden könnten. Öffnen Sie den Netzwerk-Tab Ihrer Entwicklerwerkzeuge, fügen Sie ein Dokument ein und sehen Sie zu, wie nichts passiert.
Das lohnt sich zu bestätigen statt anzunehmen, denn nach YAML umgewandeltes XML ist sehr oft Konfiguration. Verbindungszeichenfolgen, Dienstkonten, API-Schlüssel und interne Hostnamen landen alle in der Art von Dokument, die Leute zu einem Konverter bringen.
Was ist das Norwegen-Problem?
YAML 1.1 definiert seinen Boolean-Typ als feste Liste von Schreibungen, und diese Liste enthält n, N, no, No und NO. Ein Feld mit dem ISO-Code für Norwegen, ohne Anführungszeichen geschrieben, lädt also als false. Dieselbe Liste verschlingt y und Y, on und off, und jede Ja/Nein-Spalte aus einer Tabelle.
YAML 1.2 hat das Kernschema auf nur true und false verengt, was das Ökosystem nicht geheilt hat: PyYAML, Psych, Ansible und ein großer Teil des Kubernetes-Werkzeugs lösen weiterhin die 1.1-Menge auf, und Sie bestimmen selten, welcher Parser Ihre Datei liest. Der Emitter quotet jede Schreibung in dieser Liste, NO bleibt also die Zeichenkette NO.
Warum stehen manche Werte in Anführungszeichen und andere nicht?
Weil die Anführungszeichen tragend sind. Ein einfacher YAML-Skalar bekommt seinen Typ daraus, wie er geschrieben ist, also ist 01730 eine Zahl, 1.10 ein Float, NO ein Boolean, und ein führender Bindestrich beginnt einen Listeneintrag. Quoten ist die Art zu sagen, dass es Text ist.
Der Emitter quotet genau die Werte, die sonst Typ oder Bedeutung wechseln würden, und lässt alles andere schlicht, denn jeden Skalar zu quoten macht eine Datei ohne Nutzen schwerer zu lesen und zu vergleichen. Für gleichmäßiges Quoten haben die meisten YAML-Bibliotheken eine Option, Anführungszeichen zu erzwingen; die Beispiele oben zeigen sie für js-yaml und YamlDotNet.
Was passiert mit mehrzeiligem Textinhalt?
Er wird ein literaler Blockskalar, eingeleitet von einem senkrechten Strich mit Strip-Indikator, mit den Zeilen darunter eingerückt. Literal wurde absichtlich dem gefalteten vorgezogen: Ein gefalteter Block fließt einzelne Zeilenumbrüche zu Leerzeichen um und zerstört dabei still eingebetteten Code und Adressen.
Ein Fall, auf den Sie achten sollten. Ist die erste Zeile des Textes weiter eingerückt als die folgenden, was passiert, wenn ein CDATA-Abschnitt führende Leerzeichen bewahrt, ist der Block mehrdeutig und ein Parser weist ihn zurück.
Werden wiederholte Elemente zu YAML-Listen?
Ja. Ein Element, das unter demselben Eltern-Element mehr als einmal auftritt, wird eine Sequenz, geschrieben als Liste von Strichen; eines, das einmal auftritt, wird eine schlichte verschachtelte Abbildung oder ein Skalar. Das ist dieselbe Singleton-Mehrdeutigkeit, die die Seite XML nach JSON beschreibt, und sie ist hier gefährlicher, weil YAML sie versteckt: Der Unterschied zwischen einem Eintrag und zwei ist ein Strich und zwei Leerzeichen Einrückung.
Nehmen Sie das Feld „immer ein Array“ über dem Editor. Nennen Sie die Elemente, die begrifflich Listen sind, und sie werden als Sequenzen ausgegeben, ob das Dokument eines davon hält oder vierzig.
Werden XML-Kommentare und Namensräume erhalten?
Kommentare nicht. Sie fallen weg, wenn das Dokument auf einen Baum abgebildet wird, bevor der Emitter überhaupt etwas sieht. YAML-Kommentare sind nicht Teil des Datenmodells, einer, der in die Ausgabe geschrieben würde, verschwände also beim ersten Mal, wenn jemand die Datei lädt und neu speichert.
Namensraumpräfixe bleiben wortwörtlich, soap:Body wird also ein Schlüssel, der soap:Body heißt, automatisch gequotet, weil ein Doppelpunkt in einem nackten YAML-Schlüssel nicht zulässig ist. „Namensraumpräfixe entfernen“ anzukreuzen ergibt stattdessen ein schlichtes Body, auf die Gefahr hin, zwei Namensräume auf einem Schlüssel zu verschmelzen.