XMLを正規表現で扱うポイント|タグ抽出や置換のコツと注意点

Web

「XMLファイルの中から特定のデータを取り出したい」
「タグや属性を一括で置き換えたい」
そんなときに便利なのが正規表現(Regex)です。

ただし、XMLは構造化されたデータなので、正規表現で処理する際には思わぬ落とし穴もあります。

この記事では、XMLを正規表現で扱うときの基本的な使い方、よく使うパターン例、そして注意すべきポイントを初心者にもわかりやすく解説します。

スポンサーリンク

XMLと正規表現の基本的な関係

正規表現とは何か

正規表現(Regular Expression)は、文字列のパターンマッチングを行うための記法です。

テキストの検索、抽出、置換において強力なツールとして活用されています。

XMLの特徴を理解する

XMLを正規表現で処理する前に、XMLの特徴を理解しておくことが重要です:

  • 階層構造:タグが入れ子(ネスト)になっている
  • 開始タグと終了タグ<tag></tag>がペアになっている
  • 属性<tag attribute="value">のような形式
  • 大文字小文字の区別:XMLは大文字小文字を区別する
  • 特殊文字&lt;&gt;&amp;などのエスケープが必要

XMLを正規表現で扱うときの基本方針

正規表現が適している作業

単純なパターンマッチング

  • 特定のタグの内容を抽出
  • 属性値の取得や置換
  • 単純なテキスト整形
  • ログファイルからXML部分を抜き出し

簡単な一括処理

  • 同じパターンのタグを一括置換
  • 属性名の統一
  • 不要なタグの削除

正規表現が適さない作業

複雑な構造解析

  • ネストしたタグの正確な解析
  • 親子関係の判定
  • スキーマに従った厳密な処理
  • 名前空間を含むXMLの処理

重要なデータ処理

  • 金融データなどミスが許されない処理
  • XMLの妥当性検証
  • 複雑なデータ変換

XMLパーサとの使い分け

正規表現を使う場面

  • プロトタイピングや調査作業
  • 単純で一時的な処理
  • ログ解析などテキスト処理寄りの作業

XMLパーサを使う場面

  • 本格的なアプリケーション開発
  • 複雑なXML構造の処理
  • エラーハンドリングが重要な処理

よく使うXML正規表現パターン

パターン1:特定のタグの内容を抽出

基本形

<tagname>(.*?)</tagname>

具体例:タイトルの抽出

<title>(.*?)</title>

実装例(Python)

import re

xml = """
<article>
    <title>XML入門ガイド</title>
    <title>正規表現の基本</title>
    <content>記事の内容...</content>
</article>
"""

titles = re.findall(r"<title>(.*?)</title>", xml)
print(titles)  # → ['XML入門ガイド', '正規表現の基本']

ポイント

  • (.*?):非貪欲マッチで最短一致
  • ():キャプチャグループで内容を取得
  • ?がないと最長一致になり、複数タグがあるときに問題

パターン2:属性値の抽出

基本形

attribute="([^"]*)"

具体例:IDの抽出

id="([^"]*)"

実装例(JavaScript)

const xml = '<product id="123" name="商品A" price="1000" />';
const idMatch = xml.match(/id="([^"]*)"/);
const nameMatch = xml.match(/name="([^"]*)"/);

console.log(idMatch[1]);   // → 123
console.log(nameMatch[1]); // → 商品A

より柔軟な属性抽出

\b(\w+)="([^"]*)"

この方法なら、すべての属性を一度に取得できます:

import re

xml = '<product id="123" name="商品A" price="1000" />'
attributes = re.findall(r'\b(\w+)="([^"]*)"', xml)
print(attributes)  # → [('id', '123'), ('name', '商品A'), ('price', '1000')]

パターン3:タグの置換

基本的な置換

# 検索パターン
<old>(.*?)</old>

# 置換パターン
<new>$1</new>

実装例(Python)

import re

xml = "<old>古いデータ</old><old>別のデータ</old>"
new_xml = re.sub(r"<old>(.*?)</old>", r"<new>\1</new>", xml)
print(new_xml)  # → <new>古いデータ</new><new>別のデータ</new>

属性付きタグの置換

# より複雑なパターン
xml = '<item status="old">データ</item>'
new_xml = re.sub(r'<item status="old">(.*?)</item>', 
                 r'<item status="new">\1</item>', xml)

パターン4:複数行にまたがるXMLの処理

複数行モードの活用

import re

xml = """<description>
これは複数行にわたる
説明文です。
</description>"""

# re.DOTALLフラグで改行も含めてマッチ
content = re.search(r"<description>(.*?)</description>", xml, re.DOTALL)
print(content.group(1))

パターン5:自己閉じタグの処理

自己閉じタグのパターン

<tagname\s*([^>]*?)\s*/>

実装例

import re

xml = '<img src="image.jpg" alt="画像" /><br /><input type="text" />'
self_closing = re.findall(r'<(\w+)\s*([^>]*?)\s*/>', xml)
print(self_closing)  # → [('img', 'src="image.jpg" alt="画像"'), ('br', ''), ('input', 'type="text"')]

エディタ別の正規表現活用法

Visual Studio Code

検索・置換での正規表現

  1. Ctrl + Hで置換ダイアログを開く
  2. 正規表現ボタン(.*)をクリック
  3. 検索パターンと置換パターンを入力

具体例:タグ名の一括変更

  • 検索<section>(.*?)</section>
  • 置換<div>$1</div>

Notepad++

正規表現の有効化

  1. 検索→置換(Ctrl + H)
  2. 検索モードで「正規表現」を選択

実用例:属性値の統一

  • 検索class="oldclass"
  • 置換class="newclass"

IntelliJ IDEA / PyCharm

強力な正規表現機能

  • 構造検索・置換(Structural Search and Replace)
  • XMLの構造を理解した高度な置換

オンラインツール

regex101.com

  • 正規表現のテストに最適
  • マッチ結果をリアルタイムで確認
  • 説明機能で正規表現の意味を理解

regexr.com

  • 視覚的な正規表現エディタ
  • サンプルデータでのテスト機能

注意点と制限事項

正規表現の限界

ネストした構造への対応困難

問題のあるXML例

<item>
    <description>商品説明<note>注記</note>詳細</description>
    <item>ネストしたアイテム</item>
</item>

なぜ問題なのか

  • 最初の<item>がどの</item>と対応するかわからない
  • 正規表現では文脈を理解できない

推奨される対処法

# XMLパーサーを使用(推奨)
import xml.etree.ElementTree as ET

xml_string = """<item>
    <description>商品説明<note>注記</note>詳細</description>
    <item>ネストしたアイテム</item>
</item>"""

root = ET.fromstring(xml_string)
description = root.find('description').text

よくある間違いとその対策

問題1:貪欲マッチによる誤抽出

間違った正規表現

<title>(.*)</title>

問題

<title>タイトル1</title><content>内容</content><title>タイトル2</title>

この場合、「タイトル1」から「タイトル2」までのすべてがマッチしてしまいます。

正しい正規表現

<title>(.*?)</title>

問題2:名前空間の無視

問題のあるXML

<ns:book xmlns:ns="http://example.com">
    <ns:title>書籍名</ns:title>
</ns:book>

対応方法

<[^:]*:?title[^>]*>(.*?)</[^:]*:?title>

問題3:特殊文字のエスケープ

問題

<description>&lt;重要&gt;注意事項&amp;規約</description>

対処法

import html

# XMLエンティティをデコード
content = html.unescape(content)

パフォーマンスの考慮

大きなXMLファイルでの注意点

問題

  • 正規表現は大きなファイルでメモリを大量消費
  • 複雑なパターンは処理が遅い

対策

  • ストリーミング処理の検討
  • 部分的な処理での分割
  • XMLパーサーへの移行

実践的な使用例

例1:ログファイルからエラー情報を抽出

import re

log_content = """
<log level="error" timestamp="2024-01-01 10:00:00">
    <message>データベース接続エラー</message>
    <details>Connection timeout</details>
</log>
<log level="info" timestamp="2024-01-01 10:01:00">
    <message>処理完了</message>
</log>
"""

# エラーレベルのログのみ抽出
errors = re.findall(r'<log level="error"[^>]*>.*?<message>(.*?)</message>.*?</log>', 
                   log_content, re.DOTALL)
print(errors)  # → ['データベース接続エラー']

例2:設定ファイルの値を一括変更

import re

config_xml = """
<config>
    <database host="localhost" port="3306" />
    <cache host="localhost" port="6379" />
    <api host="localhost" port="8080" />
</config>
"""

# localhostを本番サーバーに変更
updated_config = re.sub(r'host="localhost"', 'host="production.server.com"', config_xml)
print(updated_config)

例3:HTMLからXMLへの簡易変換

import re

html = '<div class="content"><p>段落1</p><p>段落2</p></div>'

# HTMLタグをXMLタグに変換
xml = re.sub(r'<div class="([^"]*)">', r'<section type="\1">', html)
xml = re.sub(r'</div>', r'</section>', xml)
xml = re.sub(r'<p>', r'<paragraph>', xml)
xml = re.sub(r'</p>', r'</paragraph>', xml)

print(xml)
# → <section type="content"><paragraph>段落1</paragraph><paragraph>段落2</paragraph></section>

XMLパーサーとの比較

処理速度の比較

ファイルサイズ正規表現XMLパーサー推奨
小(~1MB)高速高速用途次第
中(1~10MB)普通高速XMLパーサー
大(10MB~)遅い高速XMLパーサー

安全性の比較

観点正規表現XMLパーサー
エラーハンドリング限定的充実
妥当性検証なしあり
メモリ効率場合による最適化済み

まとめ

XMLを正規表現で扱う際のベストプラクティス

適用場面を見極める

  1. 単純な抽出・置換:正規表現が効率的
  2. 複雑な構造解析:XMLパーサーを使用
  3. 本格的な開発:必ずXMLパーサーを使用

安全な正規表現の書き方

  1. 非貪欲マッチ?)を活用
  2. 文字クラスで不正な文字をブロック
  3. エスケープ処理を忘れずに
  4. テストデータで十分に検証

パフォーマンス最適化

  1. シンプルなパターンを心がける
  2. 部分処理で大きなファイルに対応
  3. コンパイル済み正規表現を再利用

コメント

タイトルとURLをコピーしました