Convertitore da XML a YAML
Converte in YAML, con i valori rischiosi tra virgolette.
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 dell’XML qui sopra e accanto compare lo YAML. Il documento viene controllato per la buona formazione, mappato su un albero, poi scritto da un emettitore il cui compito principale è decidere quali valori vanno messi fra apici. Nulla viene caricato: lo scanner, il mappatore e l’emettitore girano tutti in questa scheda.
Il motivo abituale per volerlo è che un file di configurazione, un manifesto Kubernetes, una pipeline di CI o un inventario Ansible ha bisogno di dati che oggi vivono in XML. L’output finisce in un file che una macchina legge alla lettera, ed è per questo che la quotatura conta più dell’impaginazione.
YAML sembra il formato amichevole ed è quello con più probabilità di cambiarti i dati in silenzio. Un codice paese NO senza apici diventa il booleano false in gran parte dell’ecosistema. Un codice postale 01730 diventa 1730. Una versione 1.10 diventa 1.1. Questo emettitore mette fra apici i valori che verrebbero altrimenti letti male, e questa pagina dice esattamente quali e perché.
La mappatura è quella da XML a JSON
YAML 1.2 è stato progettato come soprainsieme di JSON, quindi qui non c’è un albero separato. L’XML viene convertito nella stessa struttura che produce la pagina da XML a JSON e un serializzatore diverso la scrive. Ogni decisione di mappatura di quella pagina vale immutata: gli attributi diventano chiavi con prefisso, il testo che condivide un elemento con attributi o figli finisce sotto una chiave di testo, un elemento che compare due volte diventa una sequenza, e i commenti vengono persi.
Una cosa peggiora. In JSON chi consuma vede almeno delle parentesi quadre; in YAML la differenza fra un elemento e due è uno scalare indentato contro un elenco di trattini, e nessuno la nota in un diff. Usa il campo «sempre un array» per tutto ciò che concettualmente è un elenco, così un documento con un elemento e uno con cinquanta producono la stessa forma.
<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'Il problema norvegese, e l’elenco esatto che copre
YAML 1.1 definisce il proprio tipo booleano per enumerazione, e l’enumerazione è più ampia di quanto chiunque si aspetti. La pagina dei tipi pubblicata elenca, alla lettera: y, Y, yes, Yes, YES, n, N, no, No, NO, true, True, TRUE, false, False, FALSE, on, On, ON, off, Off, OFF. Ognuno di questi, senza apici, si carica come booleano.
La conseguenza ha un nome. Un insieme di codici paese ISO riceve NO per la Norvegia e il parser consegna all’applicazione false. Lo stesso elenco inghiotte una colonna Sì/No esportata da un foglio di calcolo e qualunque interruttore scritto on od off che doveva essere testo. YAML 1.2 ha ristretto lo schema centrale ai soli true e false, ma PyYAML, il Psych di Ruby, Ansible e buona parte degli strumenti Kubernetes risolvono ancora l’insieme 1.1, quindi dài per scontato che sia tutto vivo.
L’emettitore mette fra apici singoli ogni scalare che corrisponda esattamente a quell’elenco, comprese le forme di una sola lettera, più null, Null, NULL e la tilde. Nota la sensibilità alle maiuscole: yES e nO non stanno nell’elenco 1.1 e non vengono quotati, perché nemmeno un parser conforme li legge come booleani.
Che altro viene quotato, e che cosa sfugge
L’insieme booleano è il caso famoso, non quello comune. Quasi tutti i valori che si rompono sono numeri che numeri non sono mai stati, perché YAML deduce un tipo dalla grafia di uno scalare semplice esattamente come JSON non fa. Uno scalare viene messo fra apici singoli quando corrisponde all’insieme booleano o nullo, quando corrisponde a una grammatica di numero JSON (coprendo 42, 19.90 e 1.10), quando ha uno zero iniziale seguito da altre cifre, quando è vuoto, quando inizia con un carattere indicatore di YAML come un trattino o un cancelletto, o quando ha spazi a una delle due estremità.
Il testo su più righe non viene quotato. Diventa uno scalare a blocco letterale introdotto da una barra verticale con indicatore di taglio. Il letterale è scelto al posto del ripiegato di proposito: un blocco ripiegato riversa le singole interruzioni di riga in spazi, distruggendo codice e indirizzi incorporati. L’indicatore di taglio rimuove l’interruzione di riga finale che un blocco aggiungerebbe altrimenti.
Alcuni valori lasciano comunque l’emettitore senza apici e possono cambiare tipo a valle. Vengono elencati anziché sorvolati, perché nessun emettitore che usi scalari semplici ha risolto l’inferenza di tipo di YAML:
- I numeri sessagesimali. YAML 1.1 legge 22:22 come un intero in base 60, quindi una durata diventa 1342 in PyYAML. Un parser 1.2 come js-yaml restituisce la stringa, quindi dipende da quale lato legge il file.
- Le grafie esadecimali. 0x1F si carica come 31 sia in YAML 1.1 sia nello schema centrale 1.2, quindi un codice colore esadecimale ha bisogno degli apici.
- Le date. 2024-01-05 corrisponde al tipo timestamp di YAML, quindi js-yaml e PyYAML ti consegnano entrambi un oggetto data anziché una stringa.
- Gli scalari a blocco la cui prima riga è indentata più delle righe successive, cosa che capita quando una sezione CDATA conserva gli spazi iniziali. La soluzione di YAML è un indicatore di indentazione esplicito dopo la barra verticale, che questo emettitore non scrive.
Imposta il prefisso degli attributi e la chiave di testo prima di convertire
È l’unica preparazione che vale la pena fare. I valori predefiniti sono stati scelti per JSON, dove sono sicuri, e YAML ha per le chiavi una grammatica più severa che per i valori.
Il prefisso degli attributi predefinito è @_ e la chiave di testo predefinita è #text. In YAML, @ è un indicatore riservato con cui uno scalare semplice non può iniziare, quindi una chiave @_id rende il documento non analizzabile: js-yaml segnala «bad indentation of a mapping entry» e PyYAML segnala un carattere che non può iniziare alcun token. Un # iniziale è peggio, perché non fallisce. Una riga che dice #text: 19.90 è un commento, quindi il file si carica e il valore semplicemente non c’è.
Entrambi i campi stanno nella riga di controlli sopra l’editor. Metti il prefisso su qualcosa di semplice come attr_ e la chiave di testo su text, e ogni chiave nell’output è un normale nome YAML. Le chiavi che richiedono apici per altri motivi, come soap:Body, vengono quotate automaticamente, perché i due punti non sono leciti in una chiave nuda.
Farlo da codice
Due passi: analizzare l’XML in sicurezza, poi serializzare con un dumper a cui hai detto di quotare come si deve. La metà XML ha bisogno dei soliti flag sulle entità, perché le impostazioni predefinite in Java e in .NET risolvono un DOCTYPE. La metà YAML richiede attenzione perché i dumper differiscono su quanto aggressivamente quotano.
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: FalseIl fallimento di cui parla questa pagina è silenzioso. Un file YAML con dentro un NO non quotato si analizza pulito, si convalida pulito e si distribuisce pulito; il paese da lì in poi è semplicemente false. Il controllo che lo intercetta è ricaricare il file generato con la stessa libreria che usa chi lo consuma e confrontare un valore noto per essere scomodo, non leggere il diff.
Domande frequenti
Il mio XML viene caricato quando lo converto in YAML?
No. Lo scanner XML, il mappatore ad albero e l’emettitore YAML sono tutti JavaScript in questa scheda, e non c’è alcun endpoint verso cui inviare. Apri la scheda Rete negli strumenti per sviluppatori, incolla un documento e guarda che non succede nulla.
Vale la pena confermarlo anziché darlo per scontato, perché l’XML convertito in YAML è molto spesso configurazione. Stringhe di connessione, account di servizio, chiavi API e nomi host interni finiscono tutti nel tipo di documento che la gente porta a un convertitore.
Che cos’è il problema norvegese?
YAML 1.1 definisce il proprio tipo booleano come un elenco fisso di grafie, e quell’elenco comprende n, N, no, No e NO. Quindi un campo che contiene il codice ISO della Norvegia, scritto senza apici, si carica come false. Lo stesso elenco inghiotte y e Y, on e off, e qualunque colonna Sì/No esportata da un foglio di calcolo.
YAML 1.2 ha ristretto lo schema centrale ai soli true e false, cosa che non ha risanato l’ecosistema: PyYAML, Psych, Ansible e buona parte degli strumenti Kubernetes risolvono ancora l’insieme 1.1, e raramente controlli quale parser legge il tuo file. L’emettitore quota ogni grafia di quell’elenco, quindi NO resta la stringa NO.
Perché alcuni valori sono racchiusi fra apici e altri no?
Perché gli apici sono portanti. Uno scalare YAML semplice si vede dedurre il tipo da come è scritto, quindi 01730 è un numero, 1.10 è un float, NO è un booleano e un trattino iniziale avvia un elemento di elenco. Quotare è il modo di dire che è testo.
L’emettitore quota esattamente i valori che altrimenti cambierebbero tipo o significato e lascia tutto il resto semplice, perché quotare ogni scalare rende un file più difficile da leggere e da confrontare senza alcun vantaggio. Per una quotatura uniforme, quasi tutte le librerie YAML hanno un’opzione per forzare gli apici; gli esempi qui sopra la mostrano per js-yaml e YamlDotNet.
Che ne è del contenuto testuale su più righe?
Diventa uno scalare a blocco letterale, introdotto da una barra verticale con indicatore di taglio, con le righe indentate sotto. Il letterale è stato scelto al posto del ripiegato di proposito: un blocco ripiegato riversa le singole interruzioni di riga in spazi, distruggendo in silenzio codice e indirizzi incorporati.
Un caso da tenere d’occhio. Se la prima riga del testo è indentata più delle righe successive, cosa che capita quando una sezione CDATA conserva gli spazi iniziali, il blocco è ambiguo e un parser lo rifiuterà.
Gli elementi ripetuti diventano elenchi YAML?
Sì. Un elemento che compare più di una volta sotto lo stesso genitore diventa una sequenza scritta come elenco di trattini; uno che compare una volta diventa una semplice mappa annidata o uno scalare. È la stessa ambiguità del singolo che descrive la pagina da XML a JSON, ed è più pericolosa qui perché YAML la nasconde: la differenza fra un elemento e due è un trattino e due spazi di indentazione.
Usa il campo «sempre un array» sopra l’editor. Nomina gli elementi che sono concettualmente elenchi e vengono emessi come sequenze, che il documento ne contenga uno o quaranta.
I commenti e i namespace XML vengono conservati?
I commenti no. Vengono persi quando il documento viene mappato su un albero, prima che l’emettitore veda alcunché. I commenti YAML non fanno parte del modello dei dati, quindi uno scritto nell’output svanirebbe la prima volta che qualcuno carica e risalva il file.
I prefissi di namespace restano letterali, quindi soap:Body diventa una chiave scritta soap:Body, quotata automaticamente perché i due punti non sono leciti in una chiave YAML nuda. Spuntare «rimuovi i prefissi di namespace» dà invece un semplice Body, con il rischio di fondere due namespace su una sola chiave.