12 lipca 2011

XSLT, XslCompiledTransform i modularność w transformacjach

Wysoka temperatura, XSL i modularność


W ramach implementacji wersji 1.0 Weather.com C# .NET client postanowiłem dodać przykładową transformację XML'a zwracanego przez usługę Weather.com. Jako, że użycie arkusza XSL wydawało mi się najprostsze, przygotowałem prostą transofrmację. Usługa zwraca dane o aktualnym stanie pogody, ale jest też możliwe pobranie prognozy pogody na najbliższe dni. Oba wyniki zwracane przez usługę Weather.com w formacie XML mają podobną strukturę. Jako, że używam XSLT od "wielkiego dzwonu", wychodzi średnio raz na rok, to musiałem poszperać, czy istnieje możliwość pisania "modułów" które mogą być współdzielone pomiędzy arkuszami XSL.

<xsl:include />


Okazuje się, że XSL posiada dyrektywę <xsl:include /> która umożliwia importowanie zewnętrznych arkuszów. W każdym z dwóch formatów XMLa z aktualnymi warunkami pogodowymi i prognozą pogody, powielały się pewne elementy. Szczególnie istotne były jednostki temperatury, ciśnienia etc,  które były później wykorzystywane do formatowania wyników. Załużmy, że dysponujemy poniższych plikiem XML, który chcemy przekształcić w dokument HTML.

Sample.xml file

<?xml version="1.0" encoding="UTF-8"?>
<weather>
  <header>
    <utemp>C</utemp>
  </header>
  <conditions>
    <temperature>22</temperature>
    <feelslike>19</feelslike>
  </conditions>
</weather>

Do transformacji użyjemy do tego dwóch plików XSL. Weather.xsl z prostym wzorcem XSL i Shared.xml w którym zdefiniowano zmienną przypisaną do jednostki miary temperatury (w naszych przypadku to stopnie Celcjusza).


Weather.xsl file

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
  <xsl:output method="html" />
  <xsl:include href="Shared.xsl"/>

  <xsl:template match="/">
    <html>
      <head>
        <title>Weather conditions</title>
      </head>
      <body>
        <xsl:apply-templates />
      </body>
    </html>
  </xsl:template>

  <xsl:template name="ccWeather" match="weather">
    <xsl:apply-templates select="conditions"/>
  </xsl:template>

  <xsl:template match="conditions">
    Temperature: <xsl:value-of select="temperature" />° <xsl:copy-of select="$TemperatureUnit" /> <br />
    Feels Like: <xsl:value-of select="feelslike" />° <xsl:copy-of select="$TemperatureUnit" /> <br />
  </xsl:template>

</xsl:stylesheet>


Shared.xml służy prezentacji koncepcji wykorzystywania wzroców, zmiennych przez różne arkusze XSL. Jest on importowany do Weather.xls za pomocą dyrektywy <xsl:include />.

Shared.xsl file

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

  <xsl:variable name="TemperatureUnit" select="weather/header/utemp" />
  
  <!-- Templates and other modules can go here ... -->

</xsl:stylesheet>

<xsl:include /> i XmlUrlResolver


W C# aby uruchomić arkusz XSL, który zawiera deklarację <xsl:include /> konieczne jest zdefiniowanie XmlUrlResolver'a. W przypadku pominięcia tego obiektu i nie przekazania go do metody XslCompiledTransform.Load zostanie rzucony następujący wyjątek.


System.Xml.Xsl.XslLoadException was unhandled
Message=XSLT compile error.
StackTrace:
at System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader reader, Boolean include)
at System.Xml.Xsl.Xslt.XsltLoader.Load(Compiler compiler, Object stylesheet, XmlResolver xmlResolver)
QilExpression& qil)

....
InnerException: System.Xml.XmlException
Message=Resolving of external URIs was prohibited.
StackTrace:
at System.Xml.XmlNullResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn)
at System.Xml.Xsl.Xslt.XsltLoader.CreateReader(Uri uri, XmlResolver xmlResolver)
....
at System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader reader, Boolean include)
InnerException:



Poniższy kod prezentuje wczytanie pliku dany XML, pliku XSL i wygenerowanie dokumentu w formacie HTML. Kod projektu jest dostępny tutaj.

using System.Diagnostics;
using System.Net;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;

class Program
{
    static void Main(string[] args)
    {
        //Load XSLT transformation
        string xsltPath = @"Weather.xsl";
        XmlUrlResolver resolver = new XmlUrlResolver();
        resolver.Credentials = CredentialCache.DefaultCredentials;
        var transformXmlToHtml = new XslCompiledTransform();
        transformXmlToHtml.Load(new XPathDocument(xsltPath), XsltSettings.Default, resolver);
        //Transform XML into HTML
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.OmitXmlDeclaration = true;
        XmlReader reader = XmlReader.Create(@"Sample.xml");
        using (XmlWriter textWriter = XmlWriter.Create("WeatherResult.html", settings))
        {
            transformXmlToHtml.Transform(reader, textWriter);
        }

        Process.Start("WeatherResult.html");
    }
}

Wynikiem działania powyższego kodu jest otwarta domyślna przeglądarka z plikiem WeatherResult.html.



Bardzo pomocnym narzędziem do testowania transormacji XSLT  jest Visual Studio. Otwierając plik XSL pojawia się dodatkowy element w menu XML. Visual Studio 2010 umożliwia debugowanie i stawianie breakpoint'ów w plikach XSL.


W zakładce 'Properties' danego pliku należy podać zródłowy plik danych XML (Input), ścieżkę do pliku wynikowego (Output) i uruchomić 'Start XSLT Without Debugging'.





Poniżej wyniki transformacji w Visual Studio 2010:



Podsumowując, XslCompiledTransform wspiera <xsl:include /> i należy tylko użyć klasy XmlUrlResolver aby załadować zewnętrzny arkusz XSL.

Hope this helps.

Brak komentarzy:

Prześlij komentarz

Uwaga: tylko uczestnik tego bloga może przesyłać komentarze.