فصل ۱۱: XmlReader و XmlWriter؛ خواندن و نوشتن رو‌به‌جلوی XML

فصل ۱۱: XmlReader و XmlWriter؛ خواندن و نوشتن رو‌به‌جلوی XML

فصل ۱۱: XmlReader و XmlWriter؛ خواندن و نوشتن رو‌به‌جلوی XML

فصل ۱۱ — فناوری‌های دیگر XML و JSON

تصویر آغاز فصل ۱۱ — Other XML and JSON Technologiesتصویر تزئینی آغاز فصل ۱۱ در نسخهٔ اصلی.
تصویر آغاز فصل ۱۱ — Other XML and JSON Technologies

در فصل ۱۰، API مربوط به LINQ to XML و XML به‌طور کلی بررسی شد. در این فصل، کلاس‌های سطح‌پایین XmlReader/XmlWriter و نوع‌های کار با JavaScript Object Notation یا JSON را بررسی می‌کنیم؛ JSON به یک جایگزین محبوب برای XML تبدیل شده است.

در مکمل آنلاین کتاب، ابزارهای کار با XML schema و stylesheetها توضیح داده می‌شوند.

XmlReader

XmlReader کلاسی با کارایی بالا برای خواندن یک stream از XML به‌صورت سطح‌پایین و فقط رو به جلو (forward-only) است.

فایل customer.xml زیر را در نظر بگیرید:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<customer id="123" status="archived">
  <firstname>Jim</firstname>
  <lastname>Bo</lastname>
</customer>

برای ساخت XmlReader، متد static XmlReader.Create را با Stream، TextReader یا رشتهٔ URI صدا می‌زنید:

using XmlReader reader = XmlReader.Create ("customer.xml");
...

برای ساخت XmlReader که از string بخواند:

using XmlReader reader = XmlReader.Create (
  new System.IO.StringReader (myString));

همچنین می‌توانید XmlReaderSettings بدهید تا گزینه‌های parsing و validation کنترل شوند. سه property مهم برای ردکردن محتوای اضافی:

bool IgnoreComments                  // Skip comment nodes?
bool IgnoreProcessingInstructions    // Skip processing instructions?
bool IgnoreWhitespace                // Skip whitespace?

مثال، جلوگیری از انتشار whitespace nodeها:

XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using XmlReader reader = XmlReader.Create ("customer.xml", settings);
...

property مفید دیگر ConformanceLevel است. مقدار پیش‌فرض Document فرض می‌کند XML یک سند معتبر با یک root node است. اگر فقط fragment داخلی با چند node بخوانید، مثلاً:

<firstname>Jim</firstname>
<lastname>Bo</lastname>

باید ConformanceLevel را روی Fragment بگذارید تا exception رخ ندهد.

XmlReaderSettings.CloseInput تعیین می‌کند با بسته‌شدن reader، stream زیرین نیز بسته شود یا نه. property مشابه CloseOutput در XmlWriterSettings وجود دارد. پیش‌فرض هر دو false است.

خواندن Nodeها

واحدهای یک XML stream، XML nodeها هستند. reader stream را به ترتیب متنی و depth-first پیمایش می‌کند. property Depth عمق فعلی cursor را می‌دهد.

ابتدایی‌ترین روش خواندن، Read است. مانند MoveNext در IEnumerator به node بعدی می‌رود؛ اولین Read cursor را روی اولین node می‌گذارد. وقتی false برگرداند یعنی از آخرین node عبور کرده و reader باید بسته و کنار گذاشته شود.

دو property رشته‌ای Name و Value محتوای node را می‌دهند؛ بسته به نوع node یکی یا هر دو پر می‌شوند.

مثال زیر هر node را می‌خواند و نوع آن را چاپ می‌کند:

XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using XmlReader reader = XmlReader.Create ("customer.xml", settings);
while (reader.Read())
{
  Console.Write (new string (' ', reader.Depth * 2));
  Console.Write (reader.NodeType.ToString());
  if (reader.NodeType == XmlNodeType.Element ||
      reader.NodeType == XmlNodeType.EndElement)
  {
    Console.Write (" Name=" + reader.Name);
  }
  else if (reader.NodeType == XmlNodeType.Text)
  {
    Console.Write (" Value=" + reader.Value);
  }
  Console.WriteLine ();
}

خروجی:

XmlDeclaration
Element Name=customer
  Element Name=firstname
    Text Value=Jim
  EndElement Name=firstname
  Element Name=lastname
    Text Value=Bo
  EndElement Name=lastname
EndElement Name=customer

NodeType از enum XmlNodeType است که اعضای آن عبارت‌اند از: None, XmlDeclaration, Element, EndElement, Text, Attribute, Comment, Entity, EndEntity, EntityReference, ProcessingInstruction, CDATA, Document, DocumentType, DocumentFragment, Notation, Whitespace, SignificantWhitespace.

خواندن Elementها

اغلب ساختار سند XML را از قبل می‌دانید. XmlReader برای این حالت متدهایی دارد که حین خواندن ساختار خاصی را فرض می‌کنند؛ کد را ساده و هم‌زمان بخشی از validation را انجام می‌دهند.

ReadStartElement بررسی می‌کند NodeType فعلی Element باشد و سپس Read می‌کند. اگر name بدهید، تطابق نام را هم بررسی می‌کند. ReadEndElement بررسی می‌کند NodeType فعلی EndElement باشد و بعد Read می‌کند.

خواندن <firstname>Jim</firstname>:

reader.ReadStartElement ("firstname");
Console.WriteLine (reader.Value);
reader.Read();
reader.ReadEndElement();

ReadElementContentAsString همهٔ این مراحل را یک‌جا انجام می‌دهد: start element، text node و end element را می‌خواند و content را string برمی‌گرداند:

string firstName = reader.ReadElementContentAsString ("firstname", "");

آرگومان دوم namespace است که اینجا خالی است. نسخه‌های typed مانند ReadElementContentAsInt نیز وجود دارد و نتیجه را parse می‌کند.

اگر XML اصلی را با element creditlimit گسترش دهیم:

<customer id="123" status="archived">
  <firstname>Jim</firstname>
  <lastname>Bo</lastname>
  <creditlimit>500.00</creditlimit>    <!-- OK, we sneaked this in! -->
</customer>

می‌توانیم چنین بخوانیم:

XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using XmlReader r = XmlReader.Create ("customer.xml", settings);
r.MoveToContent();
r.ReadStartElement ("customer");
string firstName    = r.ReadElementContentAsString ("firstname", "");
string lastName     = r.ReadElementContentAsString ("lastname", "");
decimal creditLimit = r.ReadElementContentAsDecimal ("creditlimit", "");
r.MoveToContent();
r.ReadEndElement();

Elementهای اختیاری

اگر lastname اختیاری باشد:

r.ReadStartElement ("customer");
string firstName    = r.ReadElementContentAsString ("firstname", "");
string lastName     = r.Name == "lastname"
                      ? r.ReadElementContentAsString() : null;
decimal creditLimit = r.ReadElementContentAsDecimal ("creditlimit", "");

ترتیب تصادفی Elementها

مثال‌های این بخش به ترتیب ثابت elementها متکی‌اند. اگر elementها بتوانند در هر ترتیبی بیایند، آسان‌ترین راه این است که آن بخش XML را در X-DOM بخوانید. بخش «Patterns for Using XmlReader/XmlWriter» این موضوع را توضیح می‌دهد.

Elementهای خالی

رفتار XmlReader با empty element یک دام ناخوشایند دارد. این دو در XML معادل‌اند:

<customerList></customerList>
<customerList/>

اما XmlReader آن‌ها را متفاوت می‌بیند. در حالت اول:

reader.ReadStartElement ("customerList");
reader.ReadEndElement();

در حالت دوم ReadEndElement exception می‌دهد چون end element جداگانه‌ای وجود ندارد. workaround:

bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement ("customerList");
if (!isEmpty) reader.ReadEndElement();

این nuisance عمدتاً برای elementهایی است که ممکن است child element داشته باشند. برای elementهای text ساده، متدهایی مانند ReadElementContentAsString هر دو نوع empty element را درست مدیریت می‌کنند.

سایر متدهای ReadXXX

جدول ۱۱-۱ متدهای ReadXXX در XmlReader را خلاصه می‌کند. اکثر آن‌ها برای elementها طراحی شده‌اند.

جدول ۱۱-۱ — متدهای Read
عضوNodeTypeنمونهٔ XMLپارامتر ورودیدادهٔ خروجی
ReadContentAsXXXText<a>x</a>x
ReadElementContentAsXXXElement<a>x</a>x
ReadInnerXmlElement<a>x</a>x
ReadOuterXmlElement<a>x</a><a>x</a>
ReadStartElementElement<a>x</a>
ReadEndElementElement<a>x</a>
ReadSubtreeElement<a>x</a><a>x</a>
ReadToDescendantElement<a>x<b></b></a>"b"
ReadToFollowingElement<a>x<b></b></a>"b"
ReadToNextSiblingElement<a>x</a><b></b>"b"
ReadAttributeValueAttributeبخش «Reading Attributes»

ReadContentAsXXX یک text node را به type XXX parse می‌کند و درون خود از XmlConvert برای string-to-type conversion استفاده می‌کند. text node می‌تواند داخل element یا attribute باشد.

ReadElementContentAsXXX wrapper روی ReadContentAsXXX متناظر است و روی element node به‌جای text node درون آن عمل می‌کند.

ReadInnerXml معمولاً روی element اعمال می‌شود و XML داخلی/descendantها را برمی‌گرداند؛ اگر روی attribute باشد value آن را می‌دهد. ReadOuterXml مشابه است اما خود element در موقعیت cursor را نیز شامل می‌کند.

ReadSubtree یک proxy reader می‌دهد که فقط current element و descendantهایش را می‌بیند. باید پیش از ادامهٔ reader اصلی بسته شود؛ با بسته‌شدن proxy، cursor reader اصلی به انتهای subtree می‌رود.

ReadToDescendant به آغاز اولین descendant با name/namespace مشخص می‌رود. ReadToFollowing به اولین node مطابق، مستقل از عمق، می‌رود.

ReadToNextSibling cursor را به آغاز اولین sibling node با name/namespace مشخص می‌برد.

دو متد legacy نیز وجود دارند: ReadString و ReadElementString مانند ReadContentAsString و ReadElementContentAsString هستند، اما اگر داخل element بیش از یک text node باشد exception می‌دهند. بهتر است استفاده نشوند، چون وجود comment داخل element هم می‌تواند باعث exception شود.

Reading Attributes

XmlReader indexer برای دسترسی مستقیم و random به attributeهای یک element بر اساس نام یا موقعیت دارد؛ استفاده از indexer معادل GetAttribute است.

<customer id="123" status="archived"/>
Console.WriteLine (reader ["id"]);              // 123
Console.WriteLine (reader ["status"]);          // archived
Console.WriteLine (reader ["bogus"] == null);   // True

با اینکه ترتیب attribute از نظر معنا اهمیتی ندارد، با ordinal position هم قابل دسترسی است:

Console.WriteLine (reader [0]);            // 123
Console.WriteLine (reader [1]);            // archived

indexer می‌تواند namespace attribute را نیز بگیرد. AttributeCount تعداد attributeهای node جاری را می‌دهد.

Attribute nodeها

برای پیمایش صریح attribute nodeها باید از مسیر عادیِ Read موقتاً منحرف شوید؛ دلیل خوب، parse کردن value به typeهای دیگر با ReadContentAsXXX است. این diversion باید از start element شروع شود. در attribute traversal قاعدهٔ forward-only شل‌تر است و با MoveToAttribute می‌توانید به هر attribute جلو یا عقب بروید.

با fragment قبل:

reader.MoveToAttribute ("status");
string status = reader.ReadContentAsString();
reader.MoveToAttribute ("id");
int id = reader.ReadContentAsInt();

MoveToAttribute اگر attribute وجود نداشته باشد false می‌دهد. پیمایش ترتیبی attributeها:

if (reader.MoveToFirstAttribute())
  do { Console.WriteLine (reader.Name + "=" + reader.Value); }
  while (reader.MoveToNextAttribute());
// id=123
// status=archived

Namespaces و Prefixها

XmlReader دو سیستم موازی برای اشاره به نام element/attribute دارد:

  • Name؛
  • NamespaceURI و LocalName.

وقتی Name را می‌خوانید یا متدی با یک آرگومان name صدا می‌زنید، سیستم اول است. بدون namespace/prefix خوب کار می‌کند، اما در حضور آن‌ها literal است: namespace را نادیده می‌گیرد و prefix را دقیقاً همان‌طور که نوشته شده داخل Name نگه می‌دارد.

fragmentName
<customer ...>customer
<customer xmlns='blah' ...>customer
<x:customer ...>x:customer

پس این برای دو حالت اول:

reader.ReadStartElement ("customer");

و برای حالت سوم:

reader.ReadStartElement ("x:customer");

سیستم دوم با NamespaceURI و LocalName کار می‌کند. این‌ها prefixها و default namespaceهای parent را در نظر می‌گیرند و prefix را خودکار expand می‌کنند؛ بنابراین NamespaceURI همیشه namespace معنایی درست و LocalName همیشه بدون prefix است.

وقتی دو آرگومان name به متدی مانند ReadStartElement می‌دهید، همین سیستم namespace-aware به‌کار می‌رود. XML:

<customer xmlns="DefaultNamespace" xmlns:other="OtherNamespace">
  <address>
    <other:city>
    ...

خواندن:

reader.ReadStartElement ("customer", "DefaultNamespace");
reader.ReadStartElement ("address",  "DefaultNamespace");
reader.ReadStartElement ("city",     "OtherNamespace");

abstract کردن prefix معمولاً همان چیزی است که می‌خواهید. اگر لازم باشد prefix واقعی را با property Prefix ببینید و با LookupNamespace به namespace تبدیل کنید.

XmlWriter

XmlWriter نویسندهٔ forward-only برای XML stream است و طراحی آن متقارن با XmlReader است.

با Create و settings اختیاری ساخته می‌شود. مثال با indent:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using XmlWriter writer = XmlWriter.Create ("foo.xml", settings);
writer.WriteStartElement ("customer");
writer.WriteElementString ("firstname", "Jim");
writer.WriteElementString ("lastname", "Bo");
writer.WriteEndElement();

خروجی:

<?xml version="1.0" encoding="utf-8"?>
<customer>
  <firstname>Jim</firstname>
  <lastname>Bo</lastname>
</customer>

XmlWriter به‌طور پیش‌فرض declaration را می‌نویسد، مگر در XmlWriterSettings مقدار OmitXmlDeclaration=true یا ConformanceLevel=Fragment باشد. Fragment نوشتن چند root node را هم مجاز می‌کند؛ در غیر این صورت exception.

WriteValue یک text node می‌نویسد و string و نوع‌های nonstring مانند bool و DateTime را می‌پذیرد؛ درون خود از XmlConvert برای conversion سازگار XML استفاده می‌کند:

writer.WriteStartElement ("birthdate");
writer.WriteValue (DateTime.Now);
writer.WriteEndElement();

در مقابل:

WriteElementString ("birthdate", DateTime.Now.ToString());

می‌تواند هم ناسازگار با XML و هم مستعد parse اشتباه باشد. WriteString معادل WriteValue با string است. XmlWriter کاراکترهای غیرقانونی در attribute/element مانند &، <، > و extended Unicode را خودکار escape می‌کند.

نوشتن Attributeها

بلافاصله پس از start element می‌توان attribute نوشت:

writer.WriteStartElement ("customer");
writer.WriteAttributeString ("id", "1");
writer.WriteAttributeString ("status", "archived");

برای value غیرstring، WriteStartAttribute، WriteValue و سپس WriteEndAttribute را صدا بزنید.

نوشتن سایر Node Typeها

XmlWriter این متدها را نیز دارد:

WriteBase64       // binary data
WriteBinHex       // binary data
WriteCData
WriteComment
WriteDocType
WriteEntityRef
WriteProcessingInstruction
WriteRaw
WriteWhitespace

WriteRaw رشته را مستقیم در output stream تزریق می‌کند. WriteNode یک XmlReader می‌گیرد و هر چیزی را که از reader می‌آید echo می‌کند.

Namespaces و Prefixها در XmlWriter

overloadهای Write* اجازه می‌دهند element/attribute را به namespace متصل کنید. مثال منبع همهٔ elementها را به namespace نمونه متصل و prefix o را روی customer اعلام می‌کند:

writer.WriteStartElement ("o", "customer", "[namespace نمونهٔ کتاب]");
writer.WriteElementString ("o", "firstname", "[namespace نمونهٔ کتاب]", "Jim");
writer.WriteElementString ("o", "lastname", "[namespace نمونهٔ کتاب]", "Bo");
writer.WriteEndElement();

خروجی مفهومی:

<?xml version="1.0" encoding="utf-8"?>
<o:customer xmlns:o='[namespace نمونهٔ کتاب]'>
  <o:firstname>Jim</o:firstname>
  <o:lastname>Bo</o:lastname>
</o:customer>

برای اختصار، XmlWriter declaration namespace روی child element را وقتی parent قبلاً آن را تعریف کرده حذف می‌کند.

الگوهای استفاده از XmlReader/XmlWriter

کار با دادهٔ سلسله‌مراتبی

کلاس‌های زیر را در نظر بگیرید:

public class Contacts
{
  public IList<Customer> Customers = new List<Customer>();
  public IList<Supplier> Suppliers = new List<Supplier>();
}
public class Customer { public string FirstName, LastName; }
public class Supplier { public string Name; }

فرض کنید می‌خواهید با XmlReader/XmlWriter یک object از Contacts را به XML serialize کنید:

<?xml version="1.0" encoding="utf-8"?>
<contacts>
   <customer id="1">
      <firstname>Jay</firstname>
      <lastname>Dee</lastname>
   </customer>
   <customer>
      <firstname>Kay</firstname>
      <lastname>Gee</lastname>
   </customer>
   <supplier>
      <name>X Technologies Ltd</name>
   </supplier>
</contacts>

در مثال منبع فرض شده attribute id اختیاری است. ادامهٔ الگوهای serialization/deserialization در مقالهٔ بعدی دنبال می‌شود.

پایان محتوای تخصیص‌یافته از فایل PDF برای این مقاله.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620