Formateur XML
Indente, aligne et range. Le contenu mixte reste intact.
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 un document ci-dessus et il est réindenté à la frappe. Deux espaces, quatre espaces ou une tabulation, avec les attributs poussés sur leurs propres lignes dès qu’un élément en porte trois, quatre ou six. Le résultat apparaît dans le volet en lecture seule à côté de votre saisie. Rien n’est envoyé : l’analyseur et le formateur sont du JavaScript qui tourne dans cet onglet, et votre panneau réseau reste vide pendant que vous travaillez.
On sort un formateur quand le XML a été produit par autre chose : une réponse SOAP extraite d’un journal sous forme d’une seule ligne de 40 000 caractères, une configuration réécrite par un outil de déploiement, un sitemap généré par une machine. C’est aussi le contrôle de bonne formation le plus rapide qui soit, puisqu’un formateur ne peut pas indenter ce qu’il ne peut pas analyser.
Ce qui change ici, c’est le contenu mixte. Quand un élément contient à la fois du texte et des éléments enfants, l’espace entre eux est une donnée, et un formateur qui le range a modifié le document. Ces sous-arbres sont reproduits octet pour octet. La plupart des formateurs en ligne ne le font pas ; plusieurs relèvent de la manipulation de chaînes sur des chevrons plutôt que d’un analyseur.
Ce que la mise en forme a le droit de changer
Une seule chose est réellement insignifiante dans un document XML : l’espace entre éléments dans un modèle de contenu composé uniquement d’éléments. Un formateur peut le supprimer et générer le sien. Tout le reste est du contenu, et l’approche sûre consiste à traiter chaque octet comme du contenu jusqu’à preuve du contraire.
Les nœuds texte constitués uniquement d’espaces entre éléments frères sont donc supprimés puis régénérés à partir de votre réglage d’indentation, et rien d’autre n’est touché. Les valeurs d’attribut sont émises telles qu’écrites, car ré-échapper transformerait & en &, et une valeur référençant une entité déclarée dans la DTD comme &companyName; ne peut pas être décodée sans la DTD. Le guillemet d’origine est conservé lui aussi, puisqu’une valeur écrite entre apostrophes peut légalement contenir un guillemet double.
Le texte n’est jamais remis en forme intérieurement. Seuls les espaces de début et de fin d’un élément ne contenant que du texte sont supprimés, si bien que <price> 42.00 </price> devient <price>42.00</price> tandis que <note>deux espaces</note> garde les deux.
Le contenu mixte, et pourquoi il casse la plupart des formateurs
Un élément a un contenu mixte quand ses enfants comprennent à la fois du texte et du balisage : <p>Bonjour <b>tout le monde</b> !</p>. L’espace après « Bonjour » est un caractère du document, et le point d’exclamation après </b> aussi. Mettez chaque enfant sur sa propre ligne indentée et un consommateur qui concatène les nœuds texte obtient une autre chaîne. Ce n’est pas du rangement, c’est une corruption silencieuse.
Chaque élément est vérifié avant d’être sérialisé. S’il contient des éléments enfants à côté d’un texte non vide ou d’une section CDATA, le sous-arbre est recopié depuis votre source sans qu’aucune règle ne s’applique à l’intérieur. Le compromis est délibéré : un sous-arbre mixte conserve la disposition avec laquelle il est arrivé, même laide, parce que l’alternative est d’avoir tort.
Ce n’est pas un cas exotique : fragments XHTML dans un export de CMS, DocBook et DITA, xs:documentation à l’intérieur d’un schéma, une description RSS avec du balisage en ligne. Si votre document n’en contient aucun, cela ne vous coûte rien. S’il en contient, c’est tout l’enjeu.
- Mixte, donc reproduit tel quel : <line>Total : <amount>9,99</amount> HT</line>.
- Non mixte, donc réindenté librement : <order><id>1</id><status>open</status></order>.
Indentation et retour des attributs pour SOAP et XSD
Deux espaces par défaut, parce que c’est ce qu’émet la plupart de l’outillage XML. Quatre espaces existe parce que bon nombre de bases de code d’entreprise s’y sont standardisées. La tabulation existe parce que certains dépôts l’imposent dans leur .editorconfig, et parce qu’une tabulation vaut un octet là où quatre espaces en valent quatre.
Le réglage des attributs est celui qui compte pour SOAP et XSD. La racine d’une enveloppe SOAP porte couramment cinq déclarations d’espace de noms, et une déclaration xs:element porte name, type, minOccurs, maxOccurs, nillable et default : sur une seule ligne, cela fait 200 caractères que personne ne lira. Réglez le seuil à trois, quatre ou six et tout élément qui l’atteint reçoit un attribut par ligne, tandis que les éléments plus courts restent sur une seule.
Ce qu’il ne fera pas
Un document qui n’est pas bien formé n’est pas mis en forme : vous récupérez votre saisie inchangée et chaque erreur listée avec une ligne, une colonne et une correction. Deux autres limites méritent d’être dites franchement. xml:space="preserve" ne bénéficie d’aucun traitement particulier : un élément ne contenant que du texte et le portant voit tout de même ses espaces de début et de fin supprimés. Et un texte entrecoupé de commentaires, comme dans <a>texte<!-- pourquoi -->suite</a>, n’est pas du contenu mixte selon ce test, puisqu’un commentaire n’est pas un élément : il est donc réindenté et le texte gagne des espaces. Les deux cas sont étroits, les deux sont réels, et aucun n’est ce qu’un outil qui le ferait en silence vous dirait.
La mise en forme est idempotente : repasser la sortie avec les mêmes réglages produit les mêmes octets, c’est donc sûr dans un hook de pre-commit. Un document de 1 Mo prend environ 200 millisecondes et 5 Mo un peu moins d’une seconde. Le plafond est de 20 Mo, une limite de mémoire plutôt qu’une politique.
Mettre en forme du XML en code
La même opération dans les langages qui traitent réellement du XML. Chaque exemple analyse de façon sûre, car les valeurs par défaut de Java, de PHP et de la bibliothèque standard de Python résolvent les entités externes, et les commentaires indiquent où chaque bibliothèque remanie le contenu mixte.
// Browsers ship a parser and a serialiser but no pretty printer. This walker
// indents only elements whose children are all elements: touching anything
// else would rewrite mixed content.
function indentXml(source, unit = ' ') {
const doc = new DOMParser().parseFromString(source, 'application/xml');
if (doc.querySelector('parsererror')) {
throw new Error(doc.querySelector('parsererror').textContent.trim());
}
const walk = (el, depth) => {
const kids = [...el.childNodes];
const elementOnly =
kids.some((n) => n.nodeType === 1) &&
kids.every((n) => n.nodeType !== 3 || !n.nodeValue.trim());
if (!elementOnly) return; // mixed or text-only: leave the subtree alone
for (const n of kids) if (n.nodeType === 3) el.removeChild(n);
for (const child of [...el.children]) {
el.insertBefore(doc.createTextNode('\n' + unit.repeat(depth + 1)), child);
walk(child, depth + 1);
}
el.appendChild(doc.createTextNode('\n' + unit.repeat(depth)));
};
walk(doc.documentElement, 0);
return new XMLSerializer().serializeToString(doc);
}
// Browsers never resolve external entities, so XXE is not reachable here.
// Internal entity expansion is, so cap the input size before parsing.# ElementTree.indent (3.9+) only adds whitespace where an element has no
# non-whitespace text, so mixed content survives. It does drop comments,
# because the default parser never builds them.
import xml.etree.ElementTree as ET
from defusedxml.ElementTree import fromstring
root = fromstring(source) # safe: no entity expansion, no network
ET.indent(root, space=' ') # four spaces
print(ET.tostring(root, encoding='unicode'))
# lxml keeps comments and processing instructions, and etree.indent applies
# the same mixed-content rule:
#
# from lxml import etree
# parser = etree.XMLParser(resolve_entities=False, no_network=True,
# load_dtd=False, huge_tree=False)
# tree = etree.fromstring(source.encode(), parser)
# etree.indent(tree, space=' ')
# print(etree.tostring(tree, encoding='unicode'))
#
# Do not add remove_blank_text=True unless the document has a DTD. Without
# one, lxml guesses which blank text nodes are ignorable.import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import org.w3c.dom.*;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
Document doc = dbf.newDocumentBuilder().parse(new java.io.File("in.xml"));
// The serialiser adds indentation on top of the whitespace already in the
// tree, so an already-indented file gets deeper on every run. Remove the
// blank text nodes first. The second predicate keeps the blanks that sit
// inside mixed content, where a sibling text node carries real characters.
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList blanks = (NodeList) xpath.evaluate(
"//text()[not(normalize-space())][not(../text()[normalize-space()])]",
doc, XPathConstants.NODESET);
for (int i = 0; i < blanks.getLength(); i++) {
Node n = blanks.item(i);
n.getParentNode().removeChild(n);
}
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer t = tf.newTransformer();
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
t.transform(new DOMSource(doc), new StreamResult(System.out));using System.Text;
using System.Xml;
using System.Xml.Linq;
// XDocument.Parse discards whitespace-only text nodes by default, which is
// what you want for element-only content. On mixed content it also removes
// the space in <p>a <b>x</b> <i>y</i></p>, so pass
// LoadOptions.PreserveWhitespace when the document carries prose.
var doc = XDocument.Parse(source);
// DtdProcessing is Prohibit by default for the reader XDocument builds, so
// external entities are never fetched. Say it out loud when you construct
// the reader yourself.
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
OmitXmlDeclaration = false,
};
var output = new StringBuilder();
using (var writer = XmlWriter.Create(output, settings))
{
doc.Save(writer);
}
Console.WriteLine(output.ToString());
// XmlWriter stops indenting an element once character data has been written
// into it, so it will not reflow mixed content it is given.<?php
$doc = new DOMDocument();
// Both flags must be set before loading. preserveWhiteSpace = false makes
// libxml2 drop blank text nodes; without a DTD it applies a heuristic, and
// that heuristic keeps blanks whose siblings carry real text, which is what
// protects mixed content. Run it on a copy and diff the first time.
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
libxml_use_internal_errors(true);
if (!$doc->loadXML($source, LIBXML_NONET)) {
foreach (libxml_get_errors() as $e) {
fprintf(STDERR, "XML error at line %d, column %d: %s\n",
$e->line, $e->column, trim($e->message));
}
libxml_clear_errors();
exit(1);
}
echo $doc->saveXML();# xmllint is part of libxml2 and is almost certainly already installed.
# --nonet stops it fetching a DTD the document references.
xmllint --format --nonet document.xml
# The indent unit comes from an environment variable, not a flag:
XMLLINT_INDENT=' ' xmllint --format --nonet document.xml
# Rewrite in place:
xmllint --format --nonet --output document.xml document.xml
# xmllint refuses to format a document that is not well-formed: it prints the
# first error and exits non-zero, leaving the output file untouched.
# libxml2 will not indent an element that has a text child, which is the same
# mixed-content rule this page applies.Remarquez le motif : libxml2, le XmlWriter de .NET et le ET.indent de Python refusent tous d’indenter un élément contenant des données caractère. Les recettes qui tournent mal sont celles qui suppriment d’abord les nœuds texte blancs sans discernement, ce qui est aussi le défaut de tout formateur bâti sur une expression régulière, puisqu’une expression régulière ne voit pas de modèle de contenu.
Questions fréquentes
Mon XML est-il envoyé quand je le mets en forme ?
Non. L’analyseur et le formateur sont du JavaScript exécuté dans cet onglet, dans un Web Worker. Il n’y a pas de composant serveur auquel envoyer quoi que ce soit, pas d’analytics ayant accès à l’éditeur et pas de scripts tiers.
Ouvrez vos outils de développement, passez sur l’onglet Réseau et mettez un document en forme : la page charge ses propres ressources une fois, puis plus rien. Cela compte ici, parce que les documents qui ont le plus besoin d’être remis en forme sont ceux extraits des journaux de production. Votre saisie est conservée dans le localStorage de ce navigateur pour qu’un rafraîchissement ne la perde pas, et Effacer la supprime.
La mise en forme modifie-t-elle mes données ?
Pas les données. Les valeurs d’attribut sont recopiées telles qu’écrites, y compris les références d’entités et le guillemet d’origine. Le CDATA n’est jamais converti en texte échappé. Les commentaires, les instructions de traitement et le sous-ensemble DTD interne survivent, et le texte à l’intérieur d’un contenu mixte est reproduit octet pour octet.
Il y a un endroit où des caractères sont retirés : les espaces de début et de fin d’un élément ne contenant que du texte. Les espaces au milieu d’un nœud texte ne sont jamais réduits. Ce nettoyage s’applique aussi à un élément portant xml:space="preserve", vérifiez-les donc si vous en dépendez.
Puis-je indenter avec 4 espaces ou des tabulations plutôt que 2 ?
Oui. Le réglage d’indentation propose deux espaces, quatre espaces et la tabulation, et le choix s’applique à tout le document, y compris au niveau supplémentaire utilisé quand les attributs passent sur leurs propres lignes.
Choisissez selon la destination : si le fichier vit dans un dépôt avec un .editorconfig, alignez-vous dessus. Si la taille compte parce que le document est intégré quelque part, une tabulation fait un octet par niveau au lieu de quatre.
Pourquoi le formateur m’a-t-il rendu mon document inchangé ?
Parce qu’il n’était pas bien formé. La mise en forme exige une analyse, donc la saisie est rendue intacte et les erreurs sont listées à la place, avec la ligne, la colonne et ce qu’il faut y écrire.
Les coupables habituels sont une esperluette brute dans une chaîne de requête d’URL, une balise non fermée, une balise fermante dont le nom ne correspond pas à l’ouvrante, et deux éléments racine issus de fragments concaténés. xmllint se comporte de la même façon, ce qui explique pourquoi « xmllint ne veut pas formater mon fichier » est une recherche si courante.
Qu’advient-il des sections CDATA et des commentaires ?
Les sections CDATA passent intactes : les délimiteurs restent et les octets entre eux ne sont ni échappés, ni rognés, ni réindentés. Vérifiez-le sur tout formateur que vous utilisez, car convertir le CDATA en texte échappé est une option que certains outils prennent par défaut, et le document que vous récupérez n’est alors plus celui que vous aviez collé.
Les commentaires sont conservés par défaut, avec une case pour les retirer. Réfléchissez avant de la cocher : dans les fichiers XSLT, Maven et Ant, ils sont souvent la seule explication que quelqu’un ait écrite.
Quelle taille de document peut-il mettre en forme ?
Jusqu’à 20 Mo. Un document de 1 Mo est mis en forme en 200 millisecondes environ et 5 Mo en un peu moins d’une seconde, dans un Web Worker pour que l’éditeur ne gèle pas.
La limite existe parce que tout tourne dans cet onglet : il n’y a pas de serveur à qui confier un gros fichier, et au-delà de 20 Mo l’arbre d’analyse peut occuper plusieurs centaines de mégaoctets et le navigateur cesse de répondre. Pour plus grand, xmllint --format sur votre machine applique la même règle sur le contenu mixte.