Conversor de YAML para XML
Converte YAML em XML, inteiramente no seu navegador.
Tudo roda nesta aba. Nada do que você colar é enviado, registrado ou transmitido para lugar nenhum. Abra o painel de rede e confira.
Cole YAML acima e ao lado aparece XML indentado e bem formado, com declaração e cada caractere especial escapado. Se o YAML não for analisável, você recebe a primeira linha de reclamação do próprio analisador em vez de um painel em branco, e se alguma chave precisou ser renomeada para virar um nome de elemento XML legítimo, a ferramenta lista o que mudou.
Esta é a direção de que você precisa quando algo antigo tem de ler algo novo: uma integração que só aceita XML, um endpoint SOAP, um formato de troca validado por XSD, uma fixture para um serviço configurado em YAML. Tem um número surpreendente de arestas, e quase todas vêm do YAML, não do XML.
A análise é feita pelo js-yaml, carregado só quando você abre esta página, e não em todas as páginas do site. Tudo roda nesta aba e nada é enviado, o que importa porque o YAML é onde mora a configuração e a configuração é onde moram as credenciais.
Como os três tipos de nó do YAML são mapeados
O YAML tem exatamente três tipos de nó e cada um tem um correspondente em XML. Um mapeamento vira um conjunto de elementos filhos, um por chave, com a chave como nome do elemento. Uma sequência repete o nome do elemento pai uma vez por membro, sem invólucro, porque a repetição é como o XML expressa uma lista. Um escalar vira o conteúdo textual do seu elemento.
Em cima disso está o problema da raiz. O YAML permite qualquer nó no topo de um documento; o XML exige exatamente uma raiz. Um mapeamento com uma única chave já tem uma raiz natural, então essa chave vira o elemento raiz. Um mapeamento com duas ou mais chaves, uma sequência de topo ou um escalar cru são embrulhados num único elemento, chamado root por padrão e editável na linha de controles.
Vale conhecer duas consequências da regra das sequências. Uma sequência vazia não produz nada, então a chave some: zero repetições de um elemento são zero elementos. E uma sequência aninhada diretamente dentro de outra sequência achata, porque a interna não tem um nome próprio para 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>O YAML já decidiu os seus tipos antes de o XML vê-los
Esta é a coisa mais importante da página e não é uma propriedade deste conversor. O YAML resolve um escalar simples para um tipo com base em como ele está grafado, dentro do analisador. Quando um valor chega ao escritor de XML ele já é um número, um booleano, uma data ou uma cadeia, e o XML não tem sistema de tipos com que recuperar a distinção.
O js-yaml implementa o esquema central do YAML 1.2 mais o tipo timestamp, o que produz o seguinte. Você pode conferir cada linha colando o valor:
- true e false são booleanos e são escritos como o texto true e false. yes e no aqui continuam cadeias, mas um analisador de YAML 1.1 como PyYAML ou Ansible lê no como false, então o mesmo arquivo convertido com ferramentas diferentes produz XML diferente.
- Zeros à esquerda somem antes da conversão: 01730 resolve para o número 1730, e não há nada que o escritor de XML possa fazer. Escreva '01730'.
- Outras bases também são resolvidas, então 0x1F vira 31 e é escrito como <hex>31</hex>. Códigos de cor hexadecimais e identificadores de hardware precisam de aspas.
- Um inteiro de YAML vira um double no navegador, então um identificador de dezenove dígitos já perdeu os dígitos baixos antes de o escritor entrar em cena. Ponha aspas em identificadores, sempre.
- Datas resolvem para timestamps, e um timestamp não tem representação textual que o escritor possa produzir, então 2024-01-05 sai como um <when/> vazio. Ponha aspas e ele é escrito como texto.
Chaves de YAML frequentemente não são nomes XML legítimos
A seção 2.3 do XML 1.0 diz que um nome de elemento começa com uma letra, um sublinhado ou dois-pontos e segue com esses mais dígitos, hifens e pontos. Uma chave de YAML não tem essa restrição: «2024 total», «user@email» e a cadeia vazia são todas chaves comuns e nenhuma pode ser um nome de elemento.
Cada uma é renomeada em vez de rejeitada, e toda renomeação é relatada. Caracteres ilegítimos são substituídos um a um por um sublinhado, e um nome que ainda começa com dígito ganha um sublinhado à frente. Substituir em vez de remover é deliberado: remover tornaria «2024 total» e «2024total» o mesmo elemento e fundiria dois campos distintos. Então «2024 total» vira _2024_total, «2024-total» vira _2024-total porque o hífen já é legítimo, e «user@email» vira user_email. Não é estanque: «first name» e «first_name» pousam ambos em first_name, então renomeie chaves que diferem apenas na pontuação.
O YAML também permite chaves que não são cadeias: 2024 é um inteiro, true é um booleano, e a sintaxe de chave explícita permite uma sequência inteira como chave. Todas são convertidas em cadeia antes de virarem nomes de elemento. Uma esquisitice: chaves que parecem índices de vetor são enumeradas primeiro e em ordem numérica crescente, então um mapeamento misturando 2, 10 e name não vai emitir os elementos na ordem em que você os escreveu.
Fluxos de vários documentos, âncoras e chaves de mesclagem
Um fluxo de YAML pode ter vários documentos separados por três hifens, e manifestos de Kubernetes fazem isso rotineiramente. O XML tem exatamente uma raiz, então todos são lidos e embrulhados: um único elemento <documents> com um filho <document> por documento YAML, e uma nota dizendo quantos foram encontrados. A maioria dos conversores trunca em silêncio no primeiro, o que você descobre em produção quando dois terços de um manifesto somem sem barulho.
Âncoras, apelidos e chaves de mesclagem são resolvidos pelo analisador e já não existem quando o XML é escrito. O que você recebe é o resultado totalmente expandido, que é correto e pode ser bem maior que a entrada: um bloco base apelidado em quarenta serviços produz quarenta cópias. Essa expansão acontece na memória desta aba, então apelidamento pesado pode ser lento, e o teto de tamanho vale tanto para a saída quanto para a fonte.
Comentários não são preservados, porque não fazem parte do modelo de dados do YAML e o analisador nunca os entrega. Um fluxo vazio, ou que contenha só comentários, é relatado como vazio em vez de convertido num elemento raiz vazio.
Fazer isso em código
Dois passos em toda linguagem: carregar o YAML com um carregador seguro, e então escrever XML com algo que escape direito. Aqui a bandeira de segurança está do lado do YAML, não do XML. Várias bibliotecas de YAML, por padrão ou por uma única etiqueta no documento, instanciam classes arbitrárias a partir do arquivo, o que é execução remota de código fantasiada de configuração.
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 -A bandeira a acertar em todos esses é a do carregador, não a do escritor. yaml.load no Python, o Constructor padrão do SnakeYAML no Java e yaml_parse com yaml.decode_php habilitado todos constroem objetos arbitrários a partir de etiquetas do documento. Um arquivo YAML é dado até você usar um carregador que o deixe ser outra coisa.
Perguntas frequentes
Meu YAML é enviado para algum lugar?
Não. O analisador de YAML e o escritor de XML são ambos JavaScript rodando nesta aba, e não há componente de servidor que eles possam alcançar. Abra a aba Rede nas suas ferramentas de desenvolvedor, cole um documento, e você verá os recursos da própria página carregarem uma vez e depois nada.
Vale conferir aqui em especial. O YAML é onde mora a configuração: segredos de Kubernetes, variáveis de pipeline de CI, inventários do Ansible com nomes de host e de usuário, arquivos compose com senhas de banco de dados dentro.
Por que meu CEP, número de versão ou ID mudou?
Porque foi o YAML que mudou, não o escritor de XML. O YAML infere o tipo de um escalar de como ele está grafado, então 01730 é o número 1730, 1.10 é o flutuante 1.1, e um identificador de dezenove dígitos não cabe num double. Tudo isso acontece dentro do analisador de YAML, antes de qualquer coisa relacionada a XML rodar.
O conserto está no YAML: ponha aspas no valor. '01730', '1.10' e '9007199254740993' chegam todos como cadeias e são escritos exatamente como digitados. Se um gerador produziu o YAML, ele é que deveria ter posto aspas.
O que acontece com um arquivo YAML com vários documentos separados por ---?
Todos são lidos e embrulhados. Você recebe um único elemento <documents> com um filho <document> por documento YAML, e o painel de notas diz quantos foram encontrados.
A alternativa, que a maioria dos conversores escolhe, é converter o primeiro documento e ignorar o resto em silêncio. Esse é um péssimo padrão para manifestos de Kubernetes, onde um arquivo rotineiramente guarda um Deployment, um Service e um ConfigMap, e perder dois dos três não é coisa que se note até uma implantação falhar.
Como âncoras, apelidos e chaves de mesclagem são tratados?
São resolvidos pelo analisador e depois totalmente expandidos na saída. Uma âncora marca um nó, um apelido se refere de volta a ele, e uma chave de mesclagem aplica um mapeamento dentro de outro. Nenhum dos três existe em XML e nenhum sobrevive.
O que você recebe é correto, mas pode ser muito maior que a entrada: um bloco base apelidado em quarenta serviços produz quarenta cópias completas. Foi isso que o YAML quis dizer; só que o YAML deixou você escrever uma vez só. A expansão acontece na memória desta aba, então apelidamento pesado pode ser lento.
Posso obter valores como atributos XML em vez de elementos filhos?
Pode. Ponha antes da chave no seu YAML o prefixo de atributos mostrado na linha de controles, que é @_ por padrão, de modo que uma chave '@_id' com o valor 7 vira um atributo id no elemento que a envolve, e não um filho <id>.
Você tem de pôr aspas nessa chave. Um escalar simples não pode começar com @, que o YAML reserva, então um @_id sem aspas é erro de análise e a mensagem vai reclamar da indentação, não do caractere. Ponha ali apenas escalares: o valor de um atributo não pode conter estrutura, então um mapeamento sob uma chave @_ produz uma bagunça convertida em cadeia em vez de XML aninhado.
O XML que ele produz é válido?
Ele é bem formado, o que é uma afirmação diferente e mais fraca. Todo elemento é fechado, há exatamente uma raiz, o e comercial, o sinal de menor e a sequência ]]> são escapados no texto, valores de atributo escapam além disso a aspa dupla e os caracteres de espaço, e uma declaração UTF-8 é escrita no topo.
Validade quer dizer casar com um esquema, e o YAML não carrega nenhum de onde derivar um. Se o XML vai para algum lugar que valida, leve-o ao validador de XSD com o esquema que aquele sistema publica.