Conversor de XML para YAML
Converte para YAML, com os valores arriscados entre aspas.
Tudo roda nesta aba. Nada do que você colar é enviado, registrado ou transmitido para lugar nenhum. Abra o painel de rede e confira.
Cole XML acima e o YAML aparece ao lado. O documento é verificado quanto à boa formação, mapeado para uma árvore, e então escrito por um emissor cujo trabalho principal é decidir quais valores precisam de aspas. Nada é enviado: o analisador, o mapeador e o emissor rodam todos nesta aba.
A razão habitual para querer isto é que um arquivo de configuração, um manifesto de Kubernetes, um pipeline de CI ou um inventário do Ansible precisa de dados que hoje moram em XML. A saída vai para um arquivo que uma máquina lê ao pé da letra, e é por isso que as aspas importam mais que o layout.
O YAML parece o formato simpático e é o que tem mais chance de mudar os seus dados em silêncio. Um código de país NO sem aspas vira o booleano false na maior parte do ecossistema. Um CEP 01730 vira 1730. Uma versão 1.10 vira 1.1. Este emissor põe aspas nos valores que de outro modo seriam lidos errado, e esta página diz exatamente quais e por quê.
O mapeamento é o mapeamento de XML para JSON
O YAML 1.2 foi desenhado como um superconjunto do JSON, então aqui não há uma árvore separada. O XML é convertido para a mesma estrutura que a página de XML para JSON produz e um serializador diferente a escreve. Cada decisão de mapeamento daquela página se aplica sem mudança: atributos viram chaves com prefixo, texto que divide um elemento com atributos ou filhos vai sob uma chave de texto, um elemento que aparece duas vezes vira uma sequência, e comentários são descartados.
Uma coisa piora. Em JSON quem consome pelo menos vê colchetes; em YAML a diferença entre um item e dois é um escalar indentado contra uma lista de traços, e ninguém nota isso num diff. Use o campo «sempre um vetor» para qualquer coisa que conceitualmente seja uma lista, para que um documento de um item e um de cinquenta produzam a mesma 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'O problema da Noruega, e a lista exata que ele cobre
O YAML 1.1 define o seu tipo booleano por enumeração, e a enumeração é mais larga do que qualquer um espera. A página de tipos publicada lista, literalmente: y, Y, yes, Yes, YES, n, N, no, No, NO, true, True, TRUE, false, False, FALSE, on, On, ON, off, Off, OFF. Cada um desses, sem aspas, carrega como booleano.
A consequência tem nome. Um conjunto de códigos de país ISO recebe NO para a Noruega e o analisador entrega false à aplicação. A mesma lista engole uma coluna Sim/Não exportada de uma planilha e qualquer chave escrita on ou off que devia ser texto. O YAML 1.2 estreitou o esquema central para apenas true e false, mas PyYAML, o Psych do Ruby, o Ansible e boa parte do ferramental de Kubernetes ainda resolvem o conjunto do 1.1, então suponha que ele está todo vivo.
O emissor põe aspas simples em qualquer escalar que casa exatamente com essa lista, incluindo as formas de uma só letra, mais null, Null, NULL e o til. Note a sensibilidade a maiúsculas: yES e nO não estão na lista do 1.1 e não recebem aspas, porque nenhum analisador conforme os lê como booleanos tampouco.
O que mais recebe aspas, e o que escapa
O conjunto booleano é o caso famoso, não o comum. A maioria dos valores que quebram são números que nunca foram números, porque o YAML infere um tipo da grafia de um escalar simples exatamente do jeito que o JSON não faz. Um escalar recebe aspas simples quando casa com o conjunto booleano ou nulo, quando casa com uma gramática de número de JSON (cobrindo 42, 19.90 e 1.10), quando tem um zero à esquerda seguido de mais dígitos, quando está vazio, quando começa por um caractere indicador de YAML como um hífen ou uma cerquilha, ou quando tem espaço em alguma das pontas.
Texto de várias linhas não recebe aspas. Ele vira um escalar de bloco literal introduzido por uma barra vertical com indicador de corte. Literal foi escolhido em lugar do dobrado de propósito: um bloco dobrado reflui quebras de linha simples para espaços, destruindo código e endereços embutidos. O indicador de corte remove a quebra de linha final que um bloco acrescentaria.
Alguns valores ainda saem do emissor sem aspas e podem mudar de tipo rio abaixo. Eles são listados em vez de abafados, porque nenhum emissor que use escalares simples resolveu a inferência de tipos do YAML:
- Números sexagesimais. O YAML 1.1 lê 22:22 como um inteiro em base 60, então uma duração vira 1342 no PyYAML. Um analisador 1.2 como o js-yaml devolve a cadeia, então isso depende de qual lado lê o arquivo.
- Grafias hexadecimais. 0x1F carrega como 31 tanto no YAML 1.1 quanto no esquema central do 1.2, então um código de cor hexadecimal precisa de aspas.
- Datas. 2024-01-05 casa com o tipo timestamp do YAML, então js-yaml e PyYAML ambos lhe entregam um objeto de data em vez de uma cadeia.
- Escalares de bloco cuja primeira linha está indentada mais que as linhas seguintes, o que acontece quando uma seção CDATA preserva espaços iniciais. O conserto do YAML é um indicador de indentação explícito depois da barra vertical, que este emissor não escreve.
Defina o prefixo de atributos e a chave de texto antes de converter
Esta é a única preparação que vale a pena fazer. Os padrões foram escolhidos para JSON, onde são seguros, e o YAML tem uma gramática mais rigorosa para chaves do que para valores.
O prefixo de atributos padrão é @_ e a chave de texto padrão é #text. Em YAML, @ é um indicador reservado com o qual um escalar simples não pode começar, então uma chave @_id faz o documento não ser analisável: o js-yaml relata «bad indentation of a mapping entry» e o PyYAML relata um caractere que não pode iniciar nenhum token. Um # inicial é pior porque não falha. Uma linha dizendo #text: 19.90 é um comentário, então o arquivo carrega e o valor simplesmente não está lá.
Os dois campos ficam na linha de controles acima do editor. Ponha o prefixo em algo simples como attr_ e a chave de texto em text, e toda chave da saída será um nome YAML comum. Chaves que precisam de aspas por outros motivos, como soap:Body, recebem aspas automaticamente, porque dois-pontos não são legítimos numa chave nua.
Fazer isso em código
Dois passos: analisar o XML com segurança, e então serializar com um dumper a quem você disse para pôr aspas como deve. A metade do XML precisa das bandeiras de entidade de sempre, porque os padrões em Java e .NET resolvem um DOCTYPE. A metade do YAML pede atenção porque os dumpers divergem em quão agressivamente põem aspas.
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: FalseA falha de que esta página trata é silenciosa. Um arquivo YAML com um NO sem aspas é analisado limpo, validado limpo e implantado limpo; o país simplesmente é false daí em diante. A verificação que pega isso é carregar o arquivo gerado de volta com a mesma biblioteca que o consumidor usa e comparar um valor que se sabe ser incômodo, não ler o diff.
Perguntas frequentes
Meu XML é enviado quando eu o converto para YAML?
Não. O analisador de XML, o mapeador de árvore e o emissor de YAML são todos JavaScript nesta aba, e não há endpoint para onde postarem. Abra a aba Rede nas suas ferramentas de desenvolvedor, cole um documento, e veja nada acontecer.
Vale confirmar em vez de supor, porque XML convertido para YAML é muito frequentemente configuração. Cadeias de conexão, contas de serviço, chaves de API e nomes de host internos todos acabam no tipo de documento que as pessoas levam a um conversor.
O que é o problema da Noruega?
O YAML 1.1 define o seu tipo booleano como uma lista fixa de grafias, e essa lista inclui n, N, no, No e NO. Então um campo com o código ISO da Noruega, escrito sem aspas, carrega como false. A mesma lista engole y e Y, on e off, e qualquer coluna Sim/Não exportada de uma planilha.
O YAML 1.2 estreitou o esquema central para apenas true e false, o que não consertou o ecossistema: PyYAML, Psych, Ansible e boa parte do ferramental de Kubernetes ainda resolvem o conjunto do 1.1, e raramente você controla qual analisador lê o seu arquivo. O emissor põe aspas em todas as grafias dessa lista, então NO continua sendo a cadeia NO.
Por que alguns valores vêm entre aspas e outros não?
Porque as aspas são estruturais. Um escalar YAML simples tem o tipo inferido de como está grafado, então 01730 é um número, 1.10 é um flutuante, NO é um booleano, e um hífen inicial começa um item de lista. Pôr aspas é o jeito de dizer que é texto.
O emissor põe aspas exatamente nos valores que de outro modo mudariam de tipo ou de sentido e deixa todo o resto simples, porque pôr aspas em cada escalar deixa o arquivo mais difícil de ler e comparar sem nenhum ganho. Para aspas uniformes, a maioria das bibliotecas de YAML tem uma opção de forçar aspas; os exemplos acima a mostram para js-yaml e YamlDotNet.
O que acontece com conteúdo de texto de várias linhas?
Vira um escalar de bloco literal, introduzido por uma barra vertical com indicador de corte, com as linhas indentadas abaixo. Literal foi escolhido em lugar do dobrado de propósito: um bloco dobrado reflui quebras de linha simples para espaços, destruindo em silêncio código e endereços embutidos.
Um caso para vigiar. Se a primeira linha do texto está indentada mais que as seguintes, o que acontece quando uma seção CDATA preserva espaços iniciais, o bloco é ambíguo e um analisador vai rejeitá-lo.
Elementos repetidos viram listas de YAML?
Sim. Um elemento que aparece mais de uma vez sob o mesmo pai vira uma sequência escrita como lista de traços; um que aparece uma vez vira um mapeamento aninhado simples ou um escalar. É a mesma ambiguidade do singleton que a página de XML para JSON descreve, e aqui é mais perigosa porque o YAML a esconde: a diferença entre um item e dois é um traço e dois espaços de indentação.
Use o campo «sempre um vetor» acima do editor. Nomeie os elementos que conceitualmente são listas e eles são emitidos como sequências, quer o documento traga um deles, quer quarenta.
Comentários e espaços de nomes do XML são preservados?
Comentários não. Eles são descartados quando o documento é mapeado para uma árvore, antes de o emissor ver qualquer coisa. Comentários de YAML não fazem parte do modelo de dados, então um escrito na saída desapareceria na primeira vez que alguém carregasse e salvasse o arquivo de novo.
Prefixos de espaço de nomes são mantidos literalmente, então soap:Body vira uma chave grafada soap:Body, com aspas automáticas porque dois-pontos não são legítimos numa chave YAML nua. Marcar «remover prefixos de espaço de nomes» dá um Body simples, ao risco de fundir dois espaços de nomes numa só chave.