顯示具有 XML 標籤的文章。 顯示所有文章
顯示具有 XML 標籤的文章。 顯示所有文章

2010年4月27日 星期二

Linq to Xml: 寫出 XDeclaration 的資料

當以 linq to xml 的方式組 xml 是非常輕易的事情。但在下面的程式碼,想要輸出 xml declaration 卻碰到了軟釘子

錯誤寫法

   1: static void Main(string[] args)
   2: {
   3:   string xml = "<r><a>3</a></r>";
   4:   XDocument doc = new XDocument(
   5:     new XDeclaration("1.0", "big5", null),
   6:     XElement.Parse(xml));
   7:   Console.WriteLine(doc.ToString());
   8: }

輸出如下所述,並沒有包含預期的<?xml version="1.0" encoding="big5"?>

   1: <r>
   2:   <a>3</a>
   3: </r>

輸出到文字檔

為什麼呢?但如果改成下面的程式輸出到文字檔,在檔案內容就是正確的。

   1: static void Main(string[] args)
   2: {
   3:   string xml = "<r><a>3</a></r>";
   4:   XDocument doc = new XDocument(
   5:     new XDeclaration("1.0", "big5", null),
   6:     XElement.Parse(xml));
   7:   doc.Save(@"c:\temp\a.xml");
   8: }

StringWriter

我們也可以改用 StringWriter 寫出。如下例

   1: static void Main(string[] args)
   2: {
   3:   string xml = "<r><a>3</a></r>";
   4:   XDocument doc = new XDocument(
   5:     new XDeclaration("1.0", "big5", null),
   6:     XElement.Parse(xml));
   7:   var writer = new StringWriter();
   8:   doc.Save(writer);
   9:   Console.WriteLine(writer.ToString());
  10: }

輸出如下

   1: <?xml version="1.0" encoding="utf-16"?>
   2: <r>
   3:   <a>3</a>
   4: </r>

使用 StringWriter 的特性更怪,它會視XDocument 的內容來覆寫實際的 encoding。由於c# 的 string 都是 unicode,故一律輸出成 utf-16 的編碼。

最後,找到一個較能符合需求的方式。如下

兩段式ToString

   1: static void Main(string[] args)
   2: {
   3:   string xml = "<r><a>3</a></r>";
   4:   XDocument doc = new XDocument(
   5:     new XDeclaration("1.0", "big5", null),
   6:     XElement.Parse(xml));
   7:   Console.WriteLine(doc.Declaration.ToString() +  doc.ToString());
   8: }

使用兩段式的 ToString()方式是蠻奇怪的。但這是我試到最簡單的方法了

2008年9月18日 星期四

XDocument 與 XElement 的一個不同處

使用 XElement 或 XDocument 來載入 xml 時,到底有什麼不同的地方呢?

範例

 

      string xml = @"el 1el 2el 3";
      XElement el = XElement.Parse(xml);
      var q = from e in el.Elements()
              select e;
      Console.WriteLine("Load in XElement");
      foreach (var item in q)
      {
        Console.WriteLine(item);
      }
      XDocument doc = XDocument.Parse(xml);
      q = from e in doc.Elements()
          select e;
      Console.WriteLine("Load in XDocument");
      foreach (var item in q)
      {
        Console.WriteLine(item);
      }
結果如下
Load in XElement
el 1
el 2
el 3
Load in XDocument

  
  el 1
  el 2
  el 3
由此可看出,使用 XElement 的 Elements() 時,不會讀取 xml comment,會將該 r 當作 element 的current node
而使用 XDocument 的 Elements()時,會將 xml comment 也當成 node。會將整份文件當成 current document,所以使用 Elements()方法時,就會讀到 r 這一個子節點了。

Linq to xml 的強制轉換好處

範例

以下範例,說明將 date 的attribute 取出後,轉成 datetime 的過程。注意 invoice 這個 element 可能沒有date這個attribute
      string xml = @"";
      XElement el = XElement.Parse(xml);
      string strDate = el.Element("invoice").Attribute("date").Value;
      DateTime? date = null;
      if (!string.IsNullOrEmpty(strDate))
        date = DateTime.Parse(strDate);
      Console.WriteLine(date);
這樣一來,程式碼就顯的有些長而不易維護。 因此,在linq to xml 的設計時,有考慮到這一點。

使用強制轉換的範例

      string xml = @"";
      XElement el = XElement.Parse(xml);
      
      DateTime? date = (DateTime?) el.Element("invoice").Attribute("date");
      
      Console.WriteLine(date);
注意到可直接使用 DateTime? date = (DateTime?) … 來取得可能為 null 的 attribute。
這樣的方法可適用於 element

2008年9月1日 星期一

Linq to XML: Write NameSpace

Linq to XML 的寫作非常方便。以往要組一個 xml ,都必須從 XmlDocument 開始,現在使用 Linq to XML 就不用了。

範例1:

例如,下列程式碼

      
	//code 1
	XElement el = new XElement("r",
        new XElement("invoice",
          new XElement("invoiceNumber", "AB12345678"),
          new XElement("invoiceDate", DateTime.Now)
          )
        );
產生的 xml 結果如下
<r>
  <invoice>
    <invoiceNumber>AB12345678</invoiceNumber>
    <invoiceDate>2008-09-01T10:08:22.9666505+08:00</invoiceDate>
  </invoice>
</r>

這樣的方式極為簡便。

範例2:


如果要產生 namespace 呢? 以下的程式碼會產生 default namespace

      //code 2
      XNamespace ns = "http://www.compnay.com.tw/Invoice";
      XElement el = new XElement(ns + "r",
        new XElement(ns + "invoice",
          new XElement(ns + "invoiceNumber", "AB12345678"),
          new XElement(ns + "invoiceDate", DateTime.Now)
          ));
產生如下的 xml
<r xmlns="http://www.compnay.com.tw/Invoice">
  <invoice>
    <invoiceNumber>AB12345678</invoiceNumber>
    <invoiceDate>2008-09-01T10:16:48.2077244+08:00</invoiceDate>
  </invoice>
</r>

範例3:

也可以使用如下的寫法,但效能會差一些,原因 runtime 要計算用了哪些 namespace。
      //code 3
      XNamespace ns = "http://www.compnay.com.tw/Invoice";
      XElement el = new XElement("{http://www.compnay.com.tw/Invoice}r",
        new XElement("{http://www.compnay.com.tw/Invoice}invoice",
          new XElement("{http://www.compnay.com.tw/Invoice}invoiceNumber", "AB12345678"),
          new XElement("{http://www.compnay.com.tw/Invoice}invoiceDate", DateTime.Now)
          )
        );
結果是一樣的
<r xmlns="http://www.compnay.com.tw/Invoice">
  <invoice>
    <invoiceNumber>AB12345678</invoiceNumber>
    <invoiceDate>2008-09-01T10:20:56.8615067+08:00</invoiceDate>
  </invoice>
</r>

這樣的寫法雖然較容易了解,但較不容易修改,我個人不建議這樣的寫法。

範例4:


另外,注意到雖然我們沒有說要寫成 default namespace,但Linq to XML 會「自動」地視狀況修改。
我們可以使用 XAttribute 來使用 namesapce prefix。如下程式碼。

      //code 4 
      XNamespace ns = "http://www.compnay.com.tw/Invoice";
      XElement el = new XElement("r",
        new XAttribute(XNamespace.Xmlns + "i", "http://www.compnay.com.tw/Invoice"),
        new XElement("invoice",
          new XElement("invoiceNumber", "AB12345678"),
          new XElement("invoiceDate", DateTime.Now)
          )
        );
結果如下。注意到這樣的方法,可以很方便地產生了一個 namespace prefix i,但卻沒有任何節點使用到這個 prefix
<r xmlns:i="http://www.compnay.com.tw/Invoice">
  <invoice>
    <invoiceNumber>AB12345678</invoiceNumber>
    <invoiceDate>2008-09-01T10:27:11.773833+08:00</invoiceDate>
  </invoice>
</r>

範例5:

使用下面的程式碼,就不難了解 xml namespace 的特殊

      //code 5
      XNamespace ns = "http://www.compnay.com.tw/Invoice";
      XElement el = new XElement("r",
        new XAttribute(XNamespace.Xmlns + "i", "http://www.compnay.com.tw/Invoice"),
        new XElement("{i}invoice",
          new XElement("invoiceNumber", "AB12345678"),
          new XElement("invoiceDate", DateTime.Now)
          )
        );
產生的xml 如下。xml:i="http:....." 這個 namespace 宣告了,卻沒有任何節點使用。i 是一個 namespace 的值,是inovice節點的 default namespace。invoiceNumber 與 invoiceDate的 namespace 卻是 ""。一個簡單的xml,卻有三個 namespace,基本上是錯誤的設計。
<r xmlns:i="http://www.compnay.com.tw/Invoice">
  <invoice xmlns="i">
    <invoiceNumber xmlns="">AB12345678</invoiceNumber>
    <invoiceDate xmlns="">2008-09-01T11:02:47.1886207+08:00</invoiceDate>
  </invoice>
</r>

範例6:

如果要像 code 2,產生相同的結果,但不要每一個節點使用 ns + "xxx"這樣的方式可不可以呢?如下的程式碼會發生錯誤

    //code 6, fails
      XNamespace ns = "http://www.compnay.com.tw/Invoice";
      XElement el = new XElement("r",
        new XAttribute("xmlns", "http://www.compnay.com.tw/Invoice"),
        new XElement("invoice",
          new XElement("invoiceNumber", "AB12345678"),
          new XElement("invoiceDate", DateTime.Now)
          )
          );

錯誤的訊息為 Unhandled Exception: System.Xml.XmlException: The prefix '' cannot be redefined from '' to 'http://www.compnay.com.tw/Invoice' within the same start element tag. 看來,沒有一個好的方法,將其他的節點批次地設為 default namespace了。必須每一個節點都如 code 2 一般,將每一個節點,一個個設 namespace。也沒有辦法設哪一個 namespace 為 default namespace。

範例7:


最後的範例,是 prefix namespace, 請與範例2 對照

//code 7 
      XNamespace ns = "http://www.compnay.com.tw/Invoice";
      XElement el = new XElement(ns + "r",
        new XAttribute(XNamespace.Xmlns + "i", ns),
        new XElement(ns + "invoice",
          new XElement(ns + "invoiceNumber", "AB12345678"),
          new XElement(ns + "invoiceDate", DateTime.Now)
          )
          );
產生的 xml 結果如下
<i:r xmlns:i="http://www.compnay.com.tw/Invoice">
  <i:invoice>
    <i:invoiceNumber>AB12345678</i:invoiceNumber>
    <i:invoiceDate>2008-09-02T08:52:11.4240786+08:00</i:invoiceDate>
  </i:invoice>
</i:r>

Share with Facebook