

.NET
FrameworkのSystem.Xml.XmlReader（実体は派生クラス）を使用してXMLデータを読み込むと、属性値の中の空白文字が正規化されません。

属性値の中の空白文字の正規化とは、例えば以下のようなものです。

  * ~~`" foo&#xD;&#xA; bar "` ⇒ `"foo bar"`~~
  * `" foo bar "` ⇒ `"foo bar"`

参考：

  * [＠IT：やさしく読む「XML 1.0勧告」 第19回 落とし穴が潜む属性値の正規化](http://www.atmarkit.co.jp/fxml/rensai/w3cread19/w3cread19.html)

XmlReaderを直接使用して読み込む時は、自分で正規化することができるのですが、XmlSerializerにXmlReaderを渡してデシリアライズする時に正規化する良い方法が思い付きません。  
以下の2つの方法以外に、この問題を回避する方法はないでしょうか？

  * XmlReaderを直接使用して一旦正規化したXMLデータを作成してから、改めてXmlSerializerでデシリアライズする  
→ XMLデータを2回解析するので処理効率が悪く、XmlReaderを使うメリットが半減しそう

  * XmlReaderの派生クラスを新規作成して、属性値を正規化するValueプロパティを定義する  
→
全ての抽象・仮想メンバーをオーバーライドしてXmlReader.Createで作成したインスタンスに委譲する必要があり、実装が面倒。そもそもこの方法でできるのか、やってみないとわからない

以下ができれば、処理効率、実装効率共に良さそうな対策（イメージです）：

[code]

    using (var xmlReader = XmlReader.Create(streamReader, xmlSettings))
    {
        // Rubyの特異メソッドのようなことができれば…
        public override string xmlReader.Value
        {
            get {
                var value = 委譲先.Value
                if (NodeType == XmlNodeType.Attribute)
                {
                    //valueの正規化
                }
                return value;
            }
        }
    
        t = (T)xmlSerializer.Deserialize(xmlReader);
    }
    
[/code]


CC BY-SA 3.0
2017-10-08T09:47:48.547
38530
2017-10-09T08:32:59.917
2017-10-09T08:12:58.733
7291
7291
question
c# .net xml
XmlReaderで属性値が正規化されない問題の回避


非同期処理を行わないのであれば`XmlTextReader`を継承し、`Value`プロパティだけを`override`することができます。


CC BY-SA 3.0
2017-10-08T11:19:24.430
38531
2017-10-08T11:19:24.430
5750
38530
answer


リンク先には

>
> 最初は、`&#x1234;`のような文字参照は、それが示す文字を空き箱に追加するという意味である。「正規化された値に追加する」とは空き箱に追加する、つまり、文字参照で記述された文字はその文字として扱われ、それ以上何かの処理をされることはない。
> **その点で、この後に記述された処理が行われるケースとは明らかに結果が異なる。**
> 正規化の対象にしたくない文字は、文字参照として書いておくというテクニックがあり得るだろう。

と説明されています（特に最後の一文）。もちろん[Extensible Markup Language (XML) 1.0 (Fifth
Edition)、3.3.3 Attribute-Value
Normalization](https://www.w3.org/TR/xml/#AVNormalize)にも

>   * For a character reference, append the referenced character to the
> normalized value.
>

とあります。`&#xD;&#xA;`が`\r\n`に展開され、``に置換されないのは仕様通りの動作では？  
実際、

[code]

    <element attribute="a
        b&#xD;&#xA;c"></element>
    
[/code]

の`attribute`の値は`a b\r\nc`になっていました。

* * *

> 実際に困っている例は`" foo bar "`が`"foo bar"`にならない部分（前後の空白削除と、途中の空白圧縮）です。

これもリンク先に書かれている通りです。

>
> 前半は、この規定の発動条件について述べられている。DTDを使わない場合、属性はCDATA型として扱うことが望ましいので、これは主にDTDで属性の型を記述している場合に意味を持つ規定であるといえる。

DTDで属性を`NMTOKENS`と指定すれば仕様通りに正規化されますし、未指定だったり`CDATA`の場合は仕様通り変換されません。なお、DTDを処理するように[`XmlReaderSettings.DtdProcessing`](https://msdn.microsoft.com/en-
us/library/system.xml.xmlreadersettings.dtdprocessing\(v=vs.110\).aspx)を`DtdProcessing.Parse`に設定する必要があります。

[code]

    using System;
    using System.IO;
    using System.Xml;
    using System.Xml.Serialization;
    
    public class Test
    {
        [XmlAttribute]
        public string Nmtokens { get; set; }
        [XmlAttribute]
        public string Cdata { get; set; }
        public string Elem { get; set; }
    
        public static void Main()
        {
            var xml = @"<!DOCTYPE Test [
              <!ELEMENT Test (Elem)>
              <!ATTLIST Test Nmtokens NMTOKENS #REQUIRED Cdata CDATA #REQUIRED>
              <!ELEMENT Elem (#PCDATA)>
            ]>
            <Test Nmtokens=' foo  bar ' Cdata=' foo bar '>
              <Elem> foo   bar </Elem>
            </Test>
            ";
            var reader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse });
            var test = (Test)new XmlSerializer(typeof(Test)).Deserialize(reader);
            Console.WriteLine("Nmtokens=<{0}>, Cdata=<{1}>, Elem=<{2}>", test.Nmtokens, test.Cdata, test.Elem);
            // Nmtokens=<foo bar>, Cdata=< foo bar >, Elem=< foo   bar >
        }
    }
    
[/code]

* * *

質問文全体に渡って「属性値の中の空白文字が正規化されません」等、変換されることが仕様に沿った動作かのような記述がされていますが、仕様外の **特殊変換**
を望んでいることを認識されるべきです。


CC BY-SA 3.0
2017-10-09T00:44:50.657
38538
2017-10-09T08:32:59.917
2017-10-09T08:32:59.917
4236
4236
38530
answer