用 XmlWriter 编写 XML

用 XmlWriter 编写 XML

XmlWriter 是定义用于编写 XML 的接口的抽象基类。XmlWriter 提供只进、只读、不缓存的 XML 流生成方法,这有助于生成符合 W3C 可扩展标记语言 (XML) 1.0(第二版)(www.w3.org/TR/2000/REC-xml-20001006.html) 建议和 XML 建议中的命名空间 (www.w3.org/TR/REC-xml-names/) 的 XML 文档。

下面的列表显示 XmlWriter 出于以下目的定义的方法和属性:

  • 指定是否支持命名空间。
  • 编写格式正确的 XML。
  • 将二进制字节编码为 base64 和 binhex,并写出结果文本。
  • WriteState 属性管理输出,包括用于确定输出进度的方法。
  • 将多个文档写入一个输出流。
  • 刷新或关闭输出。
  • 报告当前命名空间前缀、xml:langxml:space 范围。
  • 写出有效的名称、限定名和名称标记。

下面的列表标识了 XmlWriter 不检查的内容:

  • 无效的元素和属性名称字符。
  • 不符合指定编码的 Unicode 字符。如果 Unicode 字符不符合指定的编码,则 XmlWriter 不将 Unicode 字符转义为字符实体。
  • 重复的属性。
  • DOCTYPE 公共标识符或系统标识符中的字符。

因为 XmlWriter 不检查这四项,这就允许您创建格式不正确的 XML。如果您需要确保元素和属性名称只包含有效字符或具有上面所列的其他功能,请参见自定义 XML 编写器创建主题。

可能使用 XmlWriter 的情形有:读取旧式数据,如分析管道 (| ) 分隔的数据文件;以及用 XmlTextWriter 将数据写成 XML 格式。

输入

data1|data2|data3|data4|data5

并且希望将其转换为:

输出

<data>
      <item> data1 </item>
      <item> data2 </item>
      <item> data3 </item>
      <item> data4 </item>
      <item> data5 </item>
</data>

创建了一个方法将文件中的数据标记化为个别的数据标记。每个对该函数的调用均返回一个数据元素。该方法接受 StreamReader

[Visual Basic]
Public Shared Function NextToken(ByVal stream As StreamReader) As String
    Dim temp As Integer = stream.Read()
    Dim t1 As String = ""
    While temp <> -1 And ChrW(temp) <> "|"
        t1 += ChrW(temp)
        temp = stream.Read()
    End While
    Return t1
End Function 'NextToken
[C#]
public static String NextToken(StreamReader stream)
  {
      int temp = stream.Read();
      String t1 = "";
      while(temp != -1 && temp != (int)'|')
            {
                  t1 += (char)temp;
                  temp = stream.Read();
            }
      return t1;
  }

代码循环访问,直到没有剩余的数据标记;并将每个标记插入到 XML 文档中。这在 Main 函数中完成,该函数带两个命令行参数。第一个参数是要读取的文件,第二个是要写入的文件。代码中的注释提供关于所发生的操作的说明。

[Visual Basic]
Imports System
Imports System.IO
Imports System.Xml
Imports Microsoft.VisualBasic.Strings

Namespace TextFiletoXml
    Class TextFiletoXml
        Public Shared file As String
        Public Shared stream As StreamReader
        Public Shared xwriter As XmlTextWriter
        Public Shared val As String

        Public Shared Function NextToken(ByVal stream As StreamReader) As String
            Dim temp As Integer = stream.Read()
            Dim t1 As String = ""
            While temp <> -1 And ChrW(temp) <> "|"
                t1 += ChrW(temp)
                temp = stream.Read()
            End While
            Return t1
        End Function 'NextToken

    Public Overloads Shared Sub Main()
        dim args() As String=System.Environment.GetCommandLineArgs()
        file = args(1)

        'Create a new stream representing the file we are reading from.
        stream = New StreamReader(file, True)

        'Create a new XmlTextWriter.
        xwriter = New XmlTextWriter(args(2), System.Text.Encoding.UTF8)

        'Write the beginning of the document including the 
        'document declaration. Standalone is true. 
        xwriter.WriteStartDocument(True)

        'Write the beginning of the "data" element. This is 
        'the opening tag to our data. 
        xwriter.WriteStartElement("data", "www.alpineskihouse.com")

        'Get the first data element from the file.
        val = NextToken(stream)

        'Create a new element with each data token from the stream.
        While val <> ""
            xwriter.WriteElementString("item", "www.alpineskihouse.com", val)
            val = NextToken(stream)
        End While     
        xwriter.WriteEndElement()  'End the "data" element.
        xwriter.WriteEndDocument() 'End the document

        'Flush the XML document to the underlying stream and
        'close the underlying stream. The data will not be written out 
        'to the stream until either the Flush() method is called or 
        'the Close() method is called.
        xwriter.Close()
    End Sub 'Main
    End Class 'TextFiletoXml
End Namespace 'TextFiletoXml
[C#]
using System;
using System.IO;
using System.Xml;

namespace TextFiletoXml
{

    class TextFiletoXml
    {
        public static String file;
        public static StreamReader stream;
        public static XmlTextWriter xwriter;
        public static String val;
        public static String NextToken(StreamReader stream)
        {
            int temp = stream.Read();
            String t1 = "";
            while(temp != -1 && temp != (int)'|')
            {
                t1 += (char)temp;
                temp = stream.Read();
            }

            return t1;

        }

        public static void Main(string[] args)
        {
            file = args[0];

            //Create a new stream representing the file we are 
            //reading from.
            stream = new StreamReader(file, true);

            //Create a new XmlTextWriter.
           xwriter = new XmlTextWriter(args[1],System.Text.Encoding.UTF8);
            //Write the beginning of the document including the 
            //document declaration. Standalone is true. 

            xwriter.WriteStartDocument(true);

            //Write the beginning of the "data" element. This is 
            //the opening tag to our data 

            xwriter.WriteStartElement("data","www.alpineskihouse.com");

            // Get the first data element from the file.
            val = NextToken(stream);

            //create a new element with each data token from //the stream.
            while(val != "")
            {

               xwriter.WriteElementString("item","www.alpineskihouse.com", val);
                val = NextToken(stream);
            }
            //End the "data" element.
            xwriter.WriteEndElement();

            //End the document
            xwriter.WriteEndDocument();

            //Flush the xml document to the underlying stream and
            //close the underlying stream. The data will not be
            //written out to the stream until either the Flush()
            //method is called or the Close() method is called.
            xwriter.Close();
        }
    }
}

XmlWriter 的方法及其实现 XmlTextWriter 根据 W3C 规则,在字符串用作参数时将空内容写出到输出。这意味着,对于下列方法,如果提供 nullString.Empty 值,这些方法将写出没有数据内容的文本,并且不引发异常。相反,它们写出 ""。如果向 text 参数传递值 nullString.Empty,则该实现应用于 WriteProcessingInstruction 方法。

  • WriteCData
  • WriteComment
  • WriteProcessingInstruction
  • WriteString

如果传递 nullString.Empty,其他方法可能引发异常。

XmlWriter 具有一个实现 XmlTextWriter。有关使用 XmlTextWriter 的更多信息,请参见用 XmlTextWriter 创建格式正确的 XML。有关 XmlWriter 方法和属性的更多信息,请参见 XmlWriter 成员

用 XmlTextWriter 创建格式正确的 XML

XmlWriter 派生的 XmlTextWriter 将 XML 写到文件、控制台、流和其他输出类型。在编写 XML 时,方法将进行额外的工作以产生格式正确的 XML。下表提供了方法列表,这些方法为您执行确保数据的格式正确的工作。

方法 所做工作的说明
WriteAttributeString XmlTextWriter 根据它找到的内容来转义属性的文本内容。
WriteString XmlTextWriter 转义特殊字符,在需要时用 &amp; &lt; &gt; 和数字字符实体替换它们。
WriteBase64 XmlTextWriter 对 base64 字节进行编码,这样就可以在 XmlReader 上用 ReadBinary 读取这些字节。
Close Close 检查 XML 是否为无效的 XML 文档,如果是无效的 XML 文档,则引发 InvalidOperationException

下面的附加任务由 XmlTextWriter 完成以确保格式正确的 XML:

  • 确保 XML 元素以正确的顺序写出。例如,它不允许在元素外部写出属性,在属性内部写出 CDATA 块,或者写出多个根元素。另外,它还确保 <?xml 声明首先出现,并确保 <!DOCTYPE 节点在根元素之前出现。
  • 确保 xml:space 属性的值和格式是正确的,并确保其值根据可扩展标记语言 (XML) 1.0(第二版)建议 (www.w3.org/XML/Group/2000/07/REC-xml-2e-review#sec-white-space) 是可接受的。下面的示例显示 WriteAttributeString 方法中 xml:space 的有效值的使用:
    w.WriteAttributeString("xml:space", "", "preserve");

    有效值 xml:space 是“default”和“preserve”。如果参数不是这些值之一,将发生 ArgumentException

  • 检查何时将字符串用作参数(如 Null==String.EmptyString.Empty),以及它是否遵循 W3C 规则。

下表显示了由 XmlTextWriter 定义的、不是在 XmlWriter 中继承或定义的或是从 Object 继承的其他方法和属性。

方法或属性 说明
XmlTextWriter 构造函数 创建 XmlTextWriter 的一个实例,该实例接受文件名、流或 TextWriter。存在一个带附加参数的重载方法,该参数定义编码类型。
Namespaces 属性 指定是否支持命名空间。当它设置为 false 时,不写出 xmlns 声明,可以指定包含任意多个冒号的元素名称。
Formatting 属性 指定是否将缩进用于格式化输出。
IndentChar 属性 定义在进行缩进的 Formatting 时用于缩进的字符。
Indentation 属性 定义在进行缩进的 Formatting 时,为层次结构中的每一层写出多少 IndentChars
QuoteChar 属性 定义用于将属性值用引号括起来的字符。这必须是单引号 &#39; 或双引号 &#34;
BaseStream 属性 返回 XmlTextWriter 正在写入的流。如果 XmlTextWriter 是用不是从 StreamWriter 派生的 TextWriter 构造的,则返回空。

下面的示例使用 XmlTextWriter 创建 XML 输出。

[Visual Basic]
Shared Sub WriteQuote(writer As XmlWriter, symbol As String, price As Double, change As Double, volume As Long)
   writer.WriteStartElement("Stock")
   writer.WriteAttributeString("Symbol", symbol)
   writer.WriteElementString("Price", XmlConvert.ToString(price))
   writer.WriteElementString("Change", XmlConvert.ToString(change))
   writer.WriteElementString("Volume", XmlConvert.ToString(volume))
   writer.WriteEndElement()
End Sub 'WriteQuote

Public Shared Sub Main()
   Dim writer As New XmlTextWriter(Console.Out)
   writer.Formatting = Formatting.Indented
   WriteQuote(writer, "MSFT", 74.125, 5.89, 69020000)
   writer.Close()
End Sub 'Main
[C#]
static void WriteQuote(XmlWriter writer, string symbol, 
                double price, double change, long volume)
{
   writer.WriteStartElement("Stock");
   writer.WriteAttributeString("Symbol", symbol);
   writer.WriteElementString("Price", XmlConvert.ToString(price));
   writer.WriteElementString("Change", XmlConvert.ToString(change));
   writer.WriteElementString("Volume", XmlConvert.ToString(volume));
   writer.WriteEndElement();
}

public static void Main(){
    XmlTextWriter writer = new XmlTextWriter(Console.Out);
    writer.Formatting = Formatting.Indented;
    WriteQuote(writer, "MSFT", 74.125, 5.89, 69020000);
    writer.Close();
}

输出

<Stock Symbol="MSFT">
      <Price>74.125</Price>
      <Change>5.89</Change>
      <Volume>69020000</Volume>
</Stock>

WriteQuote 方法的输入是堆栈符号,它是以 string 的形式出现的。价格和更改被声明为 double,值为 long。为将这些变量转换为字符串,使用了 XmlConvert 类。它具有将所有强数据类型转换为字符串的方法。另外,XmlConvert 类还具有通过将字符串转换为 .NET Framework 数据类型进行相反转换的方法。有关更多信息,请参见 XML 名称的字符编码和 XML 数据类型的转换

有关说明如何将 XML 写入文件的示例代码,请参见 XmlTextWriter.WriteProcessingInstruction。有关说明如何将 XML 写到控制台的示例代码,请参见 XmlTextWriter.WriteString

下面的代码显示如何写出产生 <price>19.95</price> 的元素:

[Visual Basic]
'Write the price.
writer.WriteElementString("price", "19.95")
[C#]
//Write the price.
writer.WriteElementString("price", "19.95");

下面的代码显示如何写出产生 <element name="purchaseOrder"/> 的属性:

[Visual Basic]
writer.WriteStartElement("element")
writer.WriteAttributeString("name", "purchaseOrder")
writer.WriteEndElement()
[C#]
writer.WriteStartElement("element"); 
writer.WriteAttributeString("name", "purchaseOrder"); 
writer.WriteEndElement();

WriteAttributeString 方法写出属性和命名空间声明

WriteAttributeString 方法有两个不同的任务。一个任务是写出属性并将其与用户定义的命名空间前缀关联。第二个任务是生成命名空间声明。如果写属性和 localname 参数为 xmlns,则该方法被视为是创建命名空间声明。

在下面的代码示例中,WriteAttributeString 方法用于在元素内写出属性。

[Visual Basic]
'Write the genre attribute.
writer.WriteAttributeString("genre", "novel")
'Write the ISBN attribute.
writer.WriteAttributeString("ISBN", "1-8630-014")
[C#]
//Write the genre attribute.
writer.WriteAttributeString("genre", "novel");
//Write the ISBN attribute.
writer.WriteAttributeString("ISBN", "1-8630-014");

WriteAttributeString 还根据它找到的内容转义属性的文本内容。如果使用双引号,则 XmlTextWriter 在属性值的文本内容中用 &quot; 转义它们。如果使用单引号,则它用 &apos; 转义属性值的文本内容。

为了生成命名空间声明,存在一个重载的 WriteAttributeString 方法,它允许应用程序定义命名空间声明。下面的代码示例创建两个默认命名空间。第一个声明将所有没有前缀的元素绑定到第一个命名空间声明,而任何用“po”前缀声明的元素则被绑定到第二个命名空间声明。

[Visual Basic]
' Write the default namespace, identified as xmlns with no prefix
writer.WriteAttributeString("xmlns", Nothing, "http://www.w3.org/2000/10/XMLSchema")
' Write a namespace for the purchase order with a prefix of "po"
writer.WriteAttributeString("xmlns", "po", Nothing, "http://contoso.com/po")
[C#]
// Write the default namespace, identified as xmlns with no prefix
writer.WriteAttributeString("xmlns", null, "http://www.w3.org/2000/10/XMLSchema");
// Write a namespace for the purchase order with a prefix of "po"
writer.WriteAttributeString("xmlns", "po", null, "http://contoso.com/po");

Close 方法

Close 方法在流关闭时检查 XML 文档是否有效。这防止了创建无效的 XML 文档,并确保 XML 的格式是正确的。除了关闭流,Close 方法还调用关闭文档所需的所有 WriteEnd<xxx> 方法。

方法对

XmlWriter 中的方法也是成对出现的:WriteStartDocumentWriteEndDocumentWriteStartElementWriteEndElementWriteStartAttributeWriteEndAttribute 方法对。例如,使用这些方法可以创建嵌套的元素或属性。用以生成 XML 文档并允许创建复杂元素或属性的就是这些方法对。

WriteStartDocument 与 WriteEndDocument 方法

WriteStartDocument 开始新的文档,并写出 XML 声明并将版本属性设置为“1.0”,WriteEndDocument 关闭该文档。在调用下一个 WriteStartDocument 以开始写出下一个文档之间,可以修改格式设置、缩进和其他属性。WriteStartDocument 方法以编程方式识别出正在写出 XML 文档并应用根级别规则。如果不使用该方法,则将创建 XML 片段并验证它的格式是否正确。不应用根级别规则。下面的代码示例显示文档的开头和结尾。

[Visual Basic]
' Write the XML declaration.
writer.WriteStartDocument()
. . .
' Close the document.
writer.WriteEndDocument()

[C#]
// Write the XML declaration. 
writer.WriteStartDocument();
. . .
// Close the document.
writer.WriteEndDocument();

WriteStartElement 与 WriteEndElement 方法

WriteStartElementWriteEndElement 对分隔一个或多个元素。在所有被重写的 WriteStartElement 方法中,开始标记的本地名称是必选参数。下面的代码使用 WriteStartElementWriteEndElement 方法对。

[Visual Basic]
' Write the title.
writer.WriteStartElement("title")
writer.WriteString("The Handmaid's Tale")
writer.WriteEndElement()

[C#]
// Write the title.
writer.WriteStartElement("title");
writer.WriteString("The Handmaid's Tale");
writer.WriteEndElement();

输出

<title>The Handmaid's Tale</title>

WriteStartElement 提供一个重写的方法签名,该签名使代码能够为其元素指定命名空间前缀。有关更多信息,请参见 XmlTextWriter 中的元素命名空间前缀

WriteStartAttribute 与 WriteEndAttribute 方法

WriteStartAttributeWriteEndAttribute 类似于其他开始和结束方法,但这些方法开始和结束属性。WriteStartAttribute 写出属性的开始,WriteString 方法用于写出属性值,WriteEndAttribute 结束属性标记。下面的代码示例显示 WriteStartAttributeWriteEndAttribute 方法对。

[Visual Basic]
writer.WriteStartAttribute(prefix, "ISBN", "urn:samples")
writer.WriteString("1-861003-78")
writer.WriteEndAttribute()

[C#]
writer.WriteStartAttribute(prefix, "ISBN", "urn:samples");
writer.WriteString("1-861003-78");
writer.WriteEndAttribute();

输出

<book bk:ISBN="1-861003-78">

WriteStartAttribute 具有一个重载方法,该方法使应用程序能够指定命名空间前缀,以便可以将命名空间前缀与它写出的属性关联。有关更多信息,请参见 XmlTextWriter 中的属性命名空间前缀

XmlTextWriter 的 XML 输出格式设置

XmlTextWriter 中的 XML 输出格式设置由若干共同工作以控制文档输出的属性组成。输出格式设置属性为:

  • Formatting
  • IndentChar
  • Indentation
  • QuoteChar

Formatting 属性具有有效值 NoneIndented,其中 None 为默认值。当 None 为选定值时,忽略 IndentCharIndentation 属性,并且不发生格式设置。如果 Formatting 属性被设置为 Indented,则应用程序查看 Indentation 属性以了解为层次结构中的每一层写出多少 IndentChars,然后 IndentChars 指定用于缩进的字符。如果 Formatting 属性被设置为 Indented,则 Indentation 的默认值是为层次结构中的每一层都写出 2 个 IndentChars,并且 IndentChars 的默认值是空格。如果 Formatting 被设置为 Indented,则子元素根据 IndentationIndentChar 值缩进。由 XmlTextWriter 执行的缩进取决于节点类型。受 Indentation 属性影响的节点为:

  • DocumentType
  • Element
  • Comment
  • ProcessingInstruction
  • CDATASection

所有其他的节点类型都不受 Indentation 属性的影响,对它们不执行缩进。

DTD 的内部子集没有任何缩进或格式设置。不过这是可以实现的,如代码示例所示。代码示例为 DTD 内部子集执行格式设置。

[C#]
String name = "Employees";
String pubid = null;
String sysid = null;
String subset = @"
    <!ELEMENT Employees (Employee)+>
    <!ELEMENT Employee EMPTY>
    <!ATTLIST Employee firstname CDATA #REQUIRED>
    <!ENTITY Company 'Microsoft'>]>
";
XmlTextWriter tw = new XmlTextWriter(Console.Out);
tw.WriteDocType(name, pubid, sysid, subset);

QuoteChar 属性确定用于用引号将属性值括起来的字符。有效值为:

  • 单引号 (&#39;)
  • 双引号 (&#34;)

QuoteChar 的默认值是双引号 (&#34;)。

下面的示例写出 XML 片段,并将 Formatting 属性设置为 Indented,缩进级别为 4,缩进字符为空格(默认值)。

[Visual Basic]
Option Explicit
Option Strict

Imports System
Imports System.IO
Imports System.Xml

Public Class Sample
    
    Public Shared Sub Main()
        'Create a writer to write XML to the console.
        Dim writer As XmlTextWriter = Nothing
        writer = New XmlTextWriter(Console.Out)
        
        'Use indentation for readability.
        writer.Formatting = Formatting.Indented
        writer.Indentation = 4
        
        'Write an element (this one is the root).
        writer.WriteStartElement("book")
        
        'Write the title element.
        writer.WriteStartElement("title")
        writer.WriteString("Pride And Prejudice")
        writer.WriteEndElement()
        
        'Write the close tag for the root element.
        writer.WriteEndElement()
        
        'Write the XML to file and close the writer.
        writer.Close()
    End Sub 'Main 
End Class 'Sample
[C#]
using System;
using System.IO;
using System.Xml;

public class Sample
{
  
  public static void Main()
  {
     //Create a writer to write XML to the console.
     XmlTextWriter writer = null;
     writer = new XmlTextWriter (Console.Out);

     //Use indentation for readability.
     writer.Formatting = Formatting.Indented;
     writer.Indentation = 4;
        
     //Write an element (this one is the root).
     writer.WriteStartElement("book");

     //Write the title element.
     writer.WriteStartElement("title");
     writer.WriteString("Pride And Prejudice");
     writer.WriteEndElement();

     //Write the close tag for the root element.
     writer.WriteEndElement();
             
     //Write the XML to file and close the writer.
     writer.Close();  
  }
}

XmlTextWriter 的命名空间功能

命名空间是用于限定 XML 文档中的元素和属性名称的技术。命名空间前缀将元素和属性与命名空间关联,命名空间反过来又与 URI 引用关联。命名空间在 XML 文档中创建元素和属性名称唯一性。

下面的列表显示 XmlTextWriter 具有允许应用程序以不同的方式声明命名空间的方法。这包括以下功能:

  • 手动声明命名空间。
  • 用新的命名空间重写当前命名空间声明。
  • 声明多个命名空间。

除声明命名空间外,还可以在元素和属性中追加命名空间前缀。若要为属性写出命名空间前缀,使用下列方法:

  • WriteAttributes
  • WriteAttributeString
  • WriteStartAttribute

有关为属性写出前缀的更多信息,请参见 XmlTextWriter 中的属性命名空间前缀

若要为元素写出命名空间前缀,使用下列方法:

  • WriteElement
  • WriteElementString
  • WriteStartElement

有关为属性写出前缀的更多信息,请参见 XmlTextWriter 中的元素命名空间前缀

编写器维护一个跟踪已由元素定义的命名空间的命名空间堆栈。下面的代码示例显示在写出元素时该命名空间堆栈的使用。

[Visual Basic]
Dim w As New XmlTextWriter(Console.Out)
w.WriteStartElement("root", "urn:1")
w.WriteStartElement("item", "urn:2")
w.WriteEndElement()
w.WriteEndElement()
w.Close()
[C#]
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.WriteStartElement("root","urn:1");
w.WriteStartElement("item","urn:2");
w.WriteEndElement();
w.WriteEndElement(); 
w.Close();

输出

<root xmlns="urn:1">
      <item xmlns="urn:2"/>
</root>

下面的示例显示在嵌套元素上使用重复的命名空间声明时所发生的事情。注意,输出中的空 item 元素不重复命名空间声明。

[Visual Basic]
Dim w As New XmlTextWriter(Console.Out)
w.WriteStartElement("root", "urn:1")
w.WriteStartElement("item", "urn:1")
w.WriteEndElement()
w.WriteEndElement()
w.Close()
[C#]
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.WriteStartElement("root","urn:1");
w.WriteStartElement("item","urn:1");
w.WriteEndElement();
w.WriteEndElement(); 
w.Close();

输出

<root xmlns="urn:1"><item/></root>

XmlTextWriter 的手动命名空间声明

可以手动写出命名空间声明,以优化命名空间声明的数目。下面的代码示例显示如何优化命名空间声明的数目。

[Visual Basic]
w.WriteStartElement("root")
w.WriteAttributeString("xmlns", "x", Nothing, "urn:1")
w.WriteStartElement("item", "urn:1")
w.WriteEndElement()
w.WriteStartElement("item", "urn:1")
w.WriteEndElement()
w.WriteEndElement()
[C#]
w.WriteStartElement("root");
w.WriteAttributeString("xmlns", "x", null, "urn:1");
w.WriteStartElement("item","urn:1");
w.WriteEndElement();
w.WriteStartElement("item","urn:1");
w.WriteEndElement();
w.WriteEndElement();

输出

<root xmlns:x="urn:1">
    <x:item/>
    <x:item/>
</x:root>

上述代码示例将命名空间声明提升为了 root 元素,以避免在两个子元素上有重复项。item 元素还从命名空间声明中选取前缀。

XmlTextWriter 的命名空间声明重写

下面的代码示例显示如何手动重写与给定前缀关联的命名空间。注意,order 值重写 123 的原始命名空间 URI。这使命名空间得以在新的元素范围中重新定义。

[Visual Basic]
w.WriteStartElement("x", "node", "123")
w.WriteAttributeString("xmlns", "x", Nothing, "order")
[C#]
w.WriteStartElement("x","node","123");
w.WriteAttributeString("xmlns","x",null,"order");

输出

<x:node xmlns:x="order"/>

XmlTextWriter 的多个命名空间声明

当有多个命名空间声明将不同的前缀映射到相同的统一资源名称 (URN) 时,XmlWriter 类通过返回到命名空间声明堆栈来选取最近的一个命名空间。下面的代码示例显示 WriteAttributeString 如何不指定任何前缀,从而使 XmlWriter 先找到 y 前缀。

[Visual Basic]
Dim w As New XmlTextWriter(Console.Out)
w.WriteStartElement("x", "root", "urn:1")
w.WriteStartElement("y", "item", "urn:1")
w.WriteAttributeString("abc", "urn:1", "xyz")
w.WriteEndElement()
w.WriteEndElement()
w.Close()
[C#]
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.WriteStartElement("x","root","urn:1");
w.WriteStartElement("y","item","urn:1");
w.WriteAttributeString("abc","urn:1","xyz");
w.WriteEndElement();
w.WriteEndElement();
w.Close();

输出

<x:root xmlns:x="urn:1">
    <y:item y:abc="xyz" xmlns:y="urn:1"/>
</x:root>

XmlTextWriter 中的属性命名空间前缀

有多种方法可以处理 XmlTextWriter 的属性 WriteAttributesWriteAttributeString WriteStartAttribute 的命名空间前缀。

WriteAttributes

如果当前位置是元素节点,则 WriteAttributes 方法写出在 XmlReader 中的当前位置找到的所有属性。如果 XmlReader 声明了命名空间前缀,则除属性之外 WriteAttributes 方法还写出该命名空间前缀。下面的代码示例显示 WriteAttributes 方法如何写出属性的命名空间前缀。

[Visual Basic]
Dim r As XmlTextReader = CreateTextReaderStr("<ROOT xmlns:p='n' p:a='abc'/>")
r.Read()
r.MoveToAttribute("p:a")

Dim tw As New XmlTextWriter(Console.Out)
tw.WriteStartElement("ROOT")
tw.WriteAttributes(r, False)
tw.WriteEndElement()
[C#]
XmlTextReader r = CreateTextReaderStr("<ROOT xmlns:p='n' p:a='abc'/>");
r.Read();
r.MoveToAttribute("p:a");

XmlTextWriter tw = new XmlTextWriter(Console.Out);
tw.WriteStartElement("ROOT");
tw.WriteAttributes(r, false);
tw.WriteEndElement();

输出

<ROOT p:a="abc" xmlns:p="n" />

WriteAttributeString

处理命名空间前缀的另一种方法是,让应用程序使用将命名空间作为前缀的一个 WriteAttributeString 方法。一个 WriteAttributeString 方法接受属性名称、属性值和命名空间作为参数,并用给定的值写出属性,然后将其与命名空间关联。

另一个 WriteAttributeString 方法使用用户定义的命名空间前缀,并在写出属性时将其与该前缀关联。有关显示如何使用 WriteAttributeString 方法和用户定义的命名空间前缀的示例代码,请参见 XmlWriter.WriteAttributeString Method (String, String, String)

WriteStartAttribute

WriteStartAttribute 生成属性的开始部分,另外还根据所使用的方法将命名空间前缀或者命名空间 URI 用作参数。

下面的代码示例将第一个输入参数作为采用命名空间前缀 bk 的字符串,该前缀是通过调用 LookupPrefix 方法找到的。用 WriteStartAttribute 设置了前缀、本地名称和命名空间之后,WriteString 方法向属性提供一个值。

[Visual Basic]
' Write an element (this one is the root).
writer.WriteStartElement("bookstore")
' Write the namespace declaration.
writer.WriteAttributeString("xmlns", "bk", Nothing, "urn:samples")
writer.WriteStartElement("book")
' Lookup the prefix and then write the ISBN attribute.
Dim prefix As String = writer.LookupPrefix("urn:samples")
writer.WriteStartAttribute(prefix, "ISBN", "urn:samples")
writer.WriteString("1-861003-78")
writer.WriteEndAttribute()
' Write the style element.
writer.WriteStartElement(prefix, "style", "urn:samples")
writer.WriteString("hardcover")
writer.WriteEndElement()
' Write the end tag for the book element.
writer.WriteEndElement()
'Write the close tag for the root element.
writer.WriteEndElement()
                
[C#]
// Write an element (this one is the root).
writer.WriteStartElement("bookstore");   
// Write the namespace declaration.
writer.WriteAttributeString("xmlns", "bk", null, "urn:samples");
writer.WriteStartElement("book");
// Lookup the prefix and then write the ISBN attribute.
string prefix = writer.LookupPrefix("urn:samples");
writer.WriteStartAttribute(prefix, "ISBN", "urn:samples");
writer.WriteString("1-861003-78");
writer.WriteEndAttribute();
// Write the style element.
writer.WriteStartElement(prefix, "style", "urn:samples");
writer.WriteString("hardcover");
writer.WriteEndElement();
// Write the end tag for the book element.
writer.WriteEndElement();
// Write the close tag for the root element.
writer.WriteEndElement();

输出

<bookstore xmlns:bk="urn:samples">
  <book bk:ISBN="1-861003-78">
      <bk:style>hardcover</bk:style>
  </book>
</bookstore>

如果属性具有关联的命名空间 URI,则根据 XML 建议中 W3C 命名空间中的命名空间默认设置,属性还必须具有前缀。下面的代码示例显示在用 WriteAttributeString 方法写出属性时必须包括此前缀。

[Visual Basic]
Dim w As New XmlTextWriter(Console.Out)
w.WriteStartElement("root")
w.WriteAttributeString("order", "urn:1", "123")
w.WriteEndElement()
w.Close()
[C#]
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.WriteStartElement("root");
w.WriteAttributeString("order","urn:1", "123");
w.WriteEndElement();
w.Close();

输出

<root n1:order="123" xmlns:n1="urn:1"/>

此输出即使在 root 元素与默认命名空间 urn:1 关联时也发生。前缀被命名为 n{i},其中 i 从 1 开始。每个元素的索引同样从 1 开始。因此,如果嵌套的子元素也需要生成的前缀,则它使用 n1

下面的代码示例显示:当用不同的命名空间写出多个属性时,属性在命名空间声明之前写出。

[Visual Basic]
Dim w As New XmlTextWriter(Console.Out)
w.WriteStartElement("root")
w.WriteAttributeString("order", "urn:1", "123")
w.WriteAttributeString("book", "urn:2", "The Great Escape")
w.WriteEndElement()
w.Close()
[C#]
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.WriteStartElement("root");
w.WriteAttributeString("order","urn:1", "123");
w.WriteAttributeString("book","urn:2", "The Great Escape");
w.WriteEndElement();
w.Close();

输出

<root n1:order="123" n2:book="The Great Escape" xmlns:n1="urn:1" xmlns:n2="urn:2"/

XmlTextWriter 中的元素命名空间前缀

可以将命名空间前缀作为 WriteStartElement 的参数进行传递以预置到元素中。这样,该方法在写出命名空间前缀时会将其预置到元素中。下面的代码示例显示命名空间前缀在 WriteStartElement 方法调用中的使用。

[Visual Basic]
Dim w As New XmlTextWriter(Console.Out)
w.WriteStartElement("x", "root", "urn:1")
w.WriteStartElement("y", "item", "urn:1")
w.WriteEndElement()
w.WriteEndElement()
w.Close()
[C#]
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.WriteStartElement("x","root","urn:1");
w.WriteStartElement("y","item","urn:1");
w.WriteEndElement();
w.WriteEndElement();
w.Close();

输出

<x:root xmlns:x="urn:1"><y:item xmlns:y="urn:1"/></x:root>

前缀 xy 都已被保留。

注意   指定前缀和空命名空间 URI 是一个错误,它与 XML 规范中 W3C 命名空间第 2 节 (www.w3.org/TR/1999/REC-xml-names-19990114/ns-decl) 冲突。如果提供空命名空间 URI,则发生异常。

 


用 XmlTextWriter 解析重复的命名空间前缀

XmlWriter 通过生成替换前缀来解决冲突。下面的代码示例显示属性与元素具有相同的前缀,但命名空间不同。

[Visual Basic]
w.WriteStartElement("x", "root", "urn:1")
w.WriteAttributeString("x", "SomeAttr", "urn:2", "123")
w.WriteEndElement()
[C#]
w.WriteStartElement("x","root","urn:1");
w.WriteAttributeString("x","SomeAttr","urn:2", "123");
w.WriteEndElement();

以如下方式为 "SomeAttr" 属性提供了一个新前缀:

<x:root n1:SomeAttr="123" xmlns:n1="urn:2" xmlns:x="urn:1"/>

上述冲突只能发生在属性中。对于嵌套的元素,新的命名空间声明可以重新定义重复的前缀映射到什么。下面的代码示例显示嵌套的元素,即第二个命名空间声明重新定义重复的前缀映射到什么。

[Visual Basic]
w.WriteStartElement("x", "root", "urn:1")
w.WriteStartElement("x", "item", "urn:2")
w.WriteEndElement()
w.WriteEndElement()
[C#]
w.WriteStartElement("x","root","urn:1");
w.WriteStartElement("x","item","urn:2");
w.WriteEndElement();
w.WriteEndElement();

输出

<x:root xmlns:x="urn:1">
    <x:item xmlns:x="urn:2"/>
</x:root>

写出 XML 内容时的特殊字符转换

XmlWriter 包括方法 WriteRaw,它使您得以手动写出原始标记。该方法禁止转义特殊字符。这与 WriteString 方法相反,后者将某些字符串转义为它们的等效实体引用。被转义的字符可从以下位置找到:XML 1.0 建议的第 2.4 节“Character Data and Markup”(字符数据与标记),以及可扩展标记语言 (XML) 1.0(第二版)建议的第 3.3.3 节“Attribute-Value Normalization”(属性值的正常化)。如果 WriteString 方法在写出属性值时被调用,则它转义 &apos;&quot;。字符值 0x-0x1F 通过 &#x1f; 编码为数字字符实体 &#0;,空白字符 0x9、0x10 和 0x13 除外。

因此,何时使用 WriteStringWritingRaw 的指导原则是:在需要遍历每个字符以查找实体字符时使用 WriteString,而 WriteRaw 原样写出给它提供的内容。

下面的示例显示当给定“<”字符时 WriteStringWriteRaw 方法之间的差异。此代码示例使用 WriteString

[Visual Basic]
w.WriteStartElement("myRoot")
w.WriteString("<")
w.WriteEndElement()
Dim tw As New XmlTextWriter(Console.Out)
tw.WriteDocType(name, pubid, sysid, subset)
[C#]
w.WriteStartElement("myRoot");
w.WriteString("<");
w.WriteEndElement();
XmlTextWriter tw = new XmlTextWriter(Console.Out);
tw.WriteDocType(name, pubid, sysid, subset);

输出

<myRoot>&lt;</myRoot>

此代码示例使用 WriteRaw,输出将非法字符作为元素内容。

[Visual Basic]
w.WriteStartElement("myRoot")
w.WriteRaw("<")
w.WriteEndElement()
[C#]
w.WriteStartElement("myRoot");
w.WriteRaw("<");
w.WriteEndElement();

输出

<myRoot><</myRoot>

下面的示例显示如何将 XML 文档从以元素为中心的文档转换为以属性为中心的文档。还可以将 XML 从以属性为中心的文档转换回以元素为中心的文档。以元素为中心的模式意味着 XML 文档被设计为具有许多元素但具有很少属性。以属性为中心的设计具有较少的元素,那些在以元素为中心的设计中应该是元素的内容变成了元素的属性。因此元素较少,而每个元素的属性较多。

如果已经以任何一种模式设计了 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>

下面的示例应用程序执行转换。

[Visual Basic]
' 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
[C#]
// 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();
    }
}

代码编译后,在命令行键入 <compiled name> -a centric.xml <output file name> 来运行它。输出文件必须存在,而且可以是空文本文件。

对于下面的输出,假定 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>

XML 文档中的代理项对字符

代理项或代理项对是一对共同表示单个字符的 16 位 Unicode 编码值。需要记住的关键一点是:代理项对实际上是 32 位单个字符,不能再假定一个 16 位 Unicode 编码值正好映射到一个字符。

代理项对的第一个值是高代理项,包含介于 U+D800 到 U+DBFF 范围内的 16 位代码值。该对的第二个值是低代理项,包含介于 U+DC00 到 U+DFFF 范围内的值。通过使用代理项对,16 位 Unicode 编码系统可以对已由 Unicode 标准定义的另外一百多万个字符 (220) 进行编址。

在传递给 XmlTextWriter 方法的任何字符串中都可以使用代理项字符。不过,代理项字符在编写的 XML 中应该有效。例如,W3C 建议不允许在元素或属性名称中使用代理项字符。如果字符串包含无效的代理项对,则引发异常。

另外,可以使用 WriteSurrogateCharEntity 写出与代理项对相对应的字符实体。字符实体以十六进制格式写出,并用以下公式生成:

(highChar -0xD800) * 0x400 + (lowChar -0xDC00) + 0x10000

如果字符串包含无效的代理项对,则引发异常。下面的示例显示将代理项对作为输入的 WriteSurrogateCharEntity 方法。

[C#]
// The following line writes &#x10000
WriteSurrogateCharEntity ('\uDC00', '\uD800');

下面的示例生成一个代理项对文件,将其加载到 XmlReader 中,并用新的文件名保存文件。然后,原始文件和新文件被加载回应用程序的 DOM 结构中以进行比较。

[C#]
char lowChar, highChar;
char [] charArray = new char[10];
FileStream targetFile = new FileStream("SurrogatePair.xml",
      FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);

lowChar = Convert.ToChar(0xDC00);
highChar = Convert.ToChar(0xD800);
XmlTextWriter tw = new XmlTextWriter(targetFile, null);
tw.Formatting = Formatting.Indented;
tw.WriteStartElement("root");
tw.WriteStartAttribute("test", null);
tw.WriteSurrogateCharEntity(lowChar, highChar);
lowChar = Convert.ToChar(0xDC01);
highChar = Convert.ToChar(0xD801);
tw.WriteSurrogateCharEntity(lowChar, highChar);
lowChar = Convert.ToChar(0xDFFF);
highChar = Convert.ToChar(0xDBFF);
tw.WriteSurrogateCharEntity(lowChar, highChar);

// Add 10 random surrogate pair.
// As unicode, the high bytes are in lower
// memory, for example, word 6A21 as 21 6A.
// The high or low is in the logical sense.
Random random = new Random();
for (int i = 0; i < 10; ++i) {
      lowChar = Convert.ToChar(random.Next(0xDC00, 0xE000));
      highChar = Convert.ToChar(random.Next(0xD800, 0xDC00));
      charArray[i] = highChar;
      charArray[++i] = lowChar;
}
tw.WriteChars(charArray, 0, charArray.Length);

for (int i = 0; i < 10; ++i) {
      lowChar = Convert.ToChar(random.Next(0xDC00, 0xE000));
      highChar = Convert.ToChar(random.Next(0xD800, 0xDC00));
      tw.WriteSurrogateCharEntity(lowChar, highChar);
}

tw.WriteEndAttribute();
tw.WriteEndElement();
tw.Flush();
tw.Close();

XmlTextReader r = new XmlTextReader("SurrogatePair.xml");

r.Read();
r.MoveToFirstAttribute();
targetFile = new FileStream("SurrogatePairFromReader.xml",
       FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);

tw = new XmlTextWriter(targetFile, null);
tw.Formatting = Formatting.Indented;
tw.WriteStartElement("root");
tw.WriteStartAttribute("test", null);
tw.WriteString(r.Value);
tw.WriteEndAttribute();
tw.WriteEndElement();
tw.Flush();
tw.Close();

// Load both result files into DOM and compare.
XmlDocument doc1 = new XmlDocument();
XmlDocument doc2 = new XmlDocument();
doc1.Load("SurrogatePair.xml");
doc2.Load("SurrogatePairFromReader.xml");
if (doc1.InnerXml != doc2.InnerXml) {
      Console.WriteLine("Surrogate Pair test case failed");
}

在使用 WriteChars 方法(它一次写出一个缓冲区的数据)写出时,存在输入中的代理项对在一个缓冲区内被意外拆分的可能性。由于代理项值是定义完善的,如果 WriteChars 遇到来自较低范围或者较高范围的 Unicode 值,它将该值标识为代理项对的一半。当遇到 WriteChars 将导致从拆分代理项对的缓冲区写入的情况时,将发生异常。若要将下一个代理项对字符继续写入输出缓冲区,则必须捕获该异常。下面的示例显示使用随机产生的代理项对字符的 WriteChars 方法的使用,该代理项对字符在写入输出缓冲区时被拆分。

[C#]
using System;
using System.Xml;

class SurrogateSplit {

    static void Main() {

        char [] charArray = new char[4];
        char lowChar, highChar;
        Random random = new Random();

        lowChar = Convert.ToChar(random.Next(0xDC00, 0xE000));
        highChar = Convert.ToChar(random.Next(0xD800, 0xDC00));

        XmlTextWriter tw = new XmlTextWriter("test.xml", null);
        tw.WriteStartElement("Root");
        charArray[0] = 'a';
        charArray[1] = 'b';
        charArray[2] = 'c';
        charArray[3] = highChar;

        try{
            tw. WriteChars(charArray, 0, charArray.Length);
        } catch (Exception ex) {

            if (ex.Message.IndexOf("second character") > 0) {

                charArray[0] = highChar;
                charArray[1] = lowChar;
                charArray[2] = 'd';
                tw.WriteChars(charArray, 0, 3);
            }
        }
        tw.WriteEndElement();
        tw.Close();
    }
}

捕获异常并继续写入缓冲区可确保代理项对字符正确写入输出流。

自定义 XML 编写器创建

XmlTextWriter 不执行下列任务:

  • XmlTextWriter 不验证元素或属性名称是否有效。
  • XmlTextWriter 写出介于 0x0 到 0x20 范围内的 Unicode 字符,和不是 XML 字符的 0xFFFE 与 0xFFFF 字符。
  • XmlTextWriter 不检测重复的属性。它写入重复的属性,不引发异常。

如果这三项对您的需要很关键,则可以编写扩展当前 XmlTextWriter 的自定义编写器并添加此功能。

下面的示例显示可以如何修改 XML 编写器,改写 WriteStringWriteStartElement 方法。此代码示例不保证编写的 XML 有效。它只检查写出的字符串是否不具有超出界限的字符,以及元素名称是否有效。它不检查属性名称。

  1. 第一步是根据 W3C 规范定义一个验证字符串中的字符是否为有效字符的方法。该方法用于验证编写器写出的方法是否正确。对字符串进行的特定测试是:
    • 验证没有字符具有大于 0xFFFD 或者小于 0x20 的十六进制值。
    • 检查字符不等于制表符 (\t)、换行符 (\n)、回车符 (\r) 或 0x20 范围以下的无效 XML 字符。如果出现任何这些字符,将发生异常。

    下面的代码示例显示该方法。

    [C#]
    internal void CheckUnicodeString(String value) 
        {
        for (int i=0; i < value.Length; ++i) {
            if (value[i] > 0xFFFD) 
            {
                throw new Exception("Invalid Unicode");
            } 
            else if (value[i] < 0x20 && value[i] != '\t' & value[i] != '\n' & value[i] != '\r')
            {
                throw new Exception("Invalid Xml Characters");
            }
        }
  2. 下一步是使用在第 1 步中定义的方法改写 XmlTextWriter 中的 WriteString 方法。如果字符串中的字符超出了可接受的字符范围界限,则改写的 WriteString 方法将引发异常。
    [C#]
            public override void WriteString(String value) 
             {
                CheckUnicodeString(value);
                base.WriteString(value);
             }

为验证 WriteStartElement 方法写出的名称是否有效,使用了 XmlConvert 类的 EncodeLocalName 方法。EncodeLocalName 方法通过将任何无效字符转换为有效的表示形式,确保名称中的字符有效。例如,Order Details 将被转换为 Order_x0020_Details。有关 EncodeLocalName 的更多信息,请参见 XmlConvert.EncodeLocalName 方法

public override void WriteStartElement(string prefix, string localName, string ns) 
{
   base.WriteStartElement(prefix,
        XmlConvert.EncodeLocalName(localName),ns);
}

若不使用 EncodeLocalName 方法,XmlConvert 类的 VerifyName 方法也验证元素或属性名称是否有效。如果名称有效,VerifyName 方法将名称作为返回参数返回;如果名称无效,则引发异常。这当希望了解由于无效的名称而发生的错误时很有用。下面的示例显示如何使用 VerifyName 方法创建 WriteStartElement 方法以检查名称是否有效。

public override void WriteStartElement(string prefix, string localName, 
                                       string ns) 
{
   base.WriteStartElement(prefix,
        XmlConvert.VerifyName(localName),ns);
}
注意   其他 WriteStartElement 方法全都不需要改写,因为它们全都调用上面的方法。

符合标准的编写器完整代码示例

下面的代码示例是类定义的完整列表。为了确保总是写出有效的字符,需要重写其他方法,如 WriteDocTypeWriteAttributeStringWriteElementStringWriteCharEntity 方法。重写这些方法类似于为重写 WriteStartElement 方法所显示的代码。

[Visual Basic]
Imports System
Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Collections
Imports Microsoft.VisualBasic

'------------------------------------------------------------------
' Class derived from XmlTextWriter classs.
'
' This example only overrides the WriteString and WriteStartElement
' methods.
'------------------------------------------------------------------
namespace MyWriter.ConformWriter 

    public class ConformWriter : Inherits XmlTextWriter

        private sub CheckUnicodeString(ByVal value as String) 
            Dim i as Integer
            Dim iVal as Integer
            for i=0 to value.Length-1
                iVal = Convert.ToInt16(value.Chars(i))
                if (iVal > &HFFFD) 
                    throw new Exception("Invalid Unicode")
                else 
                    if iVal < &H20 And (iVal <> 9 And iVal <> 13 And iVal <> 10) Then
                        throw new Exception("Invalid Xml Characters")
                    end if
                end if
            next
        end sub

        public sub New(ByVal fileName as String, ByVal encoding as Encoding)
            MyBase.New(fileName, encoding) 
        end sub

        Overrides public sub WriteString(ByVal value as String) 
            CheckUnicodeString(value)
            MyBase.WriteString(value)
        end sub

        Overrides Overloads public sub WriteStartElement(ByVal prefix As String, ByVal localname As String, ByVal ns As String) 
            MyBase.WriteStartElement(prefix, XmlConvert.EncodeLocalName(localName), ns)
        end sub
        
    end class
end namespace
[C#]
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Collections;

//------------------------------------------------------------------
// Class derived from XmlTextWriter classs.
//
// This sample only overrides the WriteString and WriteStartElement
// methods.
//------------------------------------------------------------------
namespace MyWriter.ConformWriter {

    public class ConformWriter : XmlTextWriter
    {
       internal void CheckUnicodeString(String value) {
           for (int i=0; i < value.Length; ++i) {
                if (value[i] > 0xFFFD) {
                    throw new Exception("Invalid Unicode");
                        } else if (value[i] < 0x20 && value[i] != '\t' & value[i] != '\n' & value[i] != '\r') {
                    throw new Exception("Invalid Xml Characters");
                }
            }
        }

        public ConformWriter(String fileName, Encoding encoding):base(fileName, encoding) {}

        public override void WriteString(String value) {
            CheckUnicodeString(value);
            base.WriteString(value);
        }

        public override void WriteStartElement(string prefix, string localName, string ns) {
            base.WriteStartElement(prefix, XmlConvert.EncodeLocalName(localName), ns);
        }
    }
}
posted @ 2006-11-21 14:28  疯一样的自由  阅读(3202)  评论(0编辑  收藏  举报