XML ビューアー
折りたためるツリーとソースを左右に並べて表示。
何か貼り付けると、ここに文書構造が表示されます。
すべてこのタブ内で実行されます。貼り付けた内容がアップロード・記録・送信されることはありません。 ネットワークパネルを開いて確認する.
上に文書を貼り付けると、同じものが 2 通りに表示されます。左はシンタックスハイライト・行番号・折りたたみ付きのソース、右は同じ解析結果から組み立てた折りたためるツリーです。ノードは最初すべて展開されています。要素をクリックすると、その要素と配下がまとめて折りたたまれます。両方のペインは 1 回の走査から作られるので、ツリーとエラー一覧が常に同じ文書を指します。
ツリー表示が本領を発揮するのは、その文書を書いたのが自分ではないときです。ドキュメントが PDF しかないベンダーの API レスポンス、前任チームが残した 4,000 行の設定、レコード 1 件のはずが 200 件入っているかもしれないエクスポート。知りたいのはたいてい構文エラーの場所ではなく、これがどんな形をしていて、必要な値がどこにあるかです。
読んでいるあいだも検証が走り、整形式の問題は最初の 1 件で止まらず、行・列・直し方の提案つきですべて報告されます。アップロードもありません。パーサーもツリーもエディターも、すべてこのタブで動きます。
ツリーに何が出るか
各要素が 1 行です。書かれたとおりのタグ名(プレフィックス込み)に続いて、属性が name="value" の形で並びます。子を持たない要素には「空」と印が付くので、<status/> と自分で折りたたんだノードを見分けられます。テキストは要素の下の葉として現れ、200 文字で打ち切られます。ツリーは構造を見るためのものであり、40 KB の base64 を全部描画すればそれが埋もれてしまうからです。コメントはプレースホルダーの行として表示されます。
意図的に出さないものが 2 つあります。CDATA セクションと処理命令です。それらはソースペインで読んでください。ソースペインは貼り付けたままの文書を表示し、常にそちらが正です。ツリーは本物の DOM で、要素ごとに折りたためるノードが 1 つ。だから折りたたみが即座で、テキストも選択できます。同時にそれは代償でもあり、要素が数万ある文書では、XML の解析よりツリーの構築のほうに時間がかかります。
ツリーが得意なこと、苦手なこと
ツリーは「形」に関する問いに、スクロールよりずっと速く答えます。1 段下をすべて折りたためば、最上位のセクションが 1 画面に収まります。SOAP の Header を折りたためば Body がペインの先頭に来ます。コンテナの下に並ぶ繰り返し行を見れば、そのレスポンスがレコード 1 件なのか複数件なのかが分かります。それはテストでは動くのに本番でデータを取りこぼす利用側との分かれ目です。苦手なことは 3 つあり、隠すよりはっきり言うほうが役に立ちます。
- 編集。ツリーは入力のたびに作り直されるので、書き込む先がそもそもありません。ソースペインで編集し、ツリーが組み直されるのを見てください。
- 比較。ツリーを 2 つ並べるのは、両側の書式をまず正規化する diff より、差分を見つける手段として劣ります。
- 抽出。「すべての明細のすべての価格」は XPath 式であって、スクロールの作業ではありません。
深い入れ子と、プレフィックスを淡く表示する理由
名前空間つきの XML が読みにくいのは、名前空間が複雑だからではありません。soap:Body や ns1:PlaceOrder のプレフィックスは足場です。その名前がどの語彙に属すかを告げるもので、いったん分かってしまえば、以降の行では雑音でしかありません。インデントが 8 段にもなると、画面の文字のかなりの割合がプレフィックスとコロンになります。
そこでエディターは、プレフィックスとコロンをローカル名より薄く描きます。すると soap:Body は「印の付いた Body」として読めます。何も隠していませんし、変えてもいません。テキストは選択でき、コピーもそのままです。例外は宣言です。xmlns:soap ではプレフィックスこそが中身であって足場ではないので、こちらは通常の濃さのままにします。束縛されていないプレフィックスの使用はエラーとして報告し、追加すべき宣言も示します。大きな文書から断片をコピーしたときによく起きます。
値を探す
リテラル文字列なら、ソースペインにカーソルを置いて Ctrl-F(Mac なら Cmd-F)を押してください。エディターの検索パネルが上部に開き、一致箇所のハイライトと置換欄が使えます。検索対象はソースなので、ツリーが省略している部分も含め、すべてのノードの全文が対象になります。
構造に関する問いなら XPath を使ってください。//order[status="failed"]/@id は、スクロール 20 回ぶんの答えを 1 手で返します。このサイトの XPath テスターは、文書から拾ったプレフィックスを使って XPath 1.0 を評価します。「式が何も返さない」という報告のほとんどは、次の 1 点で説明できます。XPath 1.0 には既定名前空間がありません。ルートが xmlns="urn:example:orders" を宣言していれば //order は何にも一致しません。//order は「名前空間なしの order 要素」を意味するからです。プレフィックスを束縛して //o:order と書くか、//*[local-name()="order"] で代用してください。
コードで文書をたどる
文書を手で読むのは、たいていそれを毎日読むコードを書くための一歩です。次のサンプルは文書をたどって構造を出力します。上のツリーのプログラム版です。どれも安全に解析します。Java と Python の既定は外部実体を解決してしまうからです。
// Print the shape of a document: element paths with a count, which is
// usually more useful than the tree itself when you are writing a consumer.
function outline(source) {
const doc = new DOMParser().parseFromString(source, 'application/xml');
const error = doc.querySelector('parsererror');
if (error) throw new Error(error.textContent.trim());
const counts = new Map();
const walk = (el, path) => {
const here = path + '/' + el.nodeName;
counts.set(here, (counts.get(here) ?? 0) + 1);
for (const child of el.children) walk(child, here);
};
walk(doc.documentElement, '');
for (const [path, n] of [...counts].sort()) {
console.log(n.toString().padStart(6), path);
}
}
// nodeName keeps the prefix as written. For namespace-aware work use
// el.localName with el.namespaceURI, since the prefix is arbitrary and the
// URI is what identifies the vocabulary.# iterparse reads the document as a stream and lets you drop each element
# once you are done with it, so memory stays flat on files far larger than a
# browser will open.
from defusedxml.ElementTree import iterparse
from collections import Counter
counts = Counter()
stack = []
for event, el in iterparse('document.xml', events=('start', 'end')):
if event == 'start':
stack.append(el.tag)
counts['/'.join(stack)] += 1
else:
stack.pop()
el.clear() # release the subtree; this is the whole point
for path, n in sorted(counts.items()):
print(f'{n:6d} {path}')
# ElementTree reports tags as '{namespace-uri}local' rather than 'prefix:local'
# because the prefix is arbitrary. Split on '}' when you want the local name.import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
public final class Outline {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true); // off by default
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
Document doc = dbf.newDocumentBuilder().parse(new java.io.File(args[0]));
print(doc.getDocumentElement(), 0);
}
static void print(Node node, int depth) {
if (node.getNodeType() != Node.ELEMENT_NODE) return;
Element el = (Element) node;
StringBuilder line = new StringBuilder(" ".repeat(depth));
line.append(el.getNodeName());
NamedNodeMap attrs = el.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node a = attrs.item(i);
line.append(' ').append(a.getNodeName())
.append("=\"").append(a.getNodeValue()).append('"');
}
System.out.println(line);
NodeList kids = el.getChildNodes();
for (int i = 0; i < kids.getLength(); i++) print(kids.item(i), depth + 1);
}
}using System.Xml;
using System.Xml.Linq;
// XDocument.Load uses a reader with DtdProcessing.Prohibit by default, so
// external entities are never fetched. Construct the reader yourself when
// you want that guarantee stated in the code rather than in the docs.
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersInDocument = 20L * 1024 * 1024,
};
using var reader = XmlReader.Create("document.xml", settings);
var doc = XDocument.Load(reader);
// One line per distinct element path, with how often it occurs.
var paths = doc.Descendants()
.GroupBy(e => string.Join(
"/",
e.AncestorsAndSelf().Reverse().Select(a => a.Name.LocalName)))
.OrderBy(g => g.Key);
foreach (var group in paths)
{
Console.WriteLine(group.Count().ToString().PadLeft(6) + " " + group.Key);
}# xmllint has an interactive tree browser almost nobody knows about. It gives
# you ls, cd, cat, du and pwd over the document, with XPath as the path
# syntax: the terminal equivalent of the tree on this page.
xmllint --nonet --shell document.xml
# / > du show the subtree shape
# / > cd //order[1] navigate by XPath
# / > ls list children of the current node
# / > cat status print a node
# / > exit
# Non-interactive: tag counts, a rough outline of an unfamiliar document.
xmllint --nonet --format document.xml |
grep -o '<[a-zA-Z_][^ />]*' |
sort | uniq -c | sort -rn | head -30
# One value, straight out:
xmllint --nonet --xpath 'string(//order[1]/status)' document.xmlシェルの例にある grep のアウトラインは解析ではなく数え上げの小技です。コメントや CDATA セクションの中に現れたタグ名も平然と数えます。他のサンプルはいずれも先に木を組み立てます。それが、おおよその答えと正しい答えの違いです。
よくある質問
表示するために XML はアップロードされますか。
いいえ。パーサーもツリーもエディターも、このタブで動く JavaScript です。サーバー側の仕組みも、エディターにアクセスできる解析ツールも、サードパーティのスクリプトもありません。ネットワークタブを開いて文書を貼り付けてみてください。ページ自身のアセットが一度読み込まれ、そのあとは静かなままです。
このページではその確認が特に重要です。「中身を見る」という行為は、たいてい他人のデータに対して行うものだからです。ビューアーに貼り付ける文書は、たいてい生きた API レスポンスか本番のエクスポートで、氏名や口座番号、ヘッダーのトークンがそのまま入っています。この検索で上位に出るツールのいくつかはそれをサーバーへ送りますし、保存した文書を既定で公開にするものもあります。
ツリー表示は実際のところ何の役に立ちますか。
構造についての問いに答えるためです。どの要素が繰り返されるか、入れ子はどこまで深いか、目当ての値はどの枝にあるか、任意要素はこのレスポンスに含まれているか。どれもインデントされたテキストでは見づらく、ツリーなら一目です。
元が取れる使い方はこうです。ルートの 1 段下をすべて折りたたみ、セクション名を読み、必要なものだけ展開し、残り 3,000 行は無視する。もし問いが構文の話なら、あなたが見るべきはソースペインとエラー一覧のほうです。
ツリー上で XML を編集できますか。
できません。ツリーは入力に合わせて作り直されるので、編集の対象として残り続けるものがありません。変更は左のソースペインで行ってください。
これは意図した制限です。ツリー編集器は変更のたびに、空白・混在内容・コメントの位置・属性の順序をどう扱うか決めなければなりません。安全側に倒すと使い勝手が悪くなり、便利側に倒すと、触っていない部分を黙って書き換えてしまいます。
特定の値を見つけるには。
リテラル文字列なら、ソースペインにカーソルを置いて Ctrl-F(Mac は Cmd-F)を押します。検索パネルが上部に開き、一致箇所がハイライトされます。検索対象はツリーが省略した表示ではなく全文です。
構造に関わることなら XPath です。//order[status="failed"]/@id なら 1 手で答えが出ます。要素は確かにあるのに式が何も返さないときは、既定名前空間を疑ってください。XPath 1.0 に既定名前空間はないので、プレフィックスを束縛して //o:order と書くまで、//order は urn:example:orders の要素に一致しません。
ブラウザーがファイルを表示せず「このページには次のエラーが含まれています」と言うのはなぜですか。
それは Chrome や Firefox に内蔵された XML ビューアーが、その文書は整形式でないと告げているのです。どちらも最初のエラーで止まり、そこまでしか描画しません。メッセージは libxml2 か Expat のもので、パーサーを書いた人向けの言葉です。「Opening and ending tag mismatch」「Document is empty」といった具合に。
ここに貼り付ければ、すべての問題が行・列・提案つきで一度に出ますし、解析できた部分についてはツリーも表示されます。よくある原因は、生のアンパサンド、途中で切れたダウンロード、XML 宣言より前にあるバイト順マーク、そして XML のコンテンツタイプで返された HTML のエラーページです。
どのくらいの大きさのファイルを開けますか。
20 MB までですが、実際には解析より先にツリーが重くなります。1 MB の文書の走査はおよそ 110 ミリ秒。要素ごとに折りたためるノードを 1 つ作る作業は、数万要素ともなればそれより時間がかかります。
この上限があるのは、何もアップロードしないからです。文書もそのツリーも、どちらもこのタブのメモリーに載ります。それより大きなファイルは、手元でストリーミング処理できるものを使ってください。閲覧なら xmllint --shell、抽出なら Python の iterparse です。