Convertitore da YAML a XML
Converte YAML in XML, interamente nel tuo browser.
Tutto viene eseguito in questa scheda. Nulla di ciò che incolli viene caricato, registrato o inviato da qualche parte. Apri il pannello di rete e verifica.
Incolla dello YAML qui sopra e accanto compare dell’XML indentato e ben formato, con una dichiarazione e ogni carattere speciale protetto. Se lo YAML non si analizza ottieni la prima riga di protesta del parser stesso anziché un riquadro vuoto, e se una chiave ha dovuto essere rinominata per diventare un nome di elemento XML lecito, lo strumento elenca che cosa è cambiato.
È la direzione che ti serve quando qualcosa di vecchio deve leggere qualcosa di nuovo: un’integrazione che accetta solo XML, un endpoint SOAP, un formato di scambio convalidato con XSD, una fixture per un servizio configurato in YAML. Ha un numero sorprendente di spigoli, e quasi tutti vengono da YAML e non da XML.
L’analisi è fatta da js-yaml, caricato solo quando apri questa pagina e non su ogni pagina del sito. Tutto gira in questa scheda e nulla viene caricato altrove, cosa che conta perché lo YAML è dove vive la configurazione e la configurazione è dove vivono le credenziali.
Come si mappano i tre tipi di nodo YAML
YAML ha esattamente tre tipi di nodo e ciascuno ha una controparte XML. Una mappa diventa un insieme di elementi figli, uno per chiave, con la chiave come nome dell’elemento. Una sequenza ripete il nome del proprio elemento genitore una volta per membro, senza involucro, perché la ripetizione è il modo in cui XML esprime un elenco. Uno scalare diventa il contenuto testuale del proprio elemento.
Sopra a tutto questo sta il problema della radice. YAML consente qualsiasi nodo in cima a un documento; XML richiede esattamente una radice. Una mappa con una sola chiave ha già una radice naturale, quindi quella chiave diventa l’elemento radice. Una mappa con due o più chiavi, una sequenza di primo livello o uno scalare nudo vengono avvolti in un unico elemento, chiamato root per impostazione predefinita e modificabile nella riga di controlli.
Vale la pena conoscere due conseguenze della regola delle sequenze. Una sequenza vuota non produce proprio nulla, quindi la chiave sparisce: zero ripetizioni di un elemento fanno zero elementi. E una sequenza annidata direttamente dentro un’altra sequenza si appiattisce, perché quella interna non ha un nome proprio da usare.
order:
id: '00042'
line:
- Widget
- Gasket
note: null
tags: []
<?xml version="1.0" encoding="UTF-8"?>
<order>
<id>00042</id>
<line>Widget</line>
<line>Gasket</line>
<note/>
</order>YAML ha già deciso i tuoi tipi prima che XML li veda
È la cosa più importante di questa pagina e non è una proprietà di questo convertitore. YAML risolve uno scalare semplice a un tipo in base a come è scritto, dentro il parser. Quando un valore arriva allo scrittore XML è già un numero, un booleano, una data o una stringa, e XML non ha alcun sistema di tipi con cui recuperare la distinzione.
js-yaml implementa lo schema centrale di YAML 1.2 più il tipo timestamp, il che produce quanto segue. Puoi verificarne ogni riga incollando il valore:
- true e false sono booleani e vengono scritti come i testi true e false. yes e no qui restano stringhe, ma un parser YAML 1.1 come PyYAML o Ansible legge no come false, quindi lo stesso file convertito con strumenti diversi produce XML diversi.
- Gli zeri iniziali spariscono prima della conversione: 01730 si risolve nel numero 1730, e lo scrittore XML non può farci nulla. Scrivi '01730'.
- Anche le basi alternative vengono risolte, quindi 0x1F diventa 31 e viene scritto come <hex>31</hex>. I codici colore esadecimali e gli identificatori hardware hanno bisogno degli apici.
- Un intero YAML diventa un double nel browser, quindi un identificatore di diciannove cifre ha già perso le cifre basse prima che lo scrittore entri in gioco. Metti sempre fra apici gli identificatori.
- Le date si risolvono in timestamp, e un timestamp non ha una rappresentazione testuale che lo scrittore possa produrre, quindi 2024-01-05 esce come un <when/> vuoto. Mettilo fra apici e viene scritto come testo.
Le chiavi YAML spesso non sono nomi XML leciti
La sezione 2.3 di XML 1.0 dice che un nome di elemento inizia con una lettera, un trattino basso o i due punti e continua con questi più cifre, trattini e punti. Una chiave YAML non ha una restrizione del genere: «2024 total», «user@email» e la stringa vuota sono tutte chiavi ordinarie e nessuna può essere un nome di elemento.
Ciascuna viene rinominata anziché rifiutata, e ogni rinomina viene segnalata. I caratteri illeciti vengono sostituiti uno a uno con un trattino basso, e un nome che inizia ancora con una cifra riceve un trattino basso davanti. Sostituire anziché togliere è deliberato: togliere farebbe di «2024 total» e «2024total» lo stesso elemento, fondendo due campi distinti. Quindi «2024 total» diventa _2024_total, «2024-total» diventa _2024-total perché il trattino è già lecito, e «user@email» diventa user_email. Non è a tenuta stagna: «first name» e «first_name» finiscono entrambe su first_name, quindi rinomina le chiavi che differiscono solo per la punteggiatura.
YAML consente anche chiavi che non sono stringhe: 2024 è un intero, true un booleano, e la sintassi a chiave esplicita permette un’intera sequenza come chiave. Tutte vengono rese stringhe prima di diventare nomi di elemento. Una stranezza: le chiavi che somigliano a indici di array vengono enumerate per prime e in ordine numerico crescente, quindi una mappa che mescola 2, 10 e name non emetterà i suoi elementi nell’ordine in cui li hai scritti.
Flussi a più documenti, ancore e chiavi di merge
Un flusso YAML può contenere più documenti separati da tre trattini, e i manifesti Kubernetes lo fanno di continuo. XML ha esattamente una radice, quindi vengono letti tutti e avvolti: un unico elemento <documents> con un figlio <document> per ciascun documento YAML, e una nota che dice quanti ne sono stati trovati. Quasi tutti i convertitori troncano in silenzio al primo, cosa che scopri in produzione quando due terzi di un manifesto spariscono senza rumore.
Ancore, alias e chiavi di merge vengono risolti dal parser e sono spariti quando l’XML viene scritto. Quel che ottieni è il risultato pienamente espanso, che è corretto e può essere considerevolmente più grande dell’input: un blocco di base messo in alias in quaranta servizi produce quaranta copie. Quell’espansione avviene nella memoria di questa scheda, quindi un uso pesante degli alias può essere lento, e il tetto di dimensione vale per l’output oltre che per la sorgente.
I commenti non vengono conservati, perché non fanno parte del modello dei dati YAML e il parser non li consegna mai. Un flusso vuoto, o che contenga solo commenti, viene segnalato come vuoto anziché convertito in un elemento radice vuoto.
Farlo da codice
Due passi in ogni linguaggio: caricare lo YAML con un loader sicuro, poi scrivere XML con qualcosa che faccia l’escape come si deve. Qui il flag di sicurezza sta dalla parte dello YAML, non dell’XML. Diverse librerie YAML, per impostazione predefinita o per via di un singolo tag nel documento, istanziano classi arbitrarie a partire dal file, il che è esecuzione di codice remoto travestita da configurazione.
import yaml from 'js-yaml';
import { XMLBuilder } from 'fast-xml-parser';
// load() uses the default schema, which constructs no JavaScript types.
// Do not swap in js-yaml's extended schema for untrusted input.
const docs = [];
yaml.loadAll(yamlSource, (d) => docs.push(d));
if (docs.length === 0) throw new Error('The YAML document is empty.');
// XML has one root; a multi-document stream needs wrapping, not truncating.
let data = docs.length > 1 ? { documents: { document: docs } } : docs[0];
if (data === null || typeof data !== 'object' || Array.isArray(data)
|| Object.keys(data).length !== 1) {
data = { root: data };
}
const builder = new XMLBuilder({
ignoreAttributes: false,
attributeNamePrefix: '@_',
textNodeName: '#text',
format: true,
indentBy: ' ',
suppressEmptyNode: true,
});
console.log('<?xml version="1.0" encoding="UTF-8"?>');
console.log(builder.build(data));
// XMLBuilder does not sanitise names. A YAML key of "2024 total" is written
// verbatim and the result will not parse, so validate before you ship it.import re
import yaml
import xmltodict
def legal_name(key):
"""Replace illegal characters rather than stripping them, so distinct
keys stay distinct. Prefix a leading digit."""
name = re.sub(r'[^\w.\-:]', '_', str(key), flags=re.UNICODE)
return name if re.match(r'^[A-Za-z_:]', name) else '_' + name
def sanitise(node):
if isinstance(node, dict):
return {legal_name(k): sanitise(v) for k, v in node.items()}
if isinstance(node, list):
return [sanitise(v) for v in node]
if isinstance(node, bool):
return 'true' if node else 'false'
return node if node is None else str(node)
# safe_load, never load: yaml.load with the default Loader will construct
# arbitrary Python objects from !!python tags in the document.
docs = [d for d in yaml.safe_load_all(yaml_source) if d is not None]
if not docs:
raise SystemExit('The YAML document is empty.')
data = {'documents': {'document': docs}} if len(docs) > 1 else docs[0]
if not isinstance(data, dict) or len(data) != 1:
data = {'root': data}
print(xmltodict.unparse(sanitise(data), pretty=True, indent=' ',
full_document=True))
# PyYAML applies the YAML 1.1 resolver, so an unquoted no is False here and
# a string in js-yaml. Quote anything whose type you care about.import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
// Jackson's YAML module wraps SnakeYAML but binds only to JsonNode and to
// classes you name, so the SnakeYAML deserialisation gadget problem
// (CVE-2022-1471, the default Constructor instantiating arbitrary types)
// is not reachable through this API. Using SnakeYAML directly, construct it
// as: new Yaml(new SafeConstructor(new LoaderOptions()))
JsonNode tree = new YAMLMapper().readTree(yamlSource);
XmlMapper xml = new XmlMapper();
xml.enable(SerializationFeature.INDENT_OUTPUT);
String out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ xml.writer().withRootName("root").writeValueAsString(tree);
// readTree reads the first document only. For a multi-document stream use
// new YAMLMapper().readerFor(JsonNode.class).readValues(yamlSource)
// and wrap the results yourself.using Newtonsoft.Json;
using YamlDotNet.Serialization;
// YamlDotNet's Deserializer binds only to types you name and does not
// resolve arbitrary .NET types from tags in the document.
var yaml = new DeserializerBuilder().Build();
object? tree = yaml.Deserialize<object>(new StringReader(yamlSource));
if (tree is null) throw new InvalidOperationException("The YAML is empty.");
// Round-trip through JSON so Json.NET can do the XML writing, including the
// escaping. The second argument names the root, which YAML does not supply
// and XML requires.
string json = JsonConvert.SerializeObject(tree);
var document = JsonConvert.DeserializeXmlNode(json, "root")
?? throw new InvalidOperationException("Nothing to write.");
var settings = new System.Xml.XmlWriterSettings { Indent = true, IndentChars = " " };
using var writer = System.Xml.XmlWriter.Create(Console.Out, settings);
document.Save(writer);
// Json.NET will not sanitise names: a YAML key of "2024 total" throws
// XmlException when the node is created. Rewrite keys before this point.<?php
use Symfony\Component\Yaml\Yaml;
// Symfony's parser never instantiates PHP objects unless you pass
// PARSE_OBJECT or PARSE_OBJECT_FOR_MAP. Do not pass either for input you did
// not write. The ext-yaml alternative, yaml_parse(), is governed by the
// yaml.decode_php ini setting, which is off by default; check it.
$data = Yaml::parse($source, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
function legal_name(string $key): string {
$name = preg_replace('/[^\w.\-:]/u', '_', $key);
return preg_match('/^[A-Za-z_:]/', $name) ? $name : '_' . $name;
}
function write_node(XMLWriter $w, string $name, mixed $value): void {
if (is_array($value) && array_is_list($value)) {
foreach ($value as $v) write_node($w, $name, $v); // repeat, no wrapper
return;
}
$w->startElement(legal_name($name));
if (is_array($value)) {
foreach ($value as $k => $v) write_node($w, (string) $k, $v);
} elseif (is_bool($value)) {
$w->text($value ? 'true' : 'false');
} elseif ($value !== null) {
$w->text((string) $value);
}
$w->endElement();
}
$single = count($data) === 1;
$w = new XMLWriter();
$w->openMemory();
$w->setIndent(true);
$w->setIndentString(' ');
$w->startDocument('1.0', 'UTF-8');
write_node($w, $single ? (string) array_key_first($data) : 'root',
$single ? reset($data) : $data);
$w->endDocument();
echo $w->outputMemory();# yq v4 (Mike Farah) converts directly.
yq -p=yaml -o=xml '.' config.yaml
# yq writes no XML declaration and no wrapper, so a multi-key document
# produces several roots. Wrap it first:
yq -p=yaml -o=xml '{"root": .}' config.yaml
# A multi-document stream needs collecting into one root explicitly, or yq
# emits one XML fragment per document:
yq ea -p=yaml -o=xml '{"documents": {"document": [.]}}' manifests.yaml
# Always check the result. yq does not sanitise element names, so a key with
# a space in it produces XML that will not parse:
yq -p=yaml -o=xml '{"root": .}' config.yaml | xmllint --noout --nonet -Il flag da azzeccare in ognuno di questi casi è quello del loader, non dello scrittore. yaml.load in Python, il Constructor predefinito di SnakeYAML in Java e yaml_parse con yaml.decode_php abilitato costruiscono tutti oggetti arbitrari a partire dai tag nel documento. Un file YAML è dato finché non usi un loader che gli permetta di essere qualcos’altro.
Domande frequenti
Il mio YAML viene caricato da qualche parte?
No. Il parser YAML e lo scrittore XML sono entrambi JavaScript che gira in questa scheda, e non c’è alcun componente lato server che possano raggiungere. Apri la scheda Rete negli strumenti per sviluppatori, incolla un documento, e vedrai le risorse della pagina caricarsi una volta e poi più nulla.
Qui in particolare vale la pena controllarlo. Lo YAML è dove vive la configurazione: segreti Kubernetes, variabili delle pipeline di CI, inventari Ansible con nomi host e nomi utente, file compose con dentro le password del database.
Perché il mio codice postale, numero di versione o ID è cambiato?
Perché l’ha cambiato YAML, non lo scrittore XML. YAML deduce il tipo di uno scalare da come è scritto, quindi 01730 è il numero 1730, 1.10 è il float 1.1, e un identificatore di diciannove cifre non entra in un double. Tutto questo accade dentro il parser YAML, prima che qualsiasi cosa relativa all’XML venga eseguita.
La correzione sta nello YAML: metti il valore fra apici. '01730', '1.10' e '9007199254740993' arrivano tutti come stringhe e vengono scritti esattamente come digitati. Se lo YAML lo ha prodotto un generatore, avrebbe dovuto quotarli lui.
Che cosa succede a un file YAML con più documenti separati da ---?
Vengono letti tutti e avvolti. Ottieni un unico elemento <documents> con un figlio <document> per ciascun documento YAML, e il pannello delle note dice quanti ne sono stati trovati.
L’alternativa, che quasi tutti i convertitori scelgono, è convertire il primo documento e ignorare il resto in silenzio. È una pessima impostazione predefinita per i manifesti Kubernetes, dove un file contiene abitualmente un Deployment, un Service e una ConfigMap, e perderne due su tre non è cosa che noti finché un rilascio non fallisce.
Come vengono gestiti ancore, alias e chiavi di merge?
Vengono risolti dal parser e poi pienamente espansi nell’output. Un’ancora marca un nodo, un alias vi fa riferimento, e una chiave di merge applica una mappa dentro un’altra. Nessuno dei tre esiste in XML e nessuno sopravvive.
Quel che ottieni è corretto ma può essere molto più grande dell’input: un blocco di base messo in alias in quaranta servizi produce quaranta copie complete. È quel che lo YAML intendeva, solo che YAML ti ha lasciato scriverlo una volta sola. L’espansione avviene nella memoria di questa scheda, quindi un uso pesante degli alias può essere lento.
Posso ottenere i valori come attributi XML invece che come elementi figli?
Sì. Fai precedere la chiave nel tuo YAML dal prefisso degli attributi mostrato nella riga di controlli, che per impostazione predefinita è @_: così una chiave '@_id' con valore 7 diventa un attributo id sull’elemento che la racchiude anziché un figlio <id>.
Devi mettere quella chiave fra apici. Uno scalare semplice non può iniziare con @, che YAML riserva, quindi un @_id non quotato è un errore di analisi e il messaggio si lamenterà dell’indentazione anziché del carattere. Mettici solo scalari: il valore di un attributo non può contenere struttura, quindi una mappa sotto una chiave @_ produce un pasticcio reso stringa anziché XML annidato.
L’XML che produce è valido?
È ben formato, che è un’affermazione diversa e più debole. Ogni elemento è chiuso, c’è esattamente una radice, la e commerciale, il segno di minore e la sequenza ]]> sono protetti nel testo, i valori degli attributi proteggono inoltre gli apici doppi e i caratteri di spaziatura, e in cima viene scritta una dichiarazione UTF-8.
Validità significa corrispondere a uno schema, e YAML non ne porta alcuno da cui ricavarlo. Se l’XML va da qualche parte dove viene convalidato, portalo al validatore XSD insieme allo schema che quel sistema pubblica.