Convertidor de YAML a XML
Convierte YAML a XML, íntegramente en tu navegador.
Todo se ejecuta en esta pestaña. Nada de lo que pegues se sube, se registra ni se envía a ningún sitio. Abre el panel de red y compruébalo.
Pega YAML arriba y al lado aparece XML indentado y bien formado, con declaración y cada carácter especial escapado. Si el YAML no se analiza recibes la primera línea de queja del propio analizador en lugar de un panel en blanco, y si hubo que renombrar una clave para que fuese un nombre de elemento XML legal, la herramienta lista qué cambió.
Esta es la dirección que necesitas cuando algo viejo tiene que leer algo nuevo: una integración que solo acepta XML, un endpoint SOAP, un formato de intercambio validado con XSD, un fixture para un servicio configurado en YAML. Tiene un número sorprendente de aristas, y casi todas vienen de YAML y no de XML.
El análisis lo hace js-yaml, que se carga solo al abrir esta página y no en todas las páginas del sitio. Todo corre en esta pestaña y nada se sube, lo cual importa porque YAML es donde vive la configuración y la configuración es donde viven las credenciales.
Cómo se mapean los tres tipos de nodo de YAML
YAML tiene exactamente tres tipos de nodo y cada uno tiene una contraparte en XML. Un mapeo se convierte en un conjunto de elementos hijos, uno por clave, con la clave como nombre del elemento. Una secuencia repite el nombre de su elemento padre una vez por miembro, sin envoltorio, porque la repetición es como XML expresa una lista. Un escalar se convierte en el contenido de texto de su elemento.
Encima de eso está el problema de la raíz. YAML permite cualquier nodo en lo alto de un documento; XML exige exactamente una raíz. Un mapeo con una sola clave ya tiene una raíz natural, así que esa clave se convierte en el elemento raíz. Un mapeo con dos o más claves, una secuencia de nivel superior o un escalar pelado se envuelven en un solo elemento, llamado root por defecto y editable en la fila de controles.
Conviene conocer dos consecuencias de la regla de las secuencias. Una secuencia vacía no produce nada en absoluto, así que la clave desaparece: cero repeticiones de un elemento son cero elementos. Y una secuencia anidada directamente dentro de otra secuencia se aplana, porque la interior no tiene nombre propio que usar.
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 ya ha decidido tus tipos antes de que XML los vea
Esto es lo más importante de la página y no es una propiedad de este conversor. YAML resuelve un escalar plano a un tipo según cómo esté escrito, dentro del analizador. Para cuando un valor llega al escritor de XML ya es un número, un booleano, una fecha o una cadena, y XML no tiene un sistema de tipos con el que recuperar la distinción.
js-yaml implementa el esquema central de YAML 1.2 más el tipo timestamp, lo que produce lo siguiente. Puedes comprobar cada línea pegando el valor:
- true y false son booleanos y se escriben como el texto true y false. yes y no aquí se quedan como cadenas, pero un analizador de YAML 1.1 como PyYAML o Ansible lee no como false, así que el mismo archivo convertido con herramientas distintas produce XML distinto.
- Los ceros iniciales desaparecen antes de la conversión: 01730 se resuelve al número 1730, y el escritor de XML no puede hacer nada al respecto. Escribe '01730'.
- Las bases alternativas también se resuelven, así que 0x1F se convierte en 31 y se escribe como <hex>31</hex>. Los códigos de color hexadecimales y los identificadores de hardware necesitan comillas.
- Un entero de YAML se convierte en un double en el navegador, así que un identificador de diecinueve dígitos ya ha perdido sus dígitos bajos antes de que el escritor intervenga. Entrecomilla los identificadores, siempre.
- Las fechas se resuelven a timestamps, y un timestamp no tiene representación de texto que el escritor pueda producir, así que 2024-01-05 sale como un <when/> vacío. Entrecomíllalo y se escribe como texto.
Las claves de YAML muchas veces no son nombres XML legales
La sección 2.3 de XML 1.0 dice que un nombre de elemento empieza por una letra, un subrayado o dos puntos y sigue con esos más dígitos, guiones y puntos. Una clave de YAML no tiene esa restricción: «2024 total», «user@email» y la cadena vacía son todas claves corrientes y ninguna puede ser un nombre de elemento.
Cada una se renombra en lugar de rechazarse, y cada renombrado se informa. Los caracteres ilegales se sustituyen uno a uno por un subrayado, y un nombre que sigue empezando por dígito recibe un subrayado delante. Sustituir en vez de quitar es deliberado: quitar convertiría «2024 total» y «2024total» en el mismo elemento y fusionaría dos campos distintos. Así que «2024 total» se convierte en _2024_total, «2024-total» se convierte en _2024-total porque el guion ya es legal, y «user@email» se convierte en user_email. No es hermético: «first name» y «first_name» acaban las dos en first_name, así que renombra las claves que solo difieren en la puntuación.
YAML también permite claves que no son cadenas: 2024 es un entero, true es un booleano, y la sintaxis de clave explícita permite una secuencia entera como clave. Todas se convierten a cadena antes de ser nombres de elemento. Una peculiaridad: las claves que parecen índices de array se enumeran primero y en orden numérico ascendente, así que un mapeo que mezcla 2, 10 y name no emitirá sus elementos en el orden en que los escribiste.
Flujos de varios documentos, anclas y claves de fusión
Un flujo de YAML puede contener varios documentos separados por tres guiones, y los manifiestos de Kubernetes lo hacen a diario. XML tiene exactamente una raíz, así que se leen todos y se envuelven: un solo elemento <documents> con un hijo <document> por documento YAML, y una nota que dice cuántos se encontraron. La mayoría de los conversores truncan calladamente al primero, lo cual descubres en producción cuando dos tercios de un manifiesto desaparecen sin ruido.
Las anclas, los alias y las claves de fusión los resuelve el analizador y ya no están cuando se escribe el XML. Lo que obtienes es el resultado completamente expandido, que es correcto y puede ser bastante mayor que la entrada: un bloque base aliasado en cuarenta servicios produce cuarenta copias. Esa expansión ocurre en la memoria de esta pestaña, así que un aliasado intenso puede ser lento, y el tope de tamaño se aplica a la salida además de a la fuente.
Los comentarios no se conservan, porque no forman parte del modelo de datos de YAML y el analizador nunca los entrega. Un flujo vacío, o uno que solo contiene comentarios, se informa como vacío en vez de convertirse en un elemento raíz vacío.
Hacer esto desde código
Dos pasos en cada lenguaje: cargar el YAML con un cargador seguro, y luego escribir XML con algo que escape como es debido. Aquí la bandera de seguridad está del lado del YAML, no del XML. Varias bibliotecas de YAML, por defecto o con una sola etiqueta en el documento, instanciarán clases arbitrarias del archivo, lo cual es ejecución remota de código disfrazada de configuración.
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 -La bandera que hay que acertar en todos estos es la del cargador, no la del escritor. yaml.load en Python, el Constructor por defecto de SnakeYAML en Java y yaml_parse con yaml.decode_php activado construirán todos objetos arbitrarios a partir de etiquetas del documento. Un archivo YAML son datos hasta que usas un cargador que le deja ser otra cosa.
Preguntas frecuentes
¿Se sube mi YAML a algún sitio?
No. El analizador de YAML y el escritor de XML son los dos JavaScript corriendo en esta pestaña, y no hay componente de servidor al que puedan llegar. Abre la pestaña Red de tus herramientas de desarrollo, pega un documento, y verás cargarse los recursos de la propia página una vez y después nada.
Aquí en concreto vale la pena comprobarlo. YAML es donde vive la configuración: secretos de Kubernetes, variables de canalización de CI, inventarios de Ansible con nombres de host y de usuario, archivos compose con contraseñas de base de datos dentro.
¿Por qué cambió mi código postal, número de versión o ID?
Porque lo cambió YAML, no el escritor de XML. YAML infiere el tipo de un escalar según cómo esté escrito, así que 01730 es el número 1730, 1.10 es el flotante 1.1, y un identificador de diecinueve dígitos no cabe en un double. Todo eso pasa dentro del analizador de YAML, antes de que corra nada relacionado con XML.
El arreglo está en el YAML: entrecomilla el valor. '01730', '1.10' y '9007199254740993' llegan todos como cadenas y se escriben exactamente como los tecleaste. Si un generador produjo el YAML, debería haberlos entrecomillado.
¿Qué pasa con un archivo YAML con varios documentos separados por ---?
Se leen todos y se envuelven. Obtienes un solo elemento <documents> con un hijo <document> por documento YAML, y el panel de notas dice cuántos se encontraron.
La alternativa, que eligen la mayoría de los conversores, es convertir el primer documento e ignorar el resto en silencio. Ese es un mal valor por defecto para los manifiestos de Kubernetes, donde un archivo suele contener un Deployment, un Service y un ConfigMap, y perder dos de los tres no es algo que notes hasta que falla un despliegue.
¿Cómo se tratan las anclas, los alias y las claves de fusión?
Los resuelve el analizador y luego se expanden del todo en la salida. Un ancla marca un nodo, un alias se refiere a él, y una clave de fusión aplica un mapeo dentro de otro. Ninguno de los tres existe en XML y ninguno sobrevive.
Lo que obtienes es correcto pero puede ser mucho mayor que la entrada: un bloque base aliasado en cuarenta servicios produce cuarenta copias completas. Eso es lo que decía el YAML, solo que YAML te dejó escribirlo una vez. La expansión ocurre en la memoria de esta pestaña, así que un aliasado intenso puede ser lento.
¿Puedo obtener valores como atributos XML en lugar de elementos hijos?
Sí. Antepón a la clave en tu YAML el prefijo de atributos que se muestra en la fila de controles, que es @_ por defecto, así una clave '@_id' con el valor 7 se convierte en un atributo id del elemento que la encierra en vez de en un hijo <id>.
Tienes que entrecomillar esa clave. Un escalar plano no puede empezar por @, que YAML reserva, así que un @_id sin comillas es un error de análisis y el mensaje se quejará de la indentación en lugar del carácter. Pon ahí solo escalares: el valor de un atributo no puede contener estructura, así que un mapeo bajo una clave @_ produce un revoltijo convertido a cadena en vez de XML anidado.
¿Es válido el XML que produce?
Está bien formado, que es una afirmación distinta y más débil. Todo elemento está cerrado, hay exactamente una raíz, el ampersand, el signo de menor y la secuencia ]]> se escapan en el texto, los valores de atributo escapan además las comillas dobles y los caracteres de espacio en blanco, y se escribe una declaración UTF-8 arriba.
La validez significa coincidir con un esquema, y YAML no lleva ninguno del que derivarlo. Si el XML va a algún sitio que valida, llévalo al validador de XSD con el esquema que publique ese sistema.