アイキャッチ

実装・エラー解決

Spring XMLで「属性指定 > または />」エラー|U+00A0の見つけ方と修正

全ての結論

SpringのXML設定をコピーした後に開始タグで解析エラーが出たら、属性の前の空白がU+00A0になっていないか確認します。

事象

当時、EclipseでSpringの app-context.xml を別プロジェクトへコピーしたところ、beans:beans の後に属性指定か > または /> が必要だというエラーが出ました。配布の broken.xml では、開始タグ名と xmlns:beans の間をU+00A0にしています。

<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"/>

原因

U+00A0は改行を禁止する空白で、見た目が通常の空白と似ています。XML 1.0で属性の区切りとして認められるのは、U+0020の空白、タブ、LF、CRです。U+00A0が開始タグ名の直後にあると、パーサーは属性の区切りとして読めません。この段階ではBeanの登録やXMLスキーマの検証まで進んでいません。

Eclipseで見た目が空白の文字を選択した当時の画面

対応

エラー箇所の直前にある空白を削除し、属性との区切りを半角スペースで入力し直します。属性値や要素の本文にも意図したU+00A0がある場合があるため、置換は構文上の区切りに絞ります。

<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"/>

具体的な話

エディターの文字コード表示で、壊れた側はU+00A0、修正側はU+0020と確認できます。次の検証コードはJDK 17のXMLパーサーを使い、同じタグの区切り1文字だけで結果が変わることを確かめます。

import java.nio.file.Files;
import java.nio.file.Path;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

public class XmlWhitespaceCheck {
    public static void main(String[] args) throws Exception {
        Path broken = Path.of("broken.xml");
        Path fixed = Path.of("fixed.xml");
        String original = Files.readString(broken);
        String repaired = Files.readString(fixed);
        String tag = "<beans:beans";

        if (original.codePointAt(tag.length()) != 0x00A0
                || repaired.codePointAt(tag.length()) != 0x0020
                || !original.replace(tag + "\u00a0", tag + " ").equals(repaired)) {
            throw new AssertionError("expected one change to the tag's attribute separator");
        }

        try {
            parse(broken);
            throw new AssertionError("U+00A0 was accepted as XML syntax whitespace");
        } catch (SAXParseException expected) {
            System.out.printf("broken.xml: U+00A0 rejected (line %d)%n", expected.getLineNumber());
        }
        parse(fixed);
        System.out.println("fixed.xml: U+0020 accepted");
    }

    private static void parse(Path path) throws Exception {
        var factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
        DocumentBuilder builder = factory.newDocumentBuilder();
        builder.setErrorHandler(new DefaultHandler() {
            @Override
            public void fatalError(SAXParseException error) throws SAXParseException {
                throw error;
            }
        });
        builder.parse(path.toFile());
    }
}
javac XmlWhitespaceCheck.java
java XmlWhitespaceCheck
# broken.xml: U+00A0 rejected (line 1)
# fixed.xml: U+0020 accepted

出典 W3CのXML 1.0構文定義

-実装・エラー解決