XML コンテンツの書き込み時の特殊文字の変換
更新 : November 2007
XmlWriter に含まれている WriteRaw メソッドを使用すると、生のマークアップを手動で書き出すことができます。このメソッドでは、特殊文字をエスケープしません。対照的に、WriteString メソッドは、一部の文字列を等価のエンティティ参照にエスケープします。エスケープする文字は、XML 1.0 勧告のセクション『2.4 Character Data and Markup』と、勧告『Extensible Markup Language (XML) 1.0 (Second Edition)』のセクション『3.3.3 Attribute-Value Normalization』に示されています。属性値を書くときに WriteString メソッドを呼び出すと、' と " がエスケープされます。文字値 0x ~ 0x1F は、空白文字 0x9、0x10、および 0x13 を除いて、数値文字エンティティ � ~  にエンコードされます。
そのため、WriteString または WritingRaw のどちらを使用するか迷った場合は、原則として、すべての文字を対象としてエンティティ文字を検索する必要があるときは WriteString を使用し、渡された文字をそのまま書き込むときは WriteRaw を使用します。
WriteNode メソッドでは、現在のノードからすべてをコピーし、リーダーをライタの位置に置きます。その後、リーダーを次の兄弟ノードに移動して、処理を続行します。WriteNode メソッドを利用すれば、あるドキュメントから別のドキュメントへ簡単に情報を移動できます。
WriteNode メソッドでサポートされている NodeType を次の表に示します。
ノード型 |
説明 |
---|---|
Element |
要素ノードとすべての属性ノードを書き出します。 |
Attribute |
何も実行しません。属性を書き出すには、WriteStartAttribute または WriteAttributeString を使用します。 |
Text |
テキスト ノードを書き出します。 |
CDATA |
CDATA セクション ノードを書き出します。 |
EntityReference |
エンティティ参照ノードを書き出します。 |
ProcessingInstruction |
PI ノードを書き出します。 |
Comment |
コメント ノードを書き出します。 |
DocumentType |
DocType ノードを書き出します。 |
Whitespace |
空白ノードを書き出します。 |
SignificantWhitespace |
空白ノードを書き出します。 |
EndElement |
何も実行しません。 |
EndEntity |
何も実行しません。 |
次の例は、"<" 文字を渡されたときの WriteString メソッドと WriteRaw メソッドの動作の違いを示しています。まず、WriteString を使用するコード サンプルを示します。
w.WriteStartElement("myRoot")
w.WriteString("<")
w.WriteEndElement()
Dim tw As New XmlTextWriter(Console.Out)
tw.WriteDocType(name, pubid, sysid, subset)
w.WriteStartElement("myRoot");
w.WriteString("<");
w.WriteEndElement();
XmlTextWriter tw = new XmlTextWriter(Console.Out);
tw.WriteDocType(name, pubid, sysid, subset);
出力
<myRoot><</myRoot>
次に、WriteRaw を使用するコード サンプルを示します。出力には要素コンテンツとして無効な文字が含まれています。
w.WriteStartElement("myRoot")
w.WriteRaw("<")
w.WriteEndElement()
w.WriteStartElement("myRoot");
w.WriteRaw("<");
w.WriteEndElement();
出力
<myRoot><</myRoot>
要素中心のドキュメントから属性中心のドキュメントに XML ドキュメントを変換する方法を示す例を次に示します。属性中心の XML ドキュメントを要素中心のドキュメントに変換することもできます。要素中心モードでは、XML ドキュメント内の要素数が多く、属性数が少なくなります。属性中心デザインでは、要素数が少なくなり、要素中心デザインでは要素となるはずの値が要素の属性となります。このため要素の数が少なくなりますが、1 つの要素あたりの属性数が多くなります。
どちらかのモードで XML データをデザインした後に、もう一方のモードに変換する方法を示す便利なコード サンプルを次に示します。
この例の XML は、要素中心ドキュメントを使用しています。要素に属性は含まれません。
入力 - centric.xml
<?xml version='1.0' encoding='UTF-8'?>
<root>
<Customer>
<firstname>Jerry</firstname>
<lastname>Larson</lastname>
<Order>
<OrderID>Ord-12345</OrderID>
<OrderDetail>
<Quantity>1301</Quantity>
<UnitPrice>$3000</UnitPrice>
<ProductName>Computer</ProductName>
</OrderDetail>
</Order>
</Customer>
</root>
変換を行うサンプル アプリケーションを次に示します。
' The program will convert an element-centric document to an
' attribute-centric document or element-centric to attribute-centric.
Imports System
Imports System.Xml
Imports System.IO
Imports System.Text
Imports System.Collections
Class ModeConverter
Private bufferSize As Integer = 2048
Friend Class ElementNode
Private _name As [String]
Private _prefix As [String]
Private _namespace As [String]
Private _startElement As Boolean
Friend Sub New()
Me._name = Nothing
Me._prefix = Nothing
Me._namespace = Nothing
Me._startElement = False
End Sub 'New
Friend Sub New(prefix As [String], name As [String], [nameSpace] As [String])
Me._name = name
Me._prefix = prefix
Me._namespace = [nameSpace]
End Sub 'New
Public ReadOnly Property name() As [String]
Get
Return _name
End Get
End Property
Public ReadOnly Property prefix() As [String]
Get
Return _prefix
End Get
End Property
Public ReadOnly Property [nameSpace]() As [String]
Get
Return _namespace
End Get
End Property
Public Property startElement() As Boolean
Get
Return _startElement
End Get
Set
_startElement = value
End Set
End Property
End Class 'ElementNode
' Entry point which delegates to C-style main Private Function.
Public Overloads Shared Sub Main()
Main(System.Environment.GetCommandLineArgs())
End Sub
Overloads Public Shared Sub Main(args() As [String])
Dim modeConverter As New ModeConverter()
If args(0) Is Nothing Or args(0) = "?" Or args.Length < 2 Then
modeConverter.Usage()
Return
End If
Dim sourceFile As New FileStream(args(1), FileMode.Open, FileAccess.Read, FileShare.Read)
Dim targetFile As New FileStream(args(2), FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)
If args(0) = "-a" Then
modeConverter.ConertToAttributeCentric(sourceFile, targetFile)
Else
modeConverter.ConertToElementCentric(sourceFile, targetFile)
End If
Return
End Sub 'Main
Public Sub Usage()
Console.WriteLine("? This help message " + ControlChars.Lf)
Console.WriteLine("Convert -mode sourceFile, targetFile " + ControlChars.Lf)
Console.WriteLine(ControlChars.Tab + " mode: e element centric" + ControlChars.Lf)
Console.WriteLine(ControlChars.Tab + " mode: a attribute centric" + ControlChars.Lf)
End Sub 'Usage
Public Sub ConertToAttributeCentric(sourceFile As FileStream, targetFile As FileStream)
' Stack is used to track how many.
Dim stack As New Stack()
Dim reader As New XmlTextReader(sourceFile)
reader.Read()
Dim writer As New XmlTextWriter(targetFile, reader.Encoding)
writer.Formatting = Formatting.Indented
Do
Select Case reader.NodeType
Case XmlNodeType.XmlDeclaration
writer.WriteStartDocument((Nothing = reader.GetAttribute("standalone") Or "yes" = reader.GetAttribute("standalone")))
Case XmlNodeType.Element
Dim element As New ElementNode(reader.Prefix, reader.LocalName, reader.NamespaceURI)
If 0 = stack.Count Then
writer.WriteStartElement(element.prefix, element.name, element.nameSpace)
element.startElement = True
End If
stack.Push(element)
Case XmlNodeType.Attribute
Throw New Exception("We should never been here!")
Case XmlNodeType.Text
Dim attribute As New ElementNode()
attribute = CType(stack.Pop(), ElementNode)
element = CType(stack.Peek(), ElementNode)
If Not element.startElement Then
writer.WriteStartElement(element.prefix, element.name, element.nameSpace)
element.startElement = True
End If
writer.WriteStartAttribute(attribute.prefix, attribute.name, attribute.nameSpace)
writer.WriteRaw(reader.Value)
reader.Read() 'jump over the EndElement
Case XmlNodeType.EndElement
writer.WriteEndElement()
stack.Pop()
Case XmlNodeType.CDATA
writer.WriteCData(reader.Value)
Case XmlNodeType.Comment
writer.WriteComment(reader.Value)
Case XmlNodeType.ProcessingInstruction
writer.WriteProcessingInstruction(reader.Name, reader.Value)
Case XmlNodeType.EntityReference
writer.WriteEntityRef(reader.Name)
Case XmlNodeType.Whitespace
writer.WriteWhitespace(reader.Value);
Case XmlNodeType.None
writer.WriteRaw(reader.Value)
Case XmlNodeType.SignificantWhitespace
writer.WriteWhitespace(reader.Value)
Case XmlNodeType.DocumentType
writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value)
Case XmlNodeType.EndEntity
Case Else
Console.WriteLine(("UNKNOWN Node Type = " + CInt(reader.NodeType)))
End Select
Loop While reader.Read()
writer.WriteEndDocument()
reader.Close()
writer.Flush()
writer.Close()
End Sub 'ConertToAttributeCentric
' Use the WriteNode to simplify the process.
Public Sub ConertToElementCentric(sourceFile As FileStream, targetFile As FileStream)
Dim reader As New XmlTextReader(sourceFile)
reader.Read()
Dim writer As New XmlTextWriter(targetFile, reader.Encoding)
writer.Formatting = Formatting.Indented
Do
Select Case reader.NodeType
Case XmlNodeType.Element
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI)
If reader.MoveToFirstAttribute() Then
Do
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI)
writer.WriteRaw(reader.Value)
writer.WriteEndElement()
Loop While reader.MoveToNextAttribute()
writer.WriteEndElement()
End If
Case XmlNodeType.Attribute
Throw New Exception("We should never been here!")
Case XmlNodeType.Whitespace
writer.WriteWhitespace(reader.Value)
Case XmlNodeType.EndElement
writer.WriteEndElement()
Case XmlNodeType.Text
Throw New Exception("The input document is not a attribute centric document" + ControlChars.Lf)
Case Else
Console.WriteLine(reader.NodeType)
writer.WriteNode(reader, False)
End Select
Loop While reader.Read()
reader.Close()
writer.Flush()
writer.Close()
End Sub 'ConertToElementCentric
End Class 'ModeConverter
// The program will convert an element-centric document to an
// attribute-centric document or element-centric to attribute-centric.
using System;
using System.Xml;
using System.IO;
using System.Text;
using System.Collections;
class ModeConverter {
private const int bufferSize=2048;
internal class ElementNode {
String _name;
String _prefix;
String _namespace;
bool _startElement;
internal ElementNode() {
this._name = null;
this._prefix = null;
this._namespace = null;
this._startElement = false;
}
internal ElementNode(String prefix, String name, String nameSpace) {
this._name = name;
this._prefix = prefix;
this._namespace = nameSpace;
}
public String name{
get { return _name; }
}
public String prefix{
get { return _prefix; }
}
public String nameSpace{
get { return _namespace; }
}
public bool startElement{
get { return _startElement; }
set { _startElement = value;}
}
}
public static void Main(String[] args) {
ModeConverter modeConverter = new ModeConverter();
if (args[0]== null || args[0]== "?" || args.Length < 2 ) {
modeConverter.Usage();
return;
}
FileStream sourceFile = new FileStream(args[1], FileMode.Open, FileAccess.Read, FileShare.Read);
FileStream targetFile = new FileStream(args[2], FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
if (args[0] == "-a") {
modeConverter.ConertToAttributeCentric(sourceFile, targetFile);
} else {
modeConverter.ConertToElementCentric(sourceFile, targetFile);
}
return;
}
public void Usage() {
Console.WriteLine("? This help message \n");
Console.WriteLine("Convert -mode sourceFile, targetFile \n");
Console.WriteLine("\t mode: e element centric\n");
Console.WriteLine("\t mode: a attribute centric\n");
}
public void ConertToAttributeCentric(FileStream sourceFile, FileStream targetFile) {
// Stack is used to track how many.
Stack stack = new Stack();
XmlTextReader reader = new XmlTextReader(sourceFile);
reader.Read();
XmlTextWriter writer = new XmlTextWriter(targetFile, reader.Encoding);
writer.Formatting = Formatting.Indented;
do {
switch (reader.NodeType) {
case XmlNodeType.XmlDeclaration:
writer.WriteStartDocument(null == reader.GetAttribute("standalone") || "yes" == reader.GetAttribute("standalone"));
break;
case XmlNodeType.Element:
ElementNode element = new ElementNode(reader.Prefix, reader.LocalName, reader.NamespaceURI);
if (0 == stack.Count) {
writer.WriteStartElement(element.prefix, element.name, element.nameSpace);
element.startElement=true;
}
stack.Push(element);
break;
case XmlNodeType.Attribute:
throw new Exception("We should never been here!");
case XmlNodeType.Text:
ElementNode attribute = new ElementNode();
attribute = (ElementNode)stack.Pop();
element = (ElementNode)stack.Peek();
if (!element.startElement) {
writer.WriteStartElement(element.prefix, element.name, element.nameSpace);
element.startElement=true;
}
writer.WriteStartAttribute(attribute.prefix, attribute.name, attribute.nameSpace);
writer.WriteRaw(reader.Value);
reader.Read(); //jump over the EndElement
break;
case XmlNodeType.EndElement:
writer.WriteEndElement();
stack.Pop();
break;
case XmlNodeType.CDATA:
writer.WriteCData(reader.Value);
break;
case XmlNodeType.Comment:
writer.WriteComment(reader.Value);
break;
case XmlNodeType.ProcessingInstruction:
writer.WriteProcessingInstruction(reader.Name, reader.Value);
break;
case XmlNodeType.EntityReference:
writer.WriteEntityRef( reader.Name);
break;
case XmlNodeType.Whitespace:
writer.WriteWhitespace(reader.Value);
break;
case XmlNodeType.None:
writer.WriteRaw(reader.Value);
break;
case XmlNodeType.SignificantWhitespace:
writer.WriteWhitespace(reader.Value);
break;
case XmlNodeType.DocumentType:
writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
break;
case XmlNodeType.EndEntity:
break;
default:
Console.WriteLine("UNKNOWN Node Type = " + ((int)reader.NodeType));
break;
}
} while (reader.Read());
writer.WriteEndDocument();
reader.Close();
writer.Flush();
writer.Close();
}
// Use the WriteNode to simplify the process.
public void ConertToElementCentric(FileStream sourceFile, FileStream targetFile) {
XmlTextReader reader = new XmlTextReader(sourceFile);
reader.Read();
XmlTextWriter writer = new XmlTextWriter(targetFile, reader.Encoding);
writer.Formatting = Formatting.Indented;
do {
switch (reader.NodeType) {
case XmlNodeType.Element:
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
if (reader.MoveToFirstAttribute()) {
do {
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
writer.WriteRaw(reader.Value);
writer.WriteEndElement();
} while(reader.MoveToNextAttribute());
writer.WriteEndElement();
}
break;
case XmlNodeType.Attribute:
throw new Exception("We should never been here!");
case XmlNodeType.Whitespace:
writer.WriteWhitespace(reader.Value);
break;
case XmlNodeType.EndElement:
writer.WriteEndElement();
break;
case XmlNodeType.Text:
throw new Exception("The input document is not a attribute centric document\n");
default:
Console.WriteLine(reader.NodeType);
writer.WriteNode(reader, false);
break;
}
} while (reader.Read());
reader.Close();
writer.Flush();
writer.Close();
}
}
コードをコンパイルした後、コマンド ラインで「<コンパイル名> -a centric.xml <出力ファイル名>」と入力してコードを実行します。出力ファイルは存在している必要がありますが、空のテキスト ファイルでもかまいません。
次に示す出力では、C# プログラムが centric_cs にコンパイルされていると仮定しているため、コマンド ラインが「C:\centric_cs -a centric.xml centric_out.xml」になっています。
-a モードを指定すると、アプリケーションは入力 XML を属性中心に変換します。-e モードを指定した場合は、要素中心に変換します。次に示す出力は、-a モードを使用して生成された新しい属性中心の出力です。要素が入れ子になっている代わりに、要素に属性が含まれています。
出力 : centric_out.xml
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<root>
<Customer firstname="Jerry" lastname="Larson">
<Order OrderID="Ord-12345">
<OrderDetail Quantity="1301" UnitPrice="$3000" ProductName="Computer" />
</Order>
</Customer>
</root>