Convertisseur XML vers YAML
Convertit en YAML, valeurs ambiguës mises entre guillemets.
Tout s'exécute dans cet onglet. Rien de ce que vous collez n'est envoyé, journalisé ni transmis où que ce soit. Ouvrez votre panneau réseau et vérifiez.
Collez du XML ci-dessus et le YAML apparaît à côté. Le document est contrôlé quant à sa bonne formation, projeté sur un arbre, puis écrit par un émetteur dont le travail principal est de décider quelles valeurs doivent être citées. Rien n’est téléversé : l’analyseur, le projecteur et l’émetteur tournent tous dans cet onglet.
La raison habituelle d’en vouloir, c’est qu’un fichier de configuration, un manifeste Kubernetes, un pipeline de CI ou un inventaire Ansible a besoin de données qui vivent aujourd’hui en XML. La sortie part dans un fichier qu’une machine lit au pied de la lettre, et c’est pourquoi la citation compte plus que la mise en page.
YAML a l’air du format sympathique et c’est celui qui a le plus de chances de changer vos données en silence. Un code pays NO non cité devient le booléen false dans la plus grande partie de l’écosystème. Un code postal 01730 devient 1730. Une version 1.10 devient 1.1. Cet émetteur cite les valeurs qui seraient autrement mal lues, et cette page dit exactement lesquelles et pourquoi.
La projection est celle de XML vers JSON
YAML 1.2 a été conçu comme un surensemble de JSON : il n’y a donc pas d’arbre distinct ici. Le XML est converti vers la même structure que produit la page XML vers JSON, et un sérialiseur différent l’écrit. Chaque décision de projection de cette page s’applique sans changement : les attributs deviennent des clés préfixées, le texte qui partage un élément avec des attributs ou des enfants passe sous une clé de texte, un élément apparaissant deux fois devient une séquence, et les commentaires sont perdus.
Une chose empire. En JSON, un consommateur voit au moins des crochets ; en YAML, la différence entre un élément et deux est un scalaire indenté contre une liste de tirets, et personne ne repère cela dans un diff. Utilisez le champ « toujours un tableau » pour tout ce qui est conceptuellement une liste, afin qu’un document à un élément et un document à cinquante produisent la même forme.
<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'Le problème norvégien, et la liste exacte qu’il couvre
YAML 1.1 définit son type booléen par énumération, et l’énumération est plus large que quiconque ne l’imagine. La page de types publiée liste, mot pour mot : y, Y, yes, Yes, YES, n, N, no, No, NO, true, True, TRUE, false, False, FALSE, on, On, ON, off, Off, OFF. Chacune d’elles, non citée, se charge comme un booléen.
La conséquence porte un nom. Un jeu de codes pays ISO reçoit NO pour la Norvège et l’analyseur remet false à l’application. La même liste avale une colonne Oui/Non exportée d’un tableur et tout interrupteur écrit on ou off qui se voulait du texte. YAML 1.2 a réduit le schéma central à true et false seulement, mais PyYAML, le Psych de Ruby, Ansible et une bonne part de l’outillage Kubernetes résolvent encore l’ensemble 1.1 : considérez donc qu’il est tout entier actif.
L’émetteur met entre apostrophes tout scalaire correspondant exactement à cette liste, formes à une seule lettre comprises, plus null, Null, NULL et le tilde. Notez la sensibilité à la casse : yES et nO ne sont pas dans la liste 1.1 et ne sont pas cités, car aucun analyseur conforme ne les lit comme des booléens non plus.
Ce qui est cité d’autre, et ce qui passe à travers
L’ensemble booléen est le cas célèbre, pas le cas courant. La plupart des valeurs qui cassent sont des nombres qui n’en ont jamais été, car YAML déduit un type de l’orthographe d’un scalaire nu exactement comme JSON ne le fait pas. Un scalaire est mis entre apostrophes quand il correspond à l’ensemble booléen ou nul, quand il correspond à une grammaire de nombre JSON (ce qui couvre 42, 19.90 et 1.10), quand il a un zéro de tête suivi d’autres chiffres, quand il est vide, quand il commence par un caractère indicateur de YAML comme un trait d’union ou un dièse, ou quand il a une espace à l’une de ses extrémités.
Le texte multiligne n’est pas cité. Il devient un scalaire de bloc littéral introduit par une barre verticale avec indicateur de suppression. Le littéral est choisi au lieu du plié à dessein : un bloc plié reflue les sauts de ligne simples en espaces, détruisant le code et les adresses incorporés. L’indicateur de suppression retire le saut de ligne final qu’un bloc ajouterait sinon.
Certaines valeurs quittent tout de même l’émetteur sans citation et peuvent changer de type en aval. Elles sont listées plutôt qu’escamotées, car aucun émetteur utilisant des scalaires nus n’a résolu l’inférence de types de YAML :
- Les nombres sexagésimaux. YAML 1.1 lit 22:22 comme un entier en base 60, donc une durée devient 1342 dans PyYAML. Un analyseur 1.2 comme js-yaml renvoie la chaîne : cela dépend donc du côté qui lit le fichier.
- Les écritures hexadécimales. 0x1F se charge comme 31 en YAML 1.1 comme dans le schéma central 1.2, donc un code couleur hexadécimal a besoin de citation.
- Les dates. 2024-01-05 correspond au type timestamp de YAML, donc js-yaml et PyYAML vous remettent tous deux un objet date plutôt qu’une chaîne.
- Les scalaires de bloc dont la première ligne est indentée plus loin que les suivantes, ce qui arrive quand une section CDATA préserve les espaces de tête. Le correctif de YAML est un indicateur d’indentation explicite après la barre verticale, que cet émetteur n’écrit pas.
Réglez le préfixe d’attributs et la clé de texte avant de convertir
C’est la seule préparation qui vaille la peine. Les valeurs par défaut ont été choisies pour JSON, où elles sont sûres, et YAML a une grammaire plus stricte pour les clés que pour les valeurs.
Le préfixe d’attributs par défaut est @_ et la clé de texte par défaut est #text. En YAML, @ est un indicateur réservé par lequel un scalaire nu ne peut pas commencer, donc une clé @_id rend le document non analysable : js-yaml signale « bad indentation of a mapping entry » et PyYAML signale un caractère qui ne peut commencer aucun jeton. Un # de tête est pire, car il n’échoue pas. Une ligne portant #text: 19.90 est un commentaire : le fichier se charge et la valeur n’y est tout simplement pas.
Les deux champs se trouvent dans la rangée de réglages au-dessus de l’éditeur. Mettez le préfixe à quelque chose de simple comme attr_ et la clé de texte à text, et chaque clé de la sortie est un nom YAML ordinaire. Les clés qui ont besoin de citation pour d’autres raisons, comme soap:Body, sont citées automatiquement, car les deux-points ne sont pas licites dans une clé nue.
Le faire en code
Deux étapes : analyser le XML en sécurité, puis sérialiser avec un dumper auquel vous avez dit de citer correctement. La moitié XML a besoin des drapeaux d’entités habituels, car les réglages par défaut en Java et en .NET résolvent un DOCTYPE. La moitié YAML demande de l’attention, car les dumpers diffèrent sur l’agressivité de leur citation.
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: FalseL’échec dont parle cette page est silencieux. Un fichier YAML contenant un NO non cité s’analyse proprement, se valide proprement et se déploie proprement ; le pays est simplement false à partir de là. Le contrôle qui l’attrape consiste à recharger le fichier produit avec la même bibliothèque que le consommateur et à comparer une valeur connue pour être délicate, pas à lire le diff.
Questions fréquentes
Mon XML est-il téléversé quand je le convertis en YAML ?
Non. L’analyseur XML, le projecteur d’arbre et l’émetteur YAML sont tous du JavaScript dans cet onglet, et il n’y a aucun point d’accès vers lequel poster. Ouvrez l’onglet Réseau de vos outils de développement, collez un document, et regardez qu’il ne se passe rien.
Cela vaut d’être confirmé plutôt que supposé, car du XML converti en YAML est très souvent de la configuration. Chaînes de connexion, comptes de service, clés d’API et noms d’hôtes internes finissent tous dans le genre de document que les gens apportent à un convertisseur.
Qu’est-ce que le problème norvégien ?
YAML 1.1 définit son type booléen comme une liste fixe d’orthographes, et cette liste contient n, N, no, No et NO. Donc un champ portant le code ISO de la Norvège, écrit sans citation, se charge comme false. La même liste avale y et Y, on et off, et toute colonne Oui/Non exportée d’un tableur.
YAML 1.2 a réduit le schéma central à true et false seulement, ce qui n’a pas réparé l’écosystème : PyYAML, Psych, Ansible et une bonne part de l’outillage Kubernetes résolvent encore l’ensemble 1.1, et vous maîtrisez rarement l’analyseur qui lira votre fichier. L’émetteur cite chaque orthographe de cette liste, donc NO reste la chaîne NO.
Pourquoi certaines valeurs sont-elles entourées de guillemets et d’autres non ?
Parce que les guillemets sont porteurs. Un scalaire YAML nu voit son type déduit de la façon dont il est écrit : 01730 est un nombre, 1.10 un flottant, NO un booléen, et un trait d’union de tête démarre un élément de liste. Citer, c’est la façon de dire que c’est du texte.
L’émetteur cite exactement les valeurs qui changeraient sinon de type ou de sens et laisse le reste nu, car citer chaque scalaire rend un fichier plus difficile à lire et à comparer sans rien apporter. Pour une citation uniforme, la plupart des bibliothèques YAML ont une option de citation forcée ; les exemples ci-dessus la montrent pour js-yaml et YamlDotNet.
Qu’advient-il du contenu textuel multiligne ?
Il devient un scalaire de bloc littéral, introduit par une barre verticale avec indicateur de suppression, les lignes indentées en dessous. Le littéral a été choisi plutôt que le plié exprès : un bloc plié reflue les sauts de ligne simples en espaces, détruisant discrètement le code et les adresses incorporés.
Un cas à surveiller. Si la première ligne du texte est indentée plus loin que les suivantes, ce qui arrive quand une section CDATA préserve les espaces de tête, le bloc est ambigu et un analyseur le refusera.
Les éléments répétés deviennent-ils des listes YAML ?
Oui. Un élément apparaissant plus d’une fois sous le même parent devient une séquence écrite en liste de tirets ; un élément apparaissant une fois devient une simple table imbriquée ou un scalaire. C’est la même ambiguïté du singleton que décrit la page XML vers JSON, et elle est plus dangereuse ici parce que YAML la cache : la différence entre un élément et deux est un tiret et deux espaces d’indentation.
Utilisez le champ « toujours un tableau » au-dessus de l’éditeur. Nommez les éléments qui sont conceptuellement des listes et ils sont émis comme séquences, que le document en porte un ou quarante.
Les commentaires et les espaces de noms XML sont-ils préservés ?
Les commentaires non. Ils sont perdus quand le document est projeté sur un arbre, avant que l’émetteur ne voie quoi que ce soit. Les commentaires YAML ne font pas partie du modèle de données, donc un commentaire écrit dans la sortie disparaîtrait la première fois que quelqu’un chargerait et réenregistrerait le fichier.
Les préfixes d’espaces de noms sont gardés tels quels, donc soap:Body devient une clé écrite soap:Body, citée automatiquement car les deux-points ne sont pas licites dans une clé YAML nue. Cocher « retirer les préfixes d’espaces de noms » donne un Body nu à la place, au risque de fusionner deux espaces de noms sur une seule clé.