結論

XML の整形は、XML として正しいかを確かめたうえで、要素ごとに改行し、入れ子の深さに合わせて字下げする作業です。下のツールに貼るとすぐ整形でき、誤りがあれば何行目・何文字目かを示します。コマンドなら xmllint --format がいちばん手軽です。

この記事でわかること

  • XML 整形ツールの使い方と、xmllint・Python・Java での整形のしかた
  • 整形で空白が消える・空行が増える・意味が変わる場合と、C14N での確かめ方
  • よくある XML エラーの原文(libxml2・Python・Java・ブラウザ)と直し方

2026年11月10日更新|xmllint・Python・Java・Chromium で同じ XML を整形して出力を比較

XML の整形を読み込み中です。

ツールの使い方:上の入力欄に XML を貼ると、ブラウザの XML パーサー(DOMParser)で検証し、正しければ字下げした結果を下に出します。「整形のしかた」で「スペース2つ」「スペース4つ」「タブ」「1行に圧縮」を選べ、「コメントを消す」にチェックを入れるとコメントを取り除きます。<?xml ?> 宣言・属性・名前空間・CDATA・コメント・処理命令は残ります。文字とタグが混ざった要素(例:<p>文の<b>一部</b>だけ</p>)と xml:space="preserve" の要素は、中身を字下げせずにそのまま残し、そのことを注意として表示します。文字だけの要素の中身は、前後の空白も含めて書いたとおりに残ります。DOCTYPE は内部の定義([ ] の中)も含めて書いたとおりに出力し、DOCTYPE で定義した実体(&名前;)は中身に置き換えた形で表示して、その旨を注意で示します。正しい XML なら要素の数と深さを、誤りがあればエラーの行・文字目と前後の行、よくある原因を出します。見本のボタン(pom.xml・閉じタグの間違い・& が入っている など)で動きを試せます。外部の DTD やファイルは読み込まず、入力した XML はどこにも送信されません。

この記事で実際に動かしたもの:このページのツール(Chromium で表示)、xmllint、xmlstarlet、Python の xml.dom.minidom・xml.etree.ElementTree・lxml、Java の Transformer。

flowchart TD
    A[XML を貼る] --> B{XML として<br>正しい?}
    B -->|いいえ| C[行・文字目を<br>見て直す]
    C --> A
    B -->|はい| D[字下げして出力]
    D --> E{空白に<br>意味がある?}
    E -->|ない| F[そのまま使う]
    E -->|ある| G[C14N で<br>整形前と比べる]

よく調べられることの答えを先にまとめます。

やりたいこと いちばん早い方法 詳しい節
XML を整形したい(すぐ) このページのツールに貼る。送信されない XML整形ツールの使い方
コマンドで XML を整形したい xmllint --format a.xml xmllintでXMLを整形する
Python で XML を整形したい ElementTree.indent(3.9以降)か lxml の pretty_print PythonでXMLを整形する
Java で XML を整形したい Transformer に INDENT=yes と indent-amount JavaでXMLを整形する
VS Code で XML を整形したい XML の拡張機能を入れて Format Document PowerShell・VS CodeでXMLを整形する
整形したら空行だらけになった 元の改行と字下げを消してから整形する PythonとJavaの節
整形で中身が変わっていないか確かめたい 空白だけのテキストを除いて C14N で比べる 整形しても内容が同じか確かめる
XML のエラーの意味が分からない 原文で下の表を引く。行と文字目の前後を見る よくあるXMLエラーの原文と直し方

確認環境:Ubuntu 24.04.5 LTS の上で、このページのツール(サイトのテーマの JavaScript)を Chromium 153.0.8010.12(Playwright 1.63.0)で開き、同じ XML を入れて出力・エラー表示を取りました。コマンドは xmllint(libxml2 2.9.14、Ubuntu の libxml2-utils パッケージ)、xmlstarlet 1.6.1(Ubuntu の .deb を作業場所に展開)、Python 3.13.18(xml.dom.minidom・xml.etree.ElementTree、expat 2.9.0)、lxml 6.1.3(作業場所の venv に pip で導入)、OpenJDK 21.0.12.1(javax.xml.transform.Transformer)で実行しています。整形前後の比較には xmllint の C14N(正規化)を使いました。PowerShell(.NET)と VS Code は手元で動かせないため、Microsoft の .NET API ドキュメント、VS Code の公式ドキュメント、Red Hat の XML 拡張機能の公式ドキュメントで確かめた範囲だけを書いています。速さは CPU 2コアのマシンで5つの作業が同時に動く中での1回の値です。図は mermaid 11.16.0 で描画を確かめました(確認日 2026年11月9日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「XML整形ツール|見やすくする方法とエラーの直し方・行番号つき」

XML整形とは(何が変わり、何が変わらないか)

この節では、XML 整形が何をする作業か、「整形」と「整形式(well-formed)」の違いが分かります。

XML 整形(フォーマット・pretty print)とは、要素の並びと中身はそのままに、改行と字下げを足して人が読みやすい形にすることです。API の応答や設定ファイルの XML は、1行に詰まっていることがよくあります。次は Maven の pom.xml を1行にしたものです。

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion><groupId>jp.example</groupId><artifactId>demo</artifactId><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency></dependencies></project>

このページのツール(スペース2つ)に貼ると、次のようになりました。xmllint --format の出力と1文字も違わない結果です。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>jp.example</groupId>
  <artifactId>demo</artifactId>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

整形すると、次の3つがすぐ読み取れるようになります。

1親子関係

dependency が dependencies の子だと、字下げの深さで分かる。閉じタグの位置も縦にそろう。

2誤りの有無

整形ツールは最初に XML として読むため、閉じ忘れや & の書き方の誤りがその場で見つかる。

3差分の読みやすさ

1行に1要素なので、値を1つ変えたときの Git の差分が1行で済む。

「整形」と「整形式」は別の言葉

XML の用語には「整形式(well-formed)」という言葉があり、こちらは見た目ではなく「XML の文法の決まりを守っている」という意味です。ルート要素が1つ、開始タグと終了タグが対応している、属性の値が引用符で囲まれている、などの条件を満たした XML を整形式の XML と呼びます。字下げが無くても整形式の XML はありますし、きれいに字下げしてあっても閉じタグが合わなければ整形式ではありません。

言葉 意味 見た目との関係
整形(フォーマット) 改行と字下げを足して読みやすくする作業 見た目だけを変える
整形式(well-formed) XML の文法の決まりを守っている状態 見た目とは関係ない
妥当(valid) 整形式で、さらに DTD やスキーマの決まりにも合う状態 見た目とは関係ない

整形ツールは、まず整形式かどうかを確かめ、整形式でなければ整形できません。このページのツールも同じで、整形式でない XML にはエラーの場所を示します。XML の宣言・タグ・属性の読み方そのものは、関連記事「XMLファイルの開き方|中身の読み方・文字化けと開けない時の直し方」で扱っています。

XML整形ツールの使い方とできること・注意点

この節では、このページの XML 整形ツールが何を残し、何を変えるのか、手元で確かめた動きが分かります。

ツールは、ブラウザの XML パーサーで XML を読み込んでから、要素を1つずつ書き出して字下げします。文字列の置換で改行を入れているわけではないため、タグに見える文字が CDATA やコメントの中にあっても崩れません。属性・CDATA・コメント・処理命令・混在内容を含む次の XML で確かめました。

<root a="1" b="x &amp; y"><!-- 設定 --><?app mode="dev"?><script><![CDATA[if (a < b) {}]]></script><p>文の<b>一部</b>だけ太字</p><empty></empty></root>

スペース2つで整形した結果です。表示は「正しい XML です(要素 5 個・深さ 3)。」で、「文字とタグが混ざった要素と xml:space="preserve" の要素(1 か所)は、空白が意味を持つことがあるため、中身を字下げせずそのまま残しました。」という注意も出ました。

<root a="1" b="x &amp; y">
  <!-- 設定 -->
  <?app mode="dev"?>
  <script><![CDATA[if (a < b) {}]]></script>
  <p>文の<b>一部</b>だけ太字</p>
  <empty/>
</root>

手元で確かめた動きを表にまとめます。

入力に含まれるもの ツールの出力
<?xml ?> 宣言 先頭にそのまま残る(encoding も残る)
属性・名前空間(xmlns) 順番も値も残る。&amp; も &amp; のまま
CDATA <![CDATA[ ]]> のまま残り、中身は変わらない
コメント・処理命令 1行ずつ字下げして残る。「コメントを消す」で取り除ける
文字とタグが混ざった要素・xml:space="preserve" の要素 中身を字下げせず書いたとおりに残し、注意を出す
文字だけの要素の前後の空白 消さずにそのまま残す
DOCTYPE(内部の定義を含む) 書いたとおりに残す。定義した実体は中身に置き換えて表示し、注意を出す
中身が空の要素 <empty></empty> は <empty/> になる
1行に圧縮 要素の間の改行と空白を取り除いて1行にする
元の字下げ・空行 いったん捨ててから字下げし直す(空行は残らない)

「タブ」を選ぶと字下げがタブ文字1つずつになり、「スペース4つ」では4つずつになりました。字下げ済みの XML や空行の多い XML を入れても、二重に字下げされることはありませんでした。

空白と DOCTYPE の扱い(手元で確かめた結果)

空白や DTD に意味がある XML で気になる2点を、手元で確かめました。

1前後の空白は残る

<name> 前後に空白 </name> も <code xml:space="preserve"> 2つの空白 </code> も、空白を含めて書いたとおりに出力された。

2DOCTYPE は書いたとおり、実体は展開して表示

<!DOCTYPE note [ <!ENTITY co "RJC"> ]> はそのまま残り、本文の &co; は RJC と表示された。「DOCTYPE で定義した実体(&名前;)は、中身に置き換えた形で表示しています。」という注意も出た。

実体を &co; の形のまま残したいファイル(実体で文言をまとめて管理している文書など)は、ツールの結果を読むために使い、ファイルに書き戻すときは実体を展開しない xmllint --format を使います。どのコマンドが何を残すかは、後の「整形で意味が変わる場合」の節で比べています。

xmllintでXMLを整形する(Linux・Mac のコマンド)

この節では、コマンドで XML を整形するときにいちばんよく使われる xmllint の使い方と、日本語や上書きでつまずく点が分かります。

xmllint は libxml2 に付いてくるコマンドで、--format を付けると整形した XML を標準出力に出します。Ubuntu・Debian では libxml2-utils パッケージに入っています。手元にあるかは xmllint --version で確かめられます。

# 整形して画面に出す
xmllint --format pom.xml

# 別のファイルに保存する
xmllint --format pom.xml > pom.formatted.xml

# 字下げをタブにする(環境変数 XMLLINT_INDENT で字下げの文字を変える)
XMLLINT_INDENT=$'\t' xmllint --format pom.xml

# 1行に圧縮する(要素の間の空白だけのテキストを取り除く)
xmllint --noblanks pom.xml

# 整形せずに、正しい XML かだけを確かめる(終了コードで判定)
xmllint --noout pom.xml; echo $?

最初の pom.xml では、前の節の出力がそのまま出ました。字下げはスペース2つが既定です。--noout は、正しい XML なら何も出さずに終了コード0、誤りがあればエラーを出して終了コード1になりました。CI でファイルの壊れを見つけるのに使えます。

なお、libxml2 の公式の xmllint の説明には、--format について「特に DTD が無い場合、この機能は確実に動いたことがなく、根本的に壊れている」、--noblanks について「信頼できないので使うべきではない」という注意が書かれています(2026年11月9日時点)。手元の libxml2 2.9.14 では、この記事の例はすべて期待どおりに整形され、混在内容や前後の空白も残りましたが、空白が意味を持つ XML を書き戻すときは、後の C14N の節の方法で整形前後を比べておくと安心です。

日本語が &#x6587; のような文字参照になる

宣言に encoding が無い XML を整形すると、xmllint は日本語を文字参照に置き換えて出力しました。

$ xmllint --format mix.xml
<?xml version="1.0"?>
<root a="1" b="x &amp; y">
  <p>&#x6587;&#x306E;<b>&#x4E00;&#x90E8;</b>&#x3060;&#x3051;&#x592A;&#x5B57;</p>

XML としては同じ意味ですが、人が読むには困ります。--encode UTF-8 を付けると、宣言に encoding="UTF-8" が入り、日本語のまま出力されました。

$ xmllint --format --encode UTF-8 mix.xml
<?xml version="1.0" encoding="UTF-8"?>
<root a="1" b="x &amp; y">
  <p>文の<b>一部</b>だけ太字</p>

xmlstarlet の xmlstarlet fo(-s 2 でスペース2つ、-t でタブ)も同じ libxml2 を使うため、encoding の無い XML では同じように文字参照になりました。

同じファイルに上書きするときの落とし穴

xmllint --format a.xml > a.xml と書くと、ファイルが空になります。シェルがリダイレクトの時点で a.xml を空にしてから xmllint が読むためです。手元では「Document is empty」のエラーが出て、a.xml は0バイトになりました。

$ xmllint --format a.xml > a.xml
a.xml:1: parser error : Document is empty

^
$ wc -c a.xml
0 a.xml

整形して同じファイルに保存したい

やってはいけないxmllint --format a.xml > a.xml と同じ名前にリダイレクトする。ファイルが0バイトになる
→
代わりにxmllint --format a.xml --output a.xml を使う。手元では正しく13行の XML になった

別名に出してから mv で置き換える方法でもよい

なお、xmllint --format は、もともと字下げや空行がある XML を入れても、いったん要素の間の空白を捨ててから字下げし直しました。整形済みのファイルにもう一度かけても結果は同じで、何度かけても変わりません。

PythonでXMLを整形する(minidom・ElementTree.indent・lxml)

この節では、Python で XML を整形する3つの方法の出力の違いと、空行が増える・ns0: が付くといったつまずきの直し方が分かります。

標準ライブラリだけなら ElementTree.indent(Python 3.9 以降)、コメントや CDATA まで残したいなら lxml が扱いやすい方法です。3つの方法を同じ pom.xml で試しました。

import xml.dom.minidom as md
import xml.etree.ElementTree as ET

# 1. minidom の toprettyxml
print(md.parse("pom.xml").toprettyxml(indent="  "))

# 2. ElementTree.indent(Python 3.9 以降)
ET.register_namespace("", "http://maven.apache.org/POM/4.0.0")  # ns0: を防ぐ
tree = ET.parse("pom.xml")
ET.indent(tree, space="  ")
tree.write("out.xml", encoding="utf-8", xml_declaration=True)

1行の pom.xml なら、どちらもきれいに整形されました。ただし、それぞれに次の癖がありました。

minidom:字下げ済みの XML を入れると空行だらけになる

minidom の toprettyxml は、元の改行と字下げを「文字」として残したまま、さらに改行と字下げを足します。すでに整形してある pom.xml を入れると、次のように空行が増えました。

<?xml version="1.0" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0">


  <modelVersion>4.0.0</modelVersion>


  <groupId>jp.example</groupId>

また、文字とタグが混ざった要素 <p>文の<b>一部</b>だけ太字</p> も、文の・<b>一部</b>・だけ太字 の3行に分けて字下げしました。文章としての中身に改行と空白が入るので、意味が変わる場合があります。

ElementTree:名前空間に ns0: が付き、コメントが消える

ElementTree は、何もしないと既定の名前空間に ns0: という接頭辞を付けて書き出しました。

<ns0:project xmlns:ns0="http://maven.apache.org/POM/4.0.0">
  <ns0:modelVersion>4.0.0</ns0:modelVersion>

XML としては同じ意味ですが、Maven などのツールや人が読むときに戸惑います。上のコードのように ET.register_namespace("", "名前空間のURI") を先に呼ぶと、元の <project xmlns="..."> の形で書き出されました。さらに、ElementTree の既定のパーサーはコメントと処理命令を読み飛ばし、CDATA は普通の文字(if (a &lt; b) {})に変えました。宣言も、write の xml_declaration=True を付けないと出ません。

lxml:残したいものを残しやすい

lxml(pip install lxml)は libxml2 を使うため、コメント・処理命令・CDATA・名前空間の接頭辞を元のまま残しました。字下げ済みの XML でも空行を作らないよう、remove_blank_text=True を付けたパーサーで読みます。

from lxml import etree

parser = etree.XMLParser(remove_blank_text=True)  # 元の改行・字下げを捨てる
tree = etree.parse("indented.xml", parser)
print(etree.tostring(tree, pretty_print=True, xml_declaration=True, encoding="UTF-8").decode())

# 字下げの幅を変えるなら etree.indent
etree.indent(tree, space="    ")

remove_blank_text=True を付けずに、空行を2つ含む XML を pretty_print で出すと、空行がそのまま残りました。付けると空行が消え、字下げがそろいました。

方法 字下げ済みの XML コメント・CDATA 名前空間
minidom.toprettyxml 空行が増える 残る そのまま
ElementTree.indent 字下げし直す コメントは消え、CDATA は普通の文字に ns0: になる(register_namespace で防ぐ)
lxml(pretty_print) remove_blank_text=True で字下げし直す 残る そのまま

エラーが出たときの Python の表示(ParseError)は、行番号は1から、列は0から数えます。xmllint やブラウザより1つ小さい数になる点に注意します。エラーの原文は後の表にまとめました。

JavaでXMLを整形する(Transformer)

この節では、Java の標準 API だけで XML を整形するコードと、JDK 9 以降で空行が入る問題の直し方が分かります。

Java では javax.xml.transform.Transformer に INDENT=yes を設定して書き出すと整形できます。字下げの幅は、JDK に入っている実装の独自の設定 indent-amount で決めます。

import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import java.io.File;

public class Fmt2 {
  public static void main(String[] a) throws Exception {
    DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
    f.setNamespaceAware(true);
    Document doc = f.newDocumentBuilder().parse(new File(a[0]));
    // 空白だけのテキストを取り除く(これが無いと字下げ済みの XML で空行が入る)
    NodeList ws = (NodeList) XPathFactory.newInstance().newXPath()
        .evaluate("//text()[normalize-space()='']", doc, XPathConstants.NODESET);
    for (int i = 0; i < ws.getLength(); i++) ws.item(i).getParentNode().removeChild(ws.item(i));
    Transformer t = TransformerFactory.newInstance().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));
  }
}

OpenJDK 21 で1行の pom.xml を整形すると、宣言が <?xml version="1.0" encoding="UTF-8" standalone="no"?> になる以外は、xmllint と同じ形になりました。

字下げ済みの XML で空行が入る

空白だけのテキストを取り除く4行を省くと、すでに整形してある XML を入れたときに、次のように空白だけの行が入りました。元の改行と字下げも「文字」として書き出されるためです。

<project xmlns="http://maven.apache.org/POM/4.0.0">

  <modelVersion>4.0.0</modelVersion>

  <groupId>jp.example</groupId>

上のコードのとおり XPath で空白だけのテキストを消してから書き出すと、1行の XML から整形した結果と同じ内容(md5 が一致)になりました。なお、doc.setXmlStandalone(true) で standalone="no" を消すと、JDK 21 では宣言の直後に改行が入らず、ルート要素が宣言と同じ行に続きました。

Java の Transformer は混在内容も字下げする

<p>文の<b>一部</b>だけ太字</p> を含む XML では、Transformer は p の中身を3行に分けて字下げしました。minidom と同じく、文章の中に改行と空白が入ります。HTML に近い文書(XHTML・DocBook など)を Java で整形するときは、整形前後を次の C14N の節の方法で比べます。

PowerShell・VS CodeでXMLを整形する(公式ドキュメントの範囲)

この節では、Windows でよく使われる PowerShell(.NET)と、エディタの VS Code で XML を整形する方法が、公式ドキュメントで確かめた範囲で分かります。

.NET では XmlWriterSettings の Indent を true にした XmlWriter で書き出すと字下げされ、VS Code は XML の拡張機能を入れると Format Document で整形できます。どちらも手元では動かしていないため、公式ドキュメントに書かれていることだけを載せます。

環境 整形の方法 公式の記載
.NET(PowerShell から使う) XmlWriterSettings の Indent = true で XmlWriter を作り、XmlDocument.Save(XmlWriter) で書き出す Indent の既定は false。字下げの文字 IndentChars の既定はスペース2つ。混在内容の要素では字下げを止め、その要素を閉じると再開する
.NET の XmlDocument.Save PreserveWhitespace が true のとき空白を残す 「White space is preserved in the output file only if PreserveWhitespace is set to true.」
VS Code Format Document:Windows は Shift+Alt+F、macOS は ⇧⌥F、Linux は Ctrl+Shift+I。選択範囲は Ctrl+K Ctrl+F 組み込みの整形は JavaScript・TypeScript・JSON・HTML・CSS。ほかの言語は Marketplace の Formatters の拡張機能で対応
VS Code の XML 拡張機能(Red Hat) 拡張機能を入れたあと Format Document xml.format.enabled(既定 true)、空行を残す数 xml.format.preservedNewlines(既定 2)、空要素の扱い xml.format.emptyElements(既定 ignore)、1行の幅 xml.format.maxLineWidth(既定 100)

.NET の Indent の説明にある「混在内容の要素では字下げを止める」は、このページのツールや xmllint と同じ考え方です。Red Hat の XML 拡張機能は、xml:space="preserve" を付けた要素の空白をそのまま残し、xml.format.preserveSpace に並べた要素名(既定で xsl:text・pre など)も空白を残すと説明しています(以前の整形エンジンでは対応しない、とも書かれています)。VS Code の保存時の自動整形(editor.formatOnSave)は既定でオフです。

手元にエディタやコマンドが無い場面(他人のパソコン・ブラウザだけの環境)では、このページのツールが使えます。オンラインの整形サイトに貼るときは、送信されるかどうかをサイトの説明で確かめ、顧客名やパスワードが入った XML を貼らないようにします。

整形で意味が変わる場合(空白・xml:space・混在内容・DOCTYPE)

この節では、XML の整形で中身が変わってしまう場合と、ツール・コマンドごとの違いが分かります。

XML では、要素の間の空白も「文字」としてデータに含まれます。多くの設定ファイルでは要素の間の空白を読み飛ばすので整形しても困りませんが、空白が意味を持つ場所では、整形で中身が変わります。次の4つを含む XML を、各ツールで整形して比べました。

<p>文の<b>一部</b>だけ太字</p>
<code xml:space="preserve">  2つの空白  </code>
<name>  前後に空白  </name>
<!-- 設定 --><script><![CDATA[if (a < b) {}]]></script>
方法 混在内容(p) 前後の空白(code・name) コメント・CDATA
xmllint --format 1行のまま残す 残す 残す
lxml(pretty_print) 1行のまま残す 残す 残す
このページのツール 1行のまま残し、注意を出す 残す(xml:space の要素は注意を出す) 残す
ElementTree.indent 1行のまま残す 残す コメントが消え、CDATA は文字に
minidom・Java Transformer 3行に分けて字下げ 残す 残す

空白が意味を持つ代表的な場所は次の4つです。

  • 1文章の中にタグがある要素(混在内容)XHTML・DocBook・Office 文書の段落など。「文の 一部 だけ」のように空白が入ると表示が変わる
  • 2xml:space="preserve" が付いた要素「空白をそのまま扱ってほしい」という XML 仕様の印。SVG の text や Office 文書の文字列で使われる
  • 3値の前後の空白を読むアプリの設定文字列をそのまま比べる設定値。前後の空白が消えると一致しなくなる
  • 4電子署名つきの XML空白が1つ変わるだけで署名の検証に失敗する。整形して保存しない

DOCTYPE の中で実体(<!ENTITY>)を定義した XML も注意が要ります。xmllint --format は内部サブセットと &co; をそのまま残しました。このページのツールも DOCTYPE は書いたとおりに残しますが、&co; は中身の RJC に置き換えて表示し、そのことを注意で示します。実体の形のまま書き戻したい場合は、xmllint のように実体を展開しないコマンドを使います。

ファイルの種類ごとに XML・JSON・YAML のどれが向くかは、関連記事「CSV・JSON・XML・YAML・TOMLの違いと使い分け|同じデータで比較」で扱っています。

整形しても内容が同じか確かめる(C14N で比べる)

この節では、整形した XML が元の XML と同じ内容かを、C14N(正規化)で機械的に確かめる手順と実測が分かります。

C14N(Canonical XML)は、属性の順番・引用符・空要素の書き方・文字参照などの「書き方の違い」をそろえて、同じ内容なら同じバイト列にする決まりです。xmllint の --c14n で出力できます。ただし C14N は空白も中身として扱うため、そのまま比べると整形前と整形後は必ず違う結果になります。手元では次のようになりました。

$ xmllint --c14n pom.xml | md5sum
d907c7fb956a50e7507c5b2940e43367  -
$ xmllint --c14n pom.formatted.xml | md5sum
672035ae934e748f6e5e958375b0eade  -        ← 字下げの空白が入るので違う

$ xmllint --noblanks pom.xml | xmllint --c14n - | md5sum
d907c7fb956a50e7507c5b2940e43367  -
$ xmllint --noblanks pom.formatted.xml | xmllint --c14n - | md5sum
d907c7fb956a50e7507c5b2940e43367  -        ← 空白だけのテキストを除くと一致

手順は次の3つです。

1

空白だけのテキストを取り除く

xmllint --noblanks で、要素の間の改行と字下げだけを取り除く。文字の前後の空白は残る。

整形前・整形後の両方

2

C14N にする

xmllint --c14n - で標準入力から読んで正規化する。属性の順番や <a/> と <a></a> の違いがそろう。

コメントも含めて比べる

3

md5sum か diff で比べる

同じなら中身は同じ。違えば diff で、どの要素の文字が変わったかを見る。

CI に入れることもできる

xmllint の --noblanks は公式に「信頼できない」と書かれているため、同じ比べ方を lxml でも行いました。次の6行で、下の表と同じ md5 がすべてのファイルで出ました。

import sys, hashlib
from lxml import etree

p = etree.XMLParser(remove_blank_text=True)   # 空白だけのテキストを捨てて読む
for f in sys.argv[1:]:
    b = etree.tostring(etree.parse(f, p), method="c14n")
    print(hashlib.md5(b).hexdigest()[:8], f)

前の節の XML(混在内容・xml:space・前後の空白・コメント・CDATA を含む)を各方法で整形し、この手順で比べた結果です(md5 の先頭8文字)。

整形の方法 そのまま C14N 空白だけを除いて C14N 判定
整形前 94ba5df7 94ba5df7 基準
xmllint --format 92d0e367 94ba5df7 同じ
このページのツール(スペース2つ) 92d0e367 94ba5df7 同じ(xmllint と同じ出力)
このページのツール(1行に圧縮) 94ba5df7 94ba5df7 同じ(整形前と同じバイト列)
minidom・Java Transformer 97327dd2 c55ffacd 違う(混在内容に改行が入った)
ElementTree.indent 0e7b7935 5c7d71bc 違う(コメントが消えた)

このページのツールの結果(スペース2つ)は、xmllint --format --encode UTF-8 の出力と宣言の1行を除いて同じで、混在内容・xml:space・前後の空白・コメント・CDATA を含む XML でも整形前と同じ内容になりました。pom.xml でも、スペース2つ・1行に圧縮のどちらも整形前と同じ md5 です。一方、minidom・Java Transformer・ElementTree は中身が変わりました。混在内容や前後の空白を含む XML を書き戻すときは、この手順で一度確かめると、意図しない変化に気づけます。

なお C14N では、CDATA は普通の文字(&lt; を使った形)に置き換えて比べます。CDATA で書いた XML と &lt; で書いた XML は、C14N では同じとみなされます。

XMLのエラーの読み方(行番号・文字目と表示の違い)

この節では、XML のエラーが出たときに、行番号と文字目からどこを見ればよいかと、ツールごとの数え方の違いが分かります。

XML のエラーは、パーサーが「もう読めない」と気づいた場所を示すため、本当の原因はその場所か、少し前にあります。閉じタグの名前を間違えた次の XML を、このページのツールに入れました。

<config>
  <db>
    <host>localhost</Host>
  </db>
</config>

ツールの表示は次のとおりです。エラーの行に印が付き、前後の行も出ます。

XML として読めません:3行目・26文字目のあたり。エラー:Unexpected closing tag: Host != host
1 | <config>
2 |   <db>
3 |     <host>localhost</Host>
                             ^ ここ
4 |   </db>
・開始タグと終了タグの名前が合っていません。大文字小文字も区別されます。

同じ XML を各ツールに読ませると、示す位置の数え方が少しずつ違いました。

ツール 示した位置 数え方
xmllint・lxml(libxml2) 3行目・27文字目(^ の印つき) 行も列も1から。閉じタグの後ろを指す
このページのツール(Chromium) 3行目・26文字目 行も列も1から
Java(SAXParseException) line 3・col 22 読み終えた位置。閉じタグの途中
Python(ParseError) line 3・column 21 列は0から数える

どの数え方でも、示された行の、示された位置より少し前に原因があります。エラー文の英語は、パーサーによって言い回しが違います。このページのツールの表示は Chromium の XML パーサーの言葉で、xmllint(libxml2)とは別の文になります。次の節の表で、同じ原因に対する各ツールの原文を並べました。

よくあるXMLエラーの原文と直し方(libxml2・Python・Java・ブラウザ)

この節では、XML でよく出るエラーの原文を原因ごとに並べ、直し方が分かります。

XML のエラーの原因は、ほとんどが「閉じタグの名前」「& と < の書き方」「ルート要素の数」「属性の引用符」の4つです。原因ごとに小さな XML を作り、4つのパーサーに読ませた原文です(Python と Java は位置の部分を省いています)。

原因(例) xmllint(libxml2) Python Java このページのツール
閉じタグの名前が違う(<host>…</Host>) Opening and ending tag mismatch: host line 3 and Host mismatched tag The element type "host" must be terminated by the matching end-tag "</host>". Unexpected closing tag: Host != host
& をそのまま書いた(R&D) EntityRef: expecting ';' not well-formed (invalid token) The reference to entity "D" must end with the ';' delimiter. Unexpected token inside entity:
< をそのまま書いた(a < b) StartTag: invalid element name not well-formed (invalid token) The content of elements must consist of well-formed character data or markup. Qualified name is invalid:
ルート要素が2つ Extra content at the end of the document junk after document element The markup in the document following the root element must be well-formed. Unexpected token: <
属性の値に引用符が無い(id=10) AttValue: " or ' expected not well-formed (invalid token) Open quote is expected for attribute "id" associated with an element type "user". Unexpected token: 1
終了タグが足りない Premature end of data in tag a line 1 no element found XML document structures must start and end within the same entity. Unexpected end of stream: still inside the root element
宣言の前に空行がある XML declaration allowed only at the start of the document XML or text declaration not at start of entity The processing instruction target matching "[xX][mM][lL]" is not allowed. Invalid processing instruction: <?xml
名前空間の接頭辞を宣言していない(ns:b) Namespace prefix ns on b is not defined unbound prefix The prefix "ns" for element "ns:b" is not bound. Element ns:b prefix is unbound
同じ属性が2つ Attribute x redefined duplicate attribute Attribute "x" was already specified for element "a". Attribute 'x' is redefined
HTML の実体を使った(&nbsp;) Entity 'nbsp' not defined undefined entity The entity "nbsp" was referenced, but not declared. Unexpected entity: nbsp
タグ名が数字で始まる(<1st>) StartTag: invalid element name not well-formed (invalid token) The markup in the document preceding the root element must be well-formed. Unexpected token inside qualified name: 1

直し方は、原因ごとに次のとおりです。

原因 直し方
閉じタグの名前が違う 開始タグと同じ名前にする。XML は大文字と小文字を区別する(host と Host は別の名前)
& と < をそのまま書いた 文字の & は &amp;、< は &lt; と書く。長いコードなどは CDATA で囲む
ルート要素が2つ 全体を1つの要素(例:<items>)で囲む。ログを追記した XML で起きやすい
属性の値に引用符が無い id="10" のように " か ' で囲む。数字でも囲む
終了タグが足りない ファイルが途中で切れていないか確かめ、足りない終了タグを足す
宣言の前に空行・文字がある <?xml ?> をファイルの1文字目に置く。前の空行や空白を消す(BOM は置いてよい)
接頭辞を宣言していない xmlns:ns="名前空間のURI" をその要素か親の要素に書く
同じ属性が2つ どちらかを消す。テンプレートで属性を足したときに起きやすい
&nbsp; などの HTML の実体 XML で最初から使える実体は &amp; &lt; &gt; &quot; &apos; の5つだけ。&#160; のような数値の文字参照で書く

このページのツールは、上の表のどの原因でも、エラー文の下に原因の案内を出しました(閉じタグの名前の違い・& や < のまま・ルートが2つ・属性の引用符・終了タグ不足・宣言の前の空行・接頭辞の未宣言・同じ属性が2つ・タグ名の誤り)。&nbsp; の例では「& がそのまま書かれています」という案内が出ますが、これは &nbsp; が XML では定義されていないという意味で読み替えます。

& や < を文字として書く決まりは、JSON の文字列の書き方とは違います。JSON の整形とエラーは関連記事「JSON.parseとJSON.stringifyの使い方|エラー原因と整形」で扱っています。

よく整形するXMLファイル(pom.xml・logback.xml・sitemap.xml)

この節では、仕事でよく整形する XML ファイルごとの注意点が分かります。

設定ファイルとして使う XML は、ほとんどが要素の間の空白を読み飛ばすため、整形しても動きは変わりません。ただし、ファイルごとに気をつける点があります。

整形しても困りにくい XML

pom.xml(Maven)・logback.xml などの設定ファイル

sitemap.xml・RSS(要素の値に前後の空白が無いもの)

API の応答を読むために整形するとき(保存しない)

整形の前に確かめたい XML

Office 文書(.docx の中の XML)・SVG の text など、文章や xml:space を含むもの

電子署名つきの XML(空白1つで検証に失敗する)

DOCTYPE の中で実体を定義している XML

pom.xml は、IDE やプラグインが書き換えることもあるため、整形だけのコミットを分けておくとレビューが楽になります。pom.xml の書き方と mvn コマンドは関連記事「mvnコマンド一覧とpom.xmlとは|基本5つの実行ログとオプション早見表」で扱っています。logback.xml のように要素の値を文字列として読む設定ファイルで、値の前後にわざと空白を入れている場合は注意します。このページのツールも xmllint --format も、文字だけの要素の前後の空白はそのまま残しました。logback.xml の設定項目は関連記事「SLF4J+Logback設定|logback.xmlテンプレートとローテーション」で扱っています。

XML整形の方法の選び方(判断表と速さの実測)

この節では、場面ごとにどの XML 整形の方法を使えばよいかと、2MB の XML での速さが分かります。

場面 向いている方法 理由
API の応答やログの XML を今すぐ読みたい このページのツール 貼るだけ。送信されず、誤りの行も分かる
ファイルを整形して保存したい xmllint --format --encode UTF-8 --output 空白・DOCTYPE・コメントを残し、何度かけても同じ結果
壊れた XML を CI で見つけたい xmllint --noout 誤りがあれば終了コード1
Python で読み書きのついでに整形 ElementTree.indent(register_namespace と併用) 標準ライブラリだけで済む。コメントは消える
Python でコメント・CDATA も残す lxml(remove_blank_text=True と pretty_print) libxml2 と同じ結果になる
Java のプログラムの中で整形 Transformer(空白だけのテキストを先に消す) 標準 API だけで済む。混在内容は崩れる
エディタで書きながら整形 VS Code と XML の拡張機能 Format Document と保存時の整形が使える

速さの実測(2MB・要素120,001個)

商品2万件(要素120,001個・2,056,022バイト)を1行にした XML を、各方法で整形した時間です。

0.09秒xmllint --format2回の測定で0.08〜0.10秒
0.21秒ElementTree の indent読み込みから文字列化まで
0.95秒このページのツールChromium の中で検証から表示まで
方法 時間(2MB) 備考
xmllint --format 0.08〜0.10秒 C で書かれた libxml2
ElementTree.indent 0.21秒 Python 3.13
minidom.toprettyxml 0.57秒 Python 3.13
Java Transformer 0.70秒 JVM の起動を含む
このページのツール 0.95秒 結果は2,776,024文字

数MBまでの XML なら、どの方法でも1秒前後で終わります。ブラウザのツールでは、貼り付けそのものと結果の表示にも時間がかかるので、数十MBの XML はコマンドで整形するほうが確実です。

よくある質問

この節では、XML の整形について、よく聞かれる質問への答えが分かります。

Q. XML を整形すると、プログラムの動きは変わりますか?
設定ファイルの多くは要素の間の空白を読み飛ばすので変わりません。ただし、文章の中にタグがある要素、xml:space="preserve" の要素、電子署名つきの XML では変わることがあります。心配なときは C14N の節の手順で整形前後を比べます。

Q. このページのツールに貼った XML は、どこかに送られますか?
送られません。検証も整形もブラウザの中だけで行われ、外部の DTD やファイルも読み込みません。それでも、パスワードや個人情報を含む XML は、貼る前に値を置き換えておくのが安全です。

Q. 「正しい XML です」と出れば、スキーマに合っていますか?
いいえ。ツールが確かめるのは整形式(XML の文法)までで、XSD や DTD に合っているか(妥当かどうか)は確かめません。スキーマに合うかは xmllint の --schema などで別に確かめます。

Q. 整形した XML を元の1行に戻せますか?
ツールの「1行に圧縮」か、xmllint --noblanks で要素の間の空白を取り除けます。pom.xml では、整形した XML を圧縮すると元の1行と同じ C14N になりました。

Q. xmllint で日本語が &#x3042; のようになります。
宣言に encoding が無い XML では、xmllint が日本語を文字参照で出力します。--encode UTF-8 を付けると日本語のまま出ます。

Q. Python で整形したら空行だらけになりました。
minidom の toprettyxml に字下げ済みの XML を渡したためです。ElementTree.indent を使うか、lxml で remove_blank_text=True を付けて読み直すと、空行は入りませんでした。

Q. Python の ElementTree で ns0: が付きます。
既定の名前空間に接頭辞が自動で付くためです。読み込む前に ET.register_namespace("", "名前空間のURI") を呼ぶと、元の xmlns="..." の形で書き出されます。

Q. エラーの行番号を見ても、その行に間違いが見当たりません。
パーサーは「読めなくなった場所」を示すため、原因はその行の少し前にあることがよくあります。終了タグが足りない場合は、ファイルの最後の行が示されます。ツールでは前後の行も出るので、示された位置から上へたどって確かめます。

Q. &nbsp; を使うとエラーになるのはなぜですか?
&nbsp; は HTML の実体で、XML では定義されていないためです。XML で最初から使えるのは &amp; &lt; &gt; &quot; &apos; の5つだけです。改行しない空白は &#160; と書きます。

Q. VS Code に XML の整形機能はありますか?
VS Code の組み込みの整形は JavaScript・TypeScript・JSON・HTML・CSS で、XML は Marketplace の拡張機能(Red Hat の XML など)を入れて使います(公式ドキュメント)。入れたあとは Format Document(Windows は Shift+Alt+F)で整形できます。

まとめ

  • XML 整形は、XML として正しいかを確かめてから、改行と字下げを足す作業。このページのツールに貼れば、整形と誤りの場所の表示が一度に済む。
  • コマンドなら xmllint --format:日本語は --encode UTF-8、同じファイルへの保存は --output。> で同じファイルに出すと0バイトになる。
  • Python と Java は空白の扱いに癖がある:minidom と Transformer は字下げ済みの XML で空行を作り、混在内容も字下げする。ElementTree は ns0: が付きコメントが消える。lxml は remove_blank_text=True で整う。
  • 空白に意味がある XML は整形で中身が変わる:混在内容・xml:space・前後の空白・署名つき XML。xmllint とこのページのツールは空白を残したが、書き戻す前に空白だけを除いた C14N で整形前後を比べると安心。
  • エラーは「閉じタグの名前・& と <・ルートの数・属性の引用符」がほとんど:示された行の少し前を見て、原文を表で引けば原因が分かる。

参考・出典

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「XML整形ツール|見やすくする方法とエラーの直し方・行番号つき」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり