Top > MyPage
 

Xercesの編集について

Xercesの修正

理由

XMLをRelaxで検証するmanekinekoを<URL:http://people.apache.org/~andyc/neko/doc/relaxng/>裏で Jingを使っていますが、parserとしては結局のところXercesのDOMParserを使っ ているようです(後に理由も)。

さて、このmanekinekoを使ってXMLをRelaxでValidateしたいと考えたのですが、 少々問題が、今回のような用途では問題が持ち上がりました。

その問題ですが、整形式XML(well-formed XML)かどうかは、そうでないと例外 が送出されるので、トラップできてOKなんですが、妥当なXML(valid XML)かど うかは、標準出力で

[Error] badconfig.xml:2:7: required attributes missing
[Error] badconfig2.xml:3:11: required attributes missing

と表示はされるのですが、内部的には例外等が送出されたり、或いは何かフラ グがつくなどということがなく、トラップできません(つまり、エラーが起こっ ているのかどうかを検出できない---内的には整形式かどうかはfatal error、 妥当なXMLかどうかはただのerrorという位置づけになっているようです)。

parser.parse("badconfig.xml");

で、例外でも起こらないとまったくプログラム的には検知できないというわけ です。これでは、XMLの検証をしようという今回の目的を達成できないわけで す。

修正箇所

まずは、もともとどういうようにvalidateしているかというと結局

import org.cyberneko.relaxng.parsers.DOMParser;

.....

DOMParser parser = new DOMParser();

try {
    parser.setFeature("http://xml.org/sax/features/namespaces", true);
    parser.setFeature("http://xml.org/sax/features/validation", true);

    parser
            .setProperty(
"http://cyberneko.org/xml/properties/relaxng/schema-location",
                    "config.rng");
    parser.parse("badconfig.xml");
} catch (SAXParseException e) {
....

の、

parser.parse("badconfig.xml");

です。このParserは一見すると「org.cyberneko.relaxng.parsers.DOMParser」 ですが、実際はその中身は

public class DOMParser
    extends org.apache.xerces.parsers.DOMParser {
    public DOMParser() {
        super(new JingConfiguration());
    } // <init>()

} // class DOMParser

とあるだけなので、実体は「org.apache.xerces.parsers.DOMParser」です。 なのでこの周辺を修正することになると想像できます。

もちろん、じっくり追っていって、いくつかのクラスを継承したクラスを作れ ば何とかなるのかもしれませんが、時間のことを考えて安易な方法をとったと いうのが、実際のところで、メモするにが憚れますが、とりあえずメモということで。

そこで、色々とソースを追ったのですが、XMLパーサ(Xerces)の哲学がわかって いないせいもあり、どこに何を入れ込めば良いかがわからず無為に時間が過ぎ ていったので、後述するように問題点はありますが、次のような方法をとりま した。

  • エラーが起こった際のメッセージを保存するシングルトンクラスを作成
  • エラーが起こったら(fatal errorの場合は先述したように、例外が送出されるのでそれでトラップ)、上記のシングルトンクラスにエラーメッセージをaddします(ArrayListに)。
  • それで、検出する側では、そのシングルトンクラスをgetInstanceして、エラーが起こったかどうかをチェックする

問題は大ありです。たぶん、

  • 同じ瞬間にこのクラスにアクセスした場合、ほんのちょっと先のスレッドが初期化したArrayListにデータを入れている間に、ほんのちょっと遅れてきたスレッドが初期化するというようなことが起こる可能性がある(かもしれない)。
  • しかし下手にwaitを入れるのはdead lockになりかねない。

など、未解決な部分が残っています。誰か、解決してください。ただ、管理者しかやらない作業の上に、それ自体めったに行わないことを考えると、可能性は相当低いと言うことは言えますけれど・・・。

というわけで、実際変更した中身は

org.apache.xerces.util.DefaultErrorHandler

private void printError(String type, XMLParseException ex) {
    ErrorMessage em = ErrorMessage.getInstance();
    String mes = "";
    mes = mes + "[";
    fOut.print("[");
    mes = mes + type;
    fOut.print(type);
    fOut.print("] ");
    mes = mes + "]";
    String systemId = ex.getExpandedSystemId();
    if (systemId != null) {
        int index = systemId.lastIndexOf('/');
        if (index != -1)
            systemId = systemId.substring(index + 1);
        fOut.print(systemId);
        mes = mes + systemId;
    }
    fOut.print(':');
    mes = mes + ":";
    fOut.print(ex.getLineNumber());
    mes = mes + ex.getLineNumber();
    fOut.print(':');
    mes = mes + ":";
    fOut.print(ex.getColumnNumber());
    mes = mes + ex.getColumnNumber();
    fOut.print(": ");
    mes = mes + ": ";
    fOut.print(ex.getMessage());
    mes = mes + ex.getMessage();
    fOut.println();
    fOut.flush();
    em.addMessage(mes);
} // printError(String,SAXParseException)

ErrorMessage関係のところで、要するに最後に標準出力しているメッセージをシングルトンのにErrorMessageに対して

em.addMessage(mes);

しているわけです。そのErrorMessageは

package org.apache.xerces.xni;
import java.util.ArrayList;
import java.util.List;

public class ErrorMessage {

  private static ErrorMessage instance;
  private static ArrayList messages =  new ArrayList();
  private static Object _lock = new Object();

  private ErrorMessage() {
     super();
  }

  public void addMessage(String m){
     if(m != null) {
       messages.add(m);
     }
  }

  public ArrayList getMessages() {
     return messages;
  }
  public static synchronized ErrorMessage getInstance() {
      if (instance == null) {
        instance = new ErrorMessage();
      }
      return instance;
  }

  public void initMessage() {
     messages = new ArrayList();
  }
}

です。もっと良い方法を探すべき何だが・・・・。

[2007-10-08 10:25]