Unity で xsd(xml Schema)ファイルを用いて xml ファイルを検証する

本記事は Unity 2019.3.3f1 及び Microsoft Visual Studio Community 2019 version 16.5.4 を使用しております。

##本記事の内容
タイトルの通り、xsd ファイルを用いて xmlファイルの検証をします。
第1項 では、外部 xml,xsd ファイルから読み込み、検証を行い、
第2項 では、Resource.Load を用いて検証を行います。

###本記事で使う xsd,xml ファイルは以下になります。

```xsd:Event.xsd
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="hogehoge/Event"
          elementFormDefault="qualified"
          xmlns="http://tempuri.org/XMLSchema.xsd"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xmlns:Event="hogehoge/Event"
          xmlns:Status="hogehoge/Status">
<xs:import schemaLocation="Status.xsd" namespace="hogehoge/Status"/>
  <xs:element name="Event">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Message">
          <xs:complexType mixed="true">
            <xs:attribute name="Name" type="Status:Name"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
```

```xsd:Status.xsd

<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="hogehoge/Status"
          elementFormDefault="qualified"
          xmlns="http://tempuri.org/XMLSchema.xsd"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xmlns:Status="hogehoge/Status">

  <xs:simpleType name="Name">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Anyone"/>
      <xs:enumeration value="Someone" />
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

```

```xml:Message.xml
<?xml version="1.0" encoding="utf-8"?>
<Event xmlns="hogehoge/Event">
  <Message Name="Anyone">
    私はどこかにいるだれか
  </Message>
  <Message Name="Someone">
    僕はここにいるだれか
  </Message>
</Event>
```


##1.外部 xsd,xml ファイルを読み込み、検証する
まず、外部 xsd,xml ファイルを読み込む場合には以下のようになります。

```cs:XmlGetter.cs
/// 変数の先頭の p は passed(引数)
/// l は local(メソッドスコープ内)
/// m は member(プロパティ)の略語です。

public static class XmlGetter
{
    public static XmlSchemaSet AddSchemaFromXsd(string pSchemaPath)
    {
        XmlSchemaSet lSchemas = new XmlSchemaSet();
        XmlReader lSchemaReader = null;

        string targetNamespace = "hogehoge/Event";
        try
        {
            lSchemas.Add(targetNamespace, lSchemaReader = XmlReader.Create(pSchemaPath));

        }
        catch (XmlSchemaValidationException e)
        {
            Debug.LogError(e.Message);
            Debug.LogError("error line : " + e.LineNumber);
            Debug.LogError("error position : " + e.LinePosition);
        }
        finally
        {
            if (lSchemaReader.ReadState != ReadState.Closed)
            {
                lSchemaReader.Close();
            }
        }
        return schemas;
    }

    public static XDocument GetXDocumentFromXml(string pXmlPath, XmlSchemaSet pSchemas)
    {

        XmlReaderSettings lSettings = new XmlReaderSettings();

        lSettings.ValidationType = ValidationType.Schema;

        lSettings.Schemas = pSchemas;

        XmlReader lXmlReader = null;
        XDocument pEventXml = null;
        try
        {
            lXmlReader = XmlReader.Create(pXmlPath, lSettings);
            pEventXml = XDocument.Load(lXmlReader);
        }
        catch (XmlSchemaValidationException e)
        {
            Debug.LogError(e.Message);
            Debug.LogError("error line : " + e.LineNumber);
            Debug.LogError("error position : " + e.LinePosition);

        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
        }
        finally
        {
            if (lXmlReader.ReadState != ReadState.Closed)
            {
                lXmlReader.Close();
            }
        }
        return pEventXml;
    }
}
```

```cs:XmlGetTest.cs
public class XmlGetTest:MonoBehavior
{
    public void Awake()
    {
        string lSchemaPath = "Event.xsd";
        XmlSchemaSet lSchemas = AddSchemaFromXsd(lSchemaPath);
        string lXmlPath = "Event.xml";
        XDocument lXDocument = GetXDocumentFromXml(lXmlPath, lSchemas);
    }

}
```
これは、Unity のプロジェクトの外に XML,XSD ファイルが共にある場合に使えます。
その代わりに、プロジェクトをビルドした後に、自分でフォルダをビルドフォルダに加えなければいけません。
path は自分のファイルへのパスに適時置き換えて下さい。

xmlReaderSetting の ValidationType に Schema をセットすることで、Load 時に検証も行っています。 


##2.Resource.Load で xsd ファイルを読み込む
しかし、Unity で Resource フォルダ下に XML,XSD ファイルを置きたい場合があります。今回はその例を紹介します。

Resouce.Load を使う場合、そのファイル形式は Unity で対応していなければいけません。

###2-1.ScriptedImporter を使う
Unity が対応していないファイルを使う場合、ScriptedImporter クラスを使います。

ScriptedImporter の詳しい説明はテラシュールブログさんを
参照して下さい。
テラシュールブログさん:
http://tsubakit1.hateblo.jp/entry/2017/12/14/012746

ScriptedImporter を使って xsd を TextAsset として扱うクラスを作ります。

```cs:XsdImporter.cs
[ScriptedImporter(1, "xsd")]
public class XSDImporter : ScriptedImporter
{
    public override void OnImportAsset(AssetImportContext ctx)
    {
        TextAsset lXsd;

        try
        {
            lXsd = new TextAsset( File.ReadAllText(ctx.assetPath) );
            ctx.AddObjectToAsset("XSD", lXsd);
            ctx.SetMainObject(lXsd);

        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
    }
}

```

これで xsd ファイルを Resource.Load で読み込むことが出来るようになりました。

###2-2.TextAsset を StringReader を経由して XmlReader インスタンスを生成する。
xsd ファイルを TextAsset として使う場合、StringReader クラスを使い、XmlReader インスタンスを生成します。

```cs:XmlGetter.cs

    public static XmlSchemaSet AddSchemaFromTextAsset(TextAsset pSchemaAsset)
    {
        XmlSchemaSet lSchemas = new XmlSchemaSet();
        XmlReader lSchemaReader = null;
        StringReader lStringReader = null;
        string lTargetNamespace = "hogehoge/Event";
        try
        {
            lStringReader = new StringReader(pSchemaAsset.text);
            lSchemas.Add(lTargetNamespace, lSchemaReader =XmlReader.Create( lStringReader));

        }
        catch (XmlSchemaValidationException e)
        {
            Debug.LogError(e.Message);
            Debug.LogError("error line : " + e.LineNumber);
            Debug.LogError("error position : " + e.LinePosition);
        }
        finally
        {
            if (lSchemaReader.ReadState != ReadState.Closed)
            {
                lSchemaReader.Close();
            }
        }
        return lSchemas;

    }
```

AddSchemaFromTextAsset メソッドが、渡された TextAsset の text プロパティを使った StringReader を経由して XmlReader インスタンスを作成し、 XmlSchemaSet に変換します。

xml も同様に TextAsset から StringReader を用いて XmlReader インスタンスを作成して下さい。

###2-3. import する xsd ファイルを XmlSchemaSet クラスに Add する
しかし、ここでエラーが発生します。

StringReader を使って XmlReader を作成すると、import した xsd ファイルを SchemaSet に Add する事が出来ません。

そこで、少々周りくどいですが、一つづつ SchemaSet を Add していく手段を取ります。

ついでにパスを一つづつ設定するのが嫌なのでパスジェネレーターも作りました。

```cs:PathGenerator.cs
public class PathGenerator
{
    string mBasePath;
    public PathGenerator(string pBaseath)
    {
        mBasePath = pBasepath;
    }

    public string GeneratePath(params string[] pPathes)
    {
        StringBuilder lGeneratePath =new StringBuilder().Append(mBasePath);
        foreach (string path in pPathes)
        {
            lGeneratePath.Append("/").Append(path);
        }
        return lGeneratePath.ToString();
    }
}
```

```cs:XmlGetter.cs
public enum eSchema
{
    Event,
    Status
}

public class XmlGetter
{

    /// 中略
    /// SchemaPath は自分のスキーマフォルダへのパスを入れて下さい。

    public Dictionary<string,string> GeneratePathPair()
    {
        string lEventStr = eSchema.Event.ToString();
        string lStatusStr = eSchema.Status.ToString();

        PathGenerator lTnsGenerator = new PathGenerator("hogehoge");
        PathGenerator lPathGenerator = new PathGenerator(SchemaPath);
        Dictionary<string, string> lTnsPathPairInXsd = new Dictionary<string, string>
        {
            [lTnsGenerator.GeneratePath(lEvent)] = lPathGenerator.GeneratePath(lEvent),
            [lTnsGenerator.GeneratePath(lStatus)] =
lPathGenerator.GeneratePath(lStatus)
        };
        return lTnsPathPairInXsd;
    }

    public static XmlSchemaSet GetSchemaFromXSD(Dictionary<string,string> pXsd)
    {
        XmlSchemaSet lSchemas = new XmlSchemaSet();
        StringReader lStringReader = null;
        XmlReader lSchemaReader = null;

        foreach (KeyValuePair<string, string> item in pXsd)
        {
            try
            {
                TextAsset xsdAsset = Resources.Load<TextAsset>(item.Value);
                lStringReader = new StringReader(xsdAsset.text);
                lSchemaReader = XmlReader.Create(lStringReader);
                lSchemas.Add(item.Key, lSchemaReader);

            }
            catch (XmlSchemaValidationException e)
            {
                Debug.LogError(e.Message);
                Debug.LogError("error line : " + e.LineNumber);
                Debug.LogError("error position : " + e.LinePosition);
            }
            finally
            {
                if (lStringReader != null)
                {
                    lStringReader.Close();
                }
                if (lSchemaReader.ReadState != ReadState.Closed)
                {
                    lSchemaReader.Close();

                }
            }
        }
        return lSchemas;
    }

}
```

何か指摘ありましたら宜しくお願いします。

参考にしたサイト。
xmlSchemaSet
https://docs.microsoft.com/ja-jp/dotnet/api/system.xml.schema.xmlschemaset?view=netcore-3.1

XmlReader
https://docs.microsoft.com/ja-jp/dotnet/api/system.xml.xmlreader?view=netcore-3.1

StringReader
https://docs.microsoft.com/ja-jp/dotnet/api/system.io.stringreader?view=netcore-3.1

XDocument
https://docs.microsoft.com/ja-jp/dotnet/api/system.xml.linq.xdocument?view=netcore-3.1


XmlSchema,Xml に関して参考にした書籍(「基礎 XML」著:山田祥寛)