XSLT در واقع یک StyleSheet یا یک راهنما در مورد تبدیل فایلهای xml به
انواع و یا ساختارهای دیگری چون فایلهای html، فایلهای متنی و ... است که توسط
کنسرسیوم وب ارائه شدهاست. این فایل حاوی یک سری دستورالعمل برای
برنامههای پردازشگر است که به آنها میگوید چگونه این فایل را تبدیل کنند.
اساس کار XSLT
در تصویر زیر، فایل xml به همراه xslt، به تجزیه کننده یا تحلیل کننده داده
میشوند. در این قسمت هر دو فایل منبع تحلیل شده و از روی فایل xml، درختی در
حافظه تهیه میشود و از فایل xslt یک سری قوانین استخراج میشوند. بعد این
دو محتوای جدید تولید شده، در اختیار XSL Processor قرار گرفته و از روی
آنها به ساخت درخت نتیجه (نوع درخواستی) در حافظه میرسد که در نهایت آن
را به نام یک فایل مستند میکند.
توجه داشته باشید که xslt یک زبان برنامه نویسی نیست؛ ولی تعدادی دستورالعملهای مشابه و توابع داخلی در آن قرار گرفته است. در اینجا هنگام کار با نام گرهها، باید به بزرگی و کوچکی حروف توجه کنید.
پروژه نمونه
در
این مقاله ما یک فایل xml داریم که قصد داریم آلبومهایی را طبق ساختار زیر،
در آن قرار دهیم و سپس بر اساس قوانین xslt آن را در قالب یک فایل html
نشان دهیم. فایلهای تمرینی این مقاله در
این آدرس قابل دسترسی است.
ساختار فایل xml: <Albums>
<Album>
<name>Modern Talking</name>
<cover>http://album.com/a.jpg</cover>
<Genres>
<Genre>POP</Genre>
<Genre>Jazz</Genre>
<Genre>Classic</Genre>
</Genres>
<Description>
this is a marvelous Album
</Description>
<price>
25.99$
</price>
</Album>
</Albums>
ساختار فایل Html
در ابتدا فایل، برای معرفی فایل و رعایت قرارداد، فضای نام مربوطه را یا به شکل زیر
<xsl:stylesheet version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
یا بدین شکل
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
مینویسیم و سپس کل کدهای html را با تگ زیر محصور میکنیم:
<xsl:template match="/">
<html>
....
</html>
</xsl:template>
هر آدرسی که در match بنویسید، آدرسهای دستورات داخلی در ادامهی آن خواهند بود.
نمایش فیلدها
سپس
در آن، کد html مورد نظر را وارد میکنیم و در میان این کدها، تگهای xslt
را وارد مینماییم. کد زیر بخشی از صفحه هست که برای نمایش آلبومها استفاده میکنیم:
<div>
<img src="" alt="image" />
<h3>
<xsl:value-of select="albums/album/name" />
</h3>
<h4>Artist Name 1</h4>
<h5>POP</h5>
<h6>25/5/2015</h6>
<h7>this is description</h7>
<div>
<a href="#">More</a>
</div>
</div>
تگهای xsl نشان دهنده
دستورالعملهای این قالب هستند. این دستور وظیفه انتخاب یک آیتم و نمایش آن
را دارد. در قسمت select، آدرس نام آلبوم را به صورت یک مسیر، از تگ والد به
سمت پایین وارد کردهایم. این دستور العمل با اولین گره که به آن برسد،
نمایش مییابد ولی اگر بخواهیم کدهای بالا، به تعداد هر آیتم (آلبوم)
تکرار شود، از دستور العمل حلقه استفاده میکنیم:
<xsl:for-each select="albums/album">
<div>
<img src="" alt="image" />
<h3>
<xsl:value-of select="name" />
</h3>
<h4>Artist Name 1</h4>
<h5>POP</h5>
<h6>25/5/2015</h6>
<h7>this is description</h7>
<div>
<a href="#">More</a>
</div>
</div>
</xsl:for-each>
خطوط
بالا، مرتبا گرههای album را در گره Albums، یافته و همه تگهای داخلش
را تکرار کرده و نام هر آلبوم را نیز چاپ میکند. موقعی که از حلقه استفاده میکنیم و مسیر گره والد در آن مشخص شده است، نیازی نیست که دیگر برای
دریافت نام آلبوم، کل مسیر را ذکر کنید.
ایجاد ارتباط میان دو فایل XML و XSLT
برای اجرا و تست آن باید از طریق
یک ابزار که توانایی تحلیل این دستورات را دارد، استفاده کنید. یکی از همین
ابزارها، مرورگر شماست. برای اینکه به مرورگر ارتباط فایل xml و xsl را
بفهمانیم، تکه کد زیر را در فایل xml جهت لینک شدن مینویسیم و سپس فایل
xml را در مرورگر اجرا میکنیم:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="index.xslt"?>
الان تصویر بدین شکل نمایش داده میشود:
ساخت المان یا تگ
در
ادامه بقیه فیلدها را تکمیل میکنیم. فیلد بعدی تصویر است که تصویر دیگر
مانند متن، بین تگها قرار نمیگیرد و باید داخل attribute تگ تصویر درج
شود. برای نمایش تصویر میتوانیم به دو شکل عمل کنیم. کد را به صورت زیر
بنویسیم:
<img src="{cover}" width="200px" height="200px">
<xsl:value-of select="cover" />
</img>
یا اینکه المان مورد نظر را توسط xsl ایجاد کنیم:
<xsl:element name="img">
<xsl:attribute name="src">
<xsl:value-of select="cover"/>
</xsl:attribute>
<xsl:attribute name="width">
200px
</xsl:attribute>
<xsl:attribute name="height">
200px
</xsl:attribute>
<xsl:value-of select="cover"/>
</xsl:element>
حال با یکی از کدهای بالا، مرورگر را جهت تست اجرا میکنیم:
دستور العملهای بیشتر (مرتب سازی)
دستورات
xslt فقط به چند تگ بالا خلاصه نمیشود؛ بلکه دستورات شرطی ،تعریف
متغیرها، توابع داخلی جهت کار با اعداد، رشتهها و ... هم در آن وجود
دارند. یکی از همین دستورات جذاب، مرتب سازی است. دستورالعل sort به ما اجازه میدهد تا بر اساس یک فیلد، دادهها را مرتب کنیم. با افزودن کد زیر بعد
از دستور حلقه، لیست را بر اساس نام آلبوم مرتب میکنیم:
<xsl:for-each select="Albums/Album">
<xsl:sort select="name"/>
دستورات شرطی
حال تصمیم میگیریم که آلبومهای با قیمت بالاتر از 10 دلار را 2 دلار تخفیف دهیم. برای اینکار نیاز است تا با تگ if آشنا شویم:
<xsl:variable name="numprice" select="number(substring(price,1,string-length(price)-1))" />
<xsl:if test="$numprice>=10">
<h4 >
<span style='text-decoration:line-through'>
<xsl:value-of select="price" />
</span>
<b>
<xsl:value-of select="$numprice -2" />$
</b>
</h4>
</xsl:if>
در اینجا کمی مثال را پیچیدهتر و از چند عنصر جدید در آن استفاده کردهایم. در خط اول یک متغیر با نام
numprice تعریف کردهایم. متغیرها بر دو نوعند: محلی یا عمومی. برای تعریف
متغیر عمومی لازم است آن را در بالای سند، بعد از تگ template ایجاد کنید و
متغیرهای محلی فقط در داخل همان تگی که تعریف کردهاید، اعتبار دارند. از
آنجا که قیمتها به صورت رشتهای هستند و در انتها هم حرف $ را دارند، بهتر است که قیمت را بدون $ به عدد تبدیل کنیم تا بتوانیم بعدا در شرط، به عنوان
عدد، مقایسه و در صورت صحت شرط، دو عدد از آن کم کنیم. در اینجا ما از تابع
substring برای جداسازی رشته و از تابع string-lentgh برای دریافت طول رشته و
در نهایت از تابع number برای تبدیل رشته به عدد استفاده کردیم و آنرا
برای استفادههای بعدی، در متغیر ذخیره کردیم (برای آشنایی بیشتر با این توابع به این
آدرس رجوع کنید). سپس تگ if را صدا زدیم و در
صورتی که مقدار داخل متغیر (علامت متغیر، استفاده از عبارت $ قبل از نام
آن است ) از 10 بیشتر یا برابر آن بود، دو واحد از آن کم میکنیم و روی قیمت
قبلی خط میکشیم. نتیجه حاصله در تصویر زیر مشخص است:
در حال حاضر مشکلی که وجود دارد این است که ما برای قیمتهای زیر 10 دلار هیچ شرطی نداریم و این دستور if کمی سطحی برخورد میکند و برای قیمتهای زیر 10 دلار مجددا به یک if نیازمندیم. ولی دستور دیگری، مشابه دستور switch وجود دارد که استفاده از آن در شرایط دو شرط بالا مقرون به صرفه است. نام این دستور choose میباشد. خطوط بالا را به شکل زیر تغییر میدهیم:
<xsl:variable name="numprice" select="number(substring(price,1,string-length(price)-1))" />
<xsl:choose>
<xsl:when test="$numprice>=10">
<h4 >
<span style='text-decoration:line-through;color:red'>
<xsl:value-of select="price" />
</span>
<b>
<xsl:value-of select="$numprice -2" />$
</b>
</h4>
</xsl:when>
<xsl:otherwise>
<b>
<xsl:value-of select="$numprice" />$
</b>
</xsl:otherwise>
</xsl:choose>
شما میتوانید به تعداد زیادی از تگ when برای اعمال دیگر شرطها همانند دستور switch ...case استفاده کنید. در اینجا اگر قیمتها زیر 10 دلار باشند، تغییری در قیمت ایجاد نکرده و خودش را نشان میدهیم. ولی اگر از 10 دلار به بالا باشد، قیمتها دو دلار تخفیف میخورند. شکل زیر نتیجه حاصل از اضافه شدن کد بالاست:
استفاده از template ها
برای خلاصه سازی کار و جمع و جور کردن کدها میتوان از templateها استفاده کرد. شما هم در ابتدا، یک قالب یا template را برای کل سند ایجاد کردید. حالا سعی ما این است که اینبار، قالبهای کوچکتر و جرئیتر و اختصاصیتر ساخته و آنها را در قالب اصلی صدا بزنیم. برای ساخت قالب به ریشه xsl:stylesheet رفته و یک template جدید را به شکل زیر ایجاد میکنیم:
<xsl:template match="DateOfRelease">
<xsl:variable name="date" select="."/>
<xsl:variable name="day" select="substring($date,1,2)"/>
<xsl:variable name="month" select="number(substring($date,4,2))"/>
<xsl:variable name="year" select="substring($date,7,4)"/>
Release Date:
<xsl:choose>
<xsl:when test="$month = 1">
<xsl:value-of select="$day"/>,Jan/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 2">
<xsl:value-of select="$day"/>,Feb/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 3">
<xsl:value-of select="$day"/>,Mar/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 4">
<xsl:value-of select="$day"/>,Apr/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 5">
<xsl:value-of select="$day"/>,May/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 6">
<xsl:value-of select="$day"/>,Jun/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 7">
<xsl:value-of select="$day"/>,Jul/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 8">
<xsl:value-of select="$day"/>,Aug/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 9">
<xsl:value-of select="$day"/>,Sep/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 10">
<xsl:value-of select="$day"/>,Oct/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:when test="$month = 11">
<xsl:value-of select="$day"/>,Nov/<xsl:value-of select="$year"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$day"/>,Dec/<xsl:value-of select="$year"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
در این قالب ما روی فیلد تاریخ کار میکنیم و قصد داریم تاریخ را در قالب نوشتاری دیگری درج کنیم. عبارت نقطه (.) در select خط اول به معنای گره جاری است. بعد از ساخت قالب جدید آن را در محل مورد نظر در قالب اصلی یا ریشه صدا میزنیم:
<xsl:for-each select="Albums/Album">
<xsl:sort select="name"/>
<div>
<img src="{cover}" width="200px" height="200px">
<xsl:value-of select="cover" />
</img>
<h3>
<xsl:value-of select="name" />
</h3>
<h4>POP</h4>
<xsl:variable name="numprice" select="number(substring(price,1,string-length(price)-1))" />
<xsl:choose>
<xsl:when test="$numprice>=10">
<h4 >
<span style='text-decoration:line-through;color:red'>
<xsl:value-of select="price" />
</span>
<b>
<xsl:value-of select="$numprice -2" />$
</b>
</h4>
</xsl:when>
<xsl:otherwise>
<h4 >
<b>
<xsl:value-of select="$numprice -2" />$
</b>
</h4>
</xsl:otherwise>
</xsl:choose>
<h4>
<!-- محل صدا زدن قالب-->
<xsl:apply-templates select="DateOfRelease"/>
</h4>
<h4>
<xsl:value-of select="Description"/>
</h4>
<div>
<a href="#">More</a>
</div>
</div>
</xsl:for-each>
فیلترسازی
یکی از خصوصیات دیگری که فایل
XML داشت، فیلد ژانر موسیقی بود و قصد داریم با استفاده از فیلترسازی،
تنها سبک خاصی مثل سبک پاپ را نمایش دهیم و موسیقیهایی را که خارج از این سبک
هستند، از نتیجه حذف کنیم. به همین علت دستور for-each را به شکل زیر
تغییر میدهیم:
<xsl:for-each select="Albums/Album[contains(Genres/Genre, 'POP')]">
دستور
بالا میگوید که حلقه را آلبومها حرکت بده، به شرطی که در مسیر
Genres/Genre مقداری برابر POP بیابی. حالا اگر بخواهیم این شرط را معکوس
کنیم میتوانیم عبارت را به صورت زیر درج کنیم:
<xsl:for-each select="Albums/Album[not(contains(Genres/Genre, 'POP'))]">
برای شکیل شدن کار، بهتر است که نام سبکها را در کنار تصویر هم درج کنیم. به همین علت دستور حلقه را مورد استفاده قرار میدهیم:
<xsl:for-each select="Genres/Genre">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
,
</xsl:if>
</xsl:for-each>
دستور بالا شامل دو
تابع جدید هست که مقدار برگشتی آنها اندیس گره فعلی و اندیس آخرین گره میباشد و در عبارت شرطی ما تعیین کردهایم که بین هر سبکی که مینویسد، یک
علامت جدا کننده , هم قرار گیرد؛ به جز آخرین گره که دیگر نیازی به علامت
جدا کننده ندارد. تصویر زیر آلبوم هایی را نشان میدهد که در سبک پاپ قرار
گرفتهاند:
تبدیل xml و xsl در دات نت
string xmlfile = Application.StartupPath + "\\sampleXML.xml";
//بارگذاری فایل قوانین در حافظه
XslTransform xslt = new XslTransform();
xslt.Load("sample-stylesheet.xsl");
//XPath ایجاد یک سند جدید بر اساس استاندارد
XPathDocument xpath = new XPathDocument(xmlfile);
//آماده سازی برای نوشتن فایل نهایی
XmlTextWriter xwriter = new XmlTextWriter(xmlfile + ".html", Encoding.UTF8);
//نوشتن فایل مقصد بر اساس قوانین مشخص شده
xslt.Transform(xpath, null, xwriter, null);
xwriter.Close();
در صورتی که فایل XSL نیازی به تگهایی داشته باشد که در فایل xml نیست و خودتان قصد دارید که آنها را از این طریق وارد کنید، میتوانید خطوط زیر را به کد بالا اضافه کنید:
XsltArgumentList xslArgs = new XsltArgumentList();
xslArgs.AddParam("logo", "", logo);
xslArgs.AddParam("name", "", name);
....
xslt.Transform(xpath, xslArgs, xwriter, null);