السلام عليكم ورحمة الله وبركاته
في الآونة الأخيرة كلنا سمعنا عن الـ XML وأنها قامت بكسر الحواجز بين التطبيقات المختلفة وأنها جعلت تناقل البيانات ومشاركتها عملية بسيطة وليست كابوسا كما كانت. أيضا قامت شركة Microsoft بتبني وإعتماد هذه التكنولوجيا حيث أن ملفات الـ Office 12/Office 2007 هي حرفيا عبارة عن XML حيث أن هذا الإهتمام أو الـ Standardize أدى إلى ما يعرف بي Office Open XML file format. لن أطيل الحديث عن الـ Office Open XML حيث أنه خارج نطاق موضوعنا.
تمهيد :
لقرائة ملف XML بلغة CSharp يمكنك إستخدام المكتبة System.Xml حيث أنها تحوي XmlReader و XmlTextReader حيث أن كل منهما يقومان بقراءة ملف XML وبشكل تقدمي (Forward-Only). المقصود بشكل تقدمي هنا أي أن القراءة بإتجاه واحد مثلها مثل الـ SqlDataReader أو OleDbDataReader بمعنى أنه عندما تطلب القراءة بإستخدام الدالة Read سيتقدم المؤشر إلى العنصر التالي. دعونا نرى كيف تحدث هذه العملية:
لنفرض أن لدينا ملف الـ XML التالي :
<?xml version="1.0" encoding="utf-8"?>
<MultiStockOrder>
<StockOrderMultiple>
<StockOrder>
<Symbol>MSFT</Symbol>
<Quantity>100</Quantity>
<OrderTime>2005-10-04</OrderTime>
</StockOrder>
<StockOrder>
<Symbol>INTC</Symbol>
<Quantity>110</Quantity>
<OrderTime>2005-10-04</OrderTime>
</StockOrder>
<StockOrder>
<Symbol>RTRSY</Symbol>
<Quantity>200</Quantity>
<OrderTime>2005-10-04</OrderTime>
</StockOrder>
</StockOrderMultiple>
</MultiStockOrder>وهدفنا هنا هو طباعة قيمة محتوى العنصر Symbol ، سيكون الكود كالتالي
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
namespace XmlProject
{
class Program
{
static void Main(string[] args)
{
try
{
// The following are self explanatory settings
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
settings.CheckCharacters = true;
FileStream myStockOrders = new
FileStream("your xml file path", FileMode.Open);
XmlReader xr = XmlReader.Create(myStockOrders, settings);
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element &&
"Symbol" == xr.LocalName)
{
Console.WriteLine(xr.Name + " " +
xr.ReadElementContentAsString());
}
}
xr.Close();
Console.WriteLine("Done");
Console.ReadLine();
}
catch (System.Exception ex)
{
Console.Error.WriteLine(ex.ToString());
Console.ReadLine();
}
}
}
}السؤال الذي يطرح نفسه هو : ما الفائدة من قرائتي لملف XML بطريقة تقدمية فقط ؟
أفرض أنه طلب منك أن تقوم بكتابة كود يتعامل مع ملف XML بطريقة معينه حيث أن القراءة التقدمية تكون عقيمة، بمعنى آخر كيف يتم التنقل بأريحية (Navigation) وسط ملف الـ XML. هنا سنتطرق لمصطلح قد يكون على البعض جديد وهو الـ XPath. لتبسيط الـ XPath تخيل أنها عبارة عن ملفات نظام windows في الـ command-prompt ولكن بدل أن تستخدم \ قم بإستخدام /، بمعنى آخر تخيل أنك في الـ Shell-command في نظام تشغيل Linux. لتبسيط العملية أكثر، إذا أردنا الحصول على نفس النتيجة في المثال السابق نقوم بكتابة التعبير (expression) التالي:
MultiStockOrder/StockOrderMultiple/StockOrder/Symbol
أيضا يمكنك إستخدام كلا من "." و ".."، حيث أن الأولى تشير للعنصر الحالي والثانية تشير إلى العنصر الأب على التوالي، تماما كنظام الملفات في نظام تشغيل Linux. وفي حالة أن العنصر كان يحوي attribute وطلب منك إستخراج قيمته كل ما عليك فعله هو الوصول إلى ذلك العنصر ثم إستخدام @ كما هو موضح في المثال التالي:
elemtn1/element2/...../required-element/@attribute-name
على نفس المثال السابق دعونا نرى كيفية إستخدام XPath في لغة CSharp، الكود :
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.IO;
namespace XmlProject
{
class Program
{
static void Main(string[] args)
{
try
{
FileStream myStockOrders = new
FileStream("your xml file path", FileMode.Open);
XPathDocument myDocument = new XPathDocument(myStockOrders);
XPathNavigator docNavigation = myDocument.CreateNavigator();
foreach(XPathNavigator node in
docNavigation.Select("//MultiStockOrder/StockOrderMultiple/StockOrder/Symbol"))
{
Console.WriteLine(node.Value.ToString());
}
Console.WriteLine("Done");
Console.ReadLine();
}
catch (System.Exception ex)
{
Console.Error.WriteLine(ex.ToString());
Console.ReadLine();
}
}
}
}إمكانيات الـ XPath لا تقتصر فقط على إستخراج البيانات من ملف XML بل تحتوي أيضا على أدوات منطقية وما إلى ذلك وللمزيد من المعلومات أتبع الرابط التالي
http://msdn.microsoft.com/en-us/library/ms256086.aspx
ASP.NET & XML
الموضوع أبسط مما يبدو عليه بعكس االأمثلة السابقة، بل يمكنك قراءة وعرض ملفات XML من غير أن تقوم بكتابة سطر واحد في الـ Code Behind. حتى تتضح الفكرة أكثر سنقوم بالتدرج بدءا بعرض ملف XML بإستخدام XmlDataSource و GridView (ملاحظة: نفس الفكرة تنطبق أيضا على كل من DataList, ListView, DetailsView .... etc) وإنتهائا بإستخدام ملف XSLT و asp:Xml control . في جميع الأمثلة التالية سنقوم بإستخدام ملف الـ XML التالي والذي سيكون تحت اسم games.xml، الملف :
<?xml version="1.0" encoding="utf-8" ?>
<games>
<game platform="pc">
<name>Dragon Age: Origions</name>
<genre>Computer Role-Playing</genre>
<releasedate>11/3/2009</releasedate>
</game>
<game platform="pc">
<name>Mass Effect 2</name>
<genre>Action Role-Playing</genre>
<releasedate>1/26/2010</releasedate>
</game>
<game platform="pc">
<name>Left 4 Dead 2</name>
<genre>Sci-Fi First-Person Shooter</genre>
<releasedate>11/17/2009</releasedate>
</game>
<game platform="xbox">
<name>Aliens vs. Predator</name>
<genre>Action</genre>
<releasedate>2/16/2010</releasedate>
</game>
<game platform="xbox">
<name>Dynasty Warriors: Strikeforce</name>
<genre>Action</genre>
<releasedate>2/16/2010</releasedate>
</game>
<game platform="xbox">
<name>Tropico 3</name>
<genre>Other Strategy Games</genre>
<releasedate>2/16/2010</releasedate>
</game>
<game platform="ps3">
<name>Braid</name>
<genre>2D Platform</genre>
<releasedate>11/12/2009</releasedate>
</game>
<game platform="ps3">
<name>Uncharted 2: Among Thieves</name>
<genre>Modern Action Adventure</genre>
<releasedate>10/13/2009</releasedate>
</game>
<game platform="ps3">
<name>Assassin's Creed II</name>
<genre>Historic Action Adventure</genre>
<releasedate>11/17/2009</releasedate>
</game>
</games>أولا : XmlDataSource
مثلة مثل الـ SqlDataSource ولكن بدل من أن تقوم بتحديد الـ ConnectionString و الـ SelectCommand قم بتحديد كل من DataFile و XPath حيث أن الأول هو عبارة عن عنوان URL ملف الـ XML المراد عرضه والثاني هو عبارة عن تعبر expression كما سبق ذكره. سيكون الكود في الـ design كالتالي :
<asp:GridView ID="gamesGridView" runat="server" AutoGenerateColumns="false" DataSourceID="gamesDataSource"
Width="700px" CellPadding="5">
<HeaderStyle BackColor="#a3a3f0" />
<Columns>
<asp:TemplateField HeaderText="Title">
<ItemTemplate>
<%# XPath("name") %>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Platform">
<ItemTemplate>
<%# XPath("@platform") %>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Genre">
<ItemTemplate>
<%# XPath("genre") %>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Release Date">
<ItemTemplate>
<%# XPath("releasedate") %>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:XmlDataSource ID="gamesDataSource" runat="server" DataFile="~/games.xml" XPath="/games/game" />ثانيا : XSLT & asp:Xml Control
الـ XSLT هي إختصار لـ eXtensible Stylesheet Language Transformations وهي عبارة عن لغة لنقل ملف XML من شكلة الحالي إلى شكل آخر، قد يكون HTML أو Text أو ملف XML ولكن بشكل آخر غير الملف الأصلي. في بداية الأمر عندما تقوم بالتوغل في الـ XSLT ستلاحظ أنها غريبة جدا ولكن صدقوني عندما تتعودوا عليها وتشاهدوا مثالا أو أكثر ستلاحظ أنها أقرب إلى SQL Transit. ببساطة الـ XSLT يتم من خلالها إنشاء template أو أكثر ووفقا لـ XPath expression يتم تطبيق الـ template على مجموعة العناصر الناتجة من الـ XPath expression. قبل كل شيء دعونا نرى ملف XSLT في أبسط صورة :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<!--
YOUR TEMPLATE GOES HERE
-->
</xsl:template>
</xsl:stylesheet>لاحظ العنصر xsl:template وهو المسؤول عن إنشاء قالب بالشكل الذي تريده وأيضا لاحظ أنه يحوي على attribute بإسم match حيث أنه يأخذ قيمة XPath expression وفي حالتنا هنا القيمة هي / وهي تدل على محتوى ملف الـ XML كاملا. دعونا نقوم بالتعديل على ملف الـ XSLT السابق وتغييرة إلى :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<h1>Hellow World!</h1>
</xsl:template>
</xsl:stylesheet>التعديل الأخير سيقوم بتوليد الـ HTML التالي <h1>Hellow World!</h1> لمرة واحدة وذلك لأن أي ملف XML يحوي على عنصر جذر واحد فقط (Root element). ولكي تقوم بعمل loop للعناصر الموجودة تحت العنصر الجذر يمكنك إستخدام العنصر
<xsl:for-each select ="an XPath expression"></xsl:for-each>
حيث أن الـ attribute الذي يحمل الإسم select هو عبارة عن XPath expression، فلنفرض على ضوء ملف الـ XML والذي يحمل الإسم games.xml أننا قمنا بتعديل ملف الـ XSLT إلى التالي :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<xsl:for-each select="games/game">
<h1>Hellow World!</h1>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>ماذا تتوقع أن تكون النتيجة النهائية ؟ ستكون كتابة الجملة Hello Wordl! تسعة مرات، وذلك بسبب الـ XPath expression الموجود في العنصر xsl:for-each حيث أن عدد العناصر المرجعة من هذا التعبير هو 9 وفقا للملف games.xml. ولإستخلاص قيمة عنصر من ملف XML عن طريق ملف XSLT قم بإستخدام العنصر xsl:value-of والتي تحتوي على الـ attribute الذي يحمل اسم select وهو أيضا عبارة عن XPath expression.
دعونا نرى كيف نعرض محتوى ملف XML عن طريق asp:Xml و ملف XSLT وصدقوني أنه بعد ما يتم إنشاء ملف الـ XSLT كل ما يلزمنا هو سطر واحد فقط. سيكون ملف الـ XSLT بإسم games_xslt.xslt وهو كالتالي :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<xsl:for-each select ="games/game">
<p style="padding: 5px;">
<h4 style="color: #a3a3f0">
<xsl:value-of select="name"/>
</h4>
<font style="font-size: 7pt; font-family: Tahoma; font-style:italic; font-wieght: bold;">
[Genre: <xsl:value-of select="genre"/>] [Release Date: <xsl:value-of select="releasedate" />]
</font>
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>وفي الـ Design في صفحة الـ ASP.NET سيكون الكود كالتالي :
<asp:Xml ID="gamesXml" runat="server" DocumentSource="~/games.xml" TransformSource="~/games_xslt.xslt" />
لاحظ أننا قمنا بتحديد ملف الـ XML عن طريق الخاصية DocumentSource والتي هي عبارة عن رابط وقمنا بتحديد ملف الـ XSLT عن طريق الخاصية TransformSource. ماذا لو أردنا ترتيب نتائج ملف الـ XML أبجديا على حسب اسم اللعبه ؟ يمكنك إستخدام العنصر xsl:sort والذي يحوي على attribute باسم select وهو أيضا عبارة عن XPath expression، سيكون ملف الـ XSLT كالتالي :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<xsl:for-each select ="games/game">
<xsl:sort select="name"/>
<p style="padding: 5px;">
<h4 style="color: #a3a3f0">
<xsl:value-of select="name"/>
</h4>
<font style="font-size: 7pt; font-family: Tahoma; font-style:italic; font-wieght: bold;">
[Genre: <xsl:value-of select="genre"/>] [Release Date: <xsl:value-of select="releasedate" />]
</font>
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>ماذا لو أردنا عرض الألعب التي تنتدرج تحت platform pc فقط، يمكنك إستخدام العنصر
<xsl:if test="an XPath expression"></xsl:if>
حيث أن test أيضا عبارة عن XPath expression، سيكون ملف الـ XSLT كالتالي :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<xsl:for-each select ="games/game">
<xsl:if test="@platform='pc'">
<p style="padding: 5px;">
<h4 style="color: #a3a3f0">
<xsl:value-of select="name"/>
</h4>
<font style="font-size: 7pt; font-family: Tahoma; font-style:italic; font-wieght: bold;">
[Genre: <xsl:value-of select="genre"/>] [Release Date: <xsl:value-of select="releasedate" />]
</font>
</p>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>وأخيرا وليس آخرا هناك أيضا عنصر فعال وهو xsl:choose والذي يحوي أيضا عنصرين هما xsl:when و xsl:otehrwise ويمكنك أن تتخيل أنهم مثل If-Else ويمكنك إستخدامهم كالتالي
<xsl:choose>
<xsl:when test="an XPath expression">
<!-- Your template goes here -->
</xsl:when>
<xsl:when test="an XPath expression">
<!-- Your template goes here -->
</xsl:when>
<xsl:when test="an XPath expression">
<!-- Your template goes here -->
</xsl:when>
.
.
.
.
<xsl:otherwise>
<!-- Your template goes here -->
</xsl:otherwise>
</xsl:choose>والآن دعونا نقوم بالتالي وهو إستخدما العنصر xsl:choose بحيث يتم فحص الـ platform الخاص باللعبه حيث يتم عرض صورة تدل على كل نوع فمثلا لو كانت القيمة pc يتم عرض صورة شعار windows وهكذا، سيكون ملف الـ XSLT كالتالي :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:template match="/">
<xsl:for-each select ="games/game">
<p style="padding: 5px; filter: progid : DXImageTransform.Microsoft.Alpha(opacity=0);">
<h4 style="color: #a3a3f0;">
<xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="@platform = 'pc'">
<img src="images/win.png" alt="Windows" style="vertical-align: middle; margin-left: 20px;" />
</xsl:when>
<xsl:when test="@platform = 'xbox'">
<img src="images/xbox.png" alt="XBox 360" style="vertical-align: middle; margin-left: 20px;" />
</xsl:when>
<xsl:otherwise>
<img src="images/ps3.png" alt="Play Station 3" style="vertical-align: middle; margin-left: 20px;" />
</xsl:otherwise>
</xsl:choose>
</h4>
<font style="font-size: 7pt; font-family: Tahoma; font-style:italic; font-wieght: bold;">
[Genre: <xsl:value-of select="genre"/>] [Release Date: <xsl:value-of select="releasedate" />]
</font>
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>الخلاصة :
يمكنك إعتبار ملف الـ XSLT عبارة عن ملف CSS ولكن ليس لصفحة إلكترونبة بل لملف XML حيث أنه يمكنك تغيير ملف الـ XSLT ومن ثم رفعه لموقعك وذلك لغرض تغيير طريقة عرض البيانات دون الحاجة لكتابة كود ومن ثم compile ومن ثم الرفع. وأيضا أحد الفوائد الممكنه هو حل مشلكة السيناريو التالي: تخيل انك تعمل لشركة لديها نظامين مختلفين الأول بلغة java وقاعدة البيانات oracel والآخر بلغة CSharp وقاعدة البيانات MS SQL وكان البرنامج الأول يصدر ملفات XML بشكل معين والآخر يمكن أن يقراء ملفات XML ولكن بشكل لا يتناسب مع الملفات الصادرة من النظام الأول، وأفرض أن الكود (المصدر) غير موجود، كيف يمكنك أن تكامل (integrate) بينهم ؟ بإستخدام الـ XSLT يمكنك بناء قالب يتنباسب مع النظام الثاني بحيث يتم توليد القالب من محتوى الـ XML الصادر من النظام الأول. وذلك عن طريق العنصر xsl:apply-template، وللأسف الشديد أنا ليست لدي أدنى فكرة عنه ولكن أوعدكم أن أقوم بالبحث وكتابة موضوع بخصوصة.
ملاحظة : عندما تستخدم العنصر xsl:if وتريد أن تفحص قيمة ما إذا كانت أكبر من عدد معين، لن يكون هناك مشكلة ولكن لو أستخدمة العلامة أصغر من وأردت أن تفحص قيمة ما إذا كانت أصغر من عدد معين فهذا سيؤدي إلى خطأ أجهل سببه. لو كان المطلوب منك هو فحص قيمة ما إذا كانت أصغر من عدد معين فقم بإستخدام العنصر xsl:choose.
في المرفقات ستجدون مشروع كامل لكل الحالات التي م عرضها هنا المتعلقه فقط بـ ASP.NET, XML & XSLT.
أتمنى للجميع التوفيق