Stromovou strukturu dat uložených v SQL databázi lze zobrazit mnoha způsoby. V tomto článku si ukážeme postup využívající technologii XSLT a výsledkem naší práce bude zobrazení strukturovaného diskusního fóra. Analýza relací mezi příspěvky nebude úkol pro C# kód, ale bude plně v moci XSL transformace.

Prolog

K tomuto článku mě inspiroval Pavel Růžička svým článkem SqlDataAdapter a zobrazení stromové struktury Repeaterem v ASP.NET, ve kterém řešil podobné zadání poměrně sofistikovaným způsobem. Já bych vám chtěl nabídnout o něco jednodušší řešení s XSL transformací v hlavní roli. Základním problémem zadání je změna uspořádání jednotlivých příspěvků v diskusi z dvojrozměrné databázové tabulky do přehledné stromové struktury, kterou pak zobrazíme na své webové stránce – jde vlastně o transformaci dat, a to je ta správná úloha pro XSLT.

Aplikace se nejdříve musí postarat o přečtení vstupních dat a ty pak musí předat transformaci. Třídu SqlDataAdapter již znáte, dnes se seznámíme s třídou XmlDataDocument, kterou použijeme pro převod dat z DataSetu na XmlDocument, který pak putuje do transformace, jejíž výsledek bude již toužebně očekávaný HTML kód. Avšak nepředbíhejme a začněme pěkně od začátku.

Zdrojová data

Mějme databázi obsahující tabulku diskuse. Tato tabulka obsahuje jednotlivé diskusní příspěvky uspořádané do stromové struktury takovým způsobem, že každý příspěvek odkazuje na svůj nadřazený jeho identifikátorem. Vzniká tak vztah potomek – rodič. Naše tabulka má tyto sloupce…

Sloupec Datový typ Popis
ID int Unikátní identifikátor příspěvku, primární klíč.
pID int ID nadřazeného příspěvku nebo NULL u kořenového příspěvku.
autor varchar Nick autora příspěvku.
email varchar Email autora příspěvku.
obsah varchar Text příspěvku.

…a může obsahovat například následující data:

ID pID autor email obsah
1 NULL paya NULL 4all: Ahoj, jdeme na pivo?
2 1 pierre ja@pierre.cz OK, v 19h na Stodolní?
3 2 paya NULL Domluveno :)
4 NULL NULL Nekuřte, škodí to zdraví… :-)
5 1 zila NULL Asi ne, jsem nějaký unavený…

Jak vidíte, příspěvky jsou trochu zpřeházené, některá pole nejsou vyplněná, a v kořenu diskuse je více než jeden příspěvek. Nic z toho ale nepředstavuje podstatný problém, jak uvidíte později.

Vytvoření projektu

Náš úkol je velice jednoduchý. Jeho základem bude jediná stránka, kterou si pojmenujeme diskuse.aspx a bude obsahovat následující kód:

<%@ Page language=“c#“ Codebehind=“Diskuse.aspx.cs“ AutoEventWireup=“false“ Inherits=“Interval.CZ.Diskuse.Diskuse“ %>
<html>
  <body MS_POSITIONING=“GridLayout“>
    <asp:Xml
        id=“Tree“
        TransformSource=“diskuse.xsl“
        runat=“server“>
    </asp:Xml>
  </body>
</html>

Jak vidíte, samotnou transformaci budeme provádět standardním XML webcontrolem. Potřebný stylesheet si napíšeme později, vstupní dokument bude dodán v metodě Page_Load stránky. Přejdeme tedy ke kódu stránky a nejdříve deklarujeme potřebné jmenné prostory. Budeme pracovat s konfigurací aplikace, s daty, s připojením k MS SQL serveru a s XML.

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Xml;

Načtení vstupních dat

Prvním úkolem stránky bude samozřejmě získání dat z databáze a převod do XML formátu, aby mohly putovat dále do transformace. Mohli bychom sice pro přístup k datům použít komponentu SQLXML, to by však znamenalo závislost na její existenci a já vám chci ukázat způsob, který je trochu bližší standardním metodám přístupu k datům.

Aplikace potřebuje znát připojovací řetězec (connection string) k databázi a dotaz vracející požadovaná data. Tyto dvě informace uložíme do konfiguračního souboru aplikace web.config. Provedeme to přidáním nových klíčů do elementu appSettings, který k tomuto účelu vytvoříme:

<?xml version=“1.0″ encoding=“utf-8″ ?>
<configuration>
  <appSettings>
    <add key=“connStr“ value=“Data Source=server; Initial Catalog=db; User ID=sa; Password=“ />
    <add key=“command“ value=“SELECT * FROM diskuse“ />
  </appSettings>
  …

Nezapomeňte samozřejmě do connection stringu zapsat skutečnou adresu serveru, název databáze a jméno uživatele s jeho heslem. Tato data pak načteme ve stránce takto:

private void Page_Load(object sender, System.EventArgs e)
{
  string connStr =
      ConfigurationSettings.AppSettings[„connStr“];
  string command =
      ConfigurationSettings.AppSettings[„command“];

K získání dat použijeme běžně používaný SqlDataAdapter, který pak těmito daty naplní DataSet:

  SqlDataAdapter adapter =
      new SqlDataAdapter(command, connStr);
  DataSet data = new DataSet(„Diskuse“);
  adapter.Fill(data, „Prispevek“);
  adapter.Dispose();

Nakonec získaná data převedeme do XML dokumentu využitím třídy XmlDataDocument. Tato třída je přímým potomkem XmlDocument a umožňuje pracovat s DataSetem stejně jako s XML dokumentem. Toho využijeme, vytvoříme novou instanci z našeho DataSetu a přímo ji předáme XML webcontrolu jako vstupní dokument. Žádné další manipulace nejsou třeba, protože vše zařídí XML transformace. XML webcontrol se během renderování stránky postará sám o transformaci nad předaným dokumentem a výsledek přímo umístí do stránky na svou pozici.

  this.Tree.Document = new XmlDataDocument(data);
}

Tímto je náš C# kód kompletní. Jak vidíte, samotné výkonné jádro obsahuje pouze sedm řádků a je použitelné na jakékoli data v jakémkoli formátu, protože je nijak přímo nezpracovává.

Transformace

Přistupme k poslednímu, avšak nejdůležitějšímu kroku, kterým je napsání transformačního stylesheetu diskuse.xsl. Protože vstupní dokument je zatím nezpracovaný, bude muset stylesheet analyzovat stromovou strukturu sám. Jak uvidíte níže, nebude to činit žádné potíže. Nejdříve se podívejme, jak mohou XML data vygenerovaná z DataSetu vypadat:

<Diskuse>
  <Prispevek>
    <ID>1</ID>
    <autor>paya</autor>
    <obsah>4all: Ahoj, jdeme na pivo?</obsah>
  </Prispevek>
  <Prispevek>
    <ID>2</ID>
    <pID>1</pID>
    <autor>pierre</autor>
    <email>ja@pierre.cz</email>
    <obsah>OK, v 19h na Stodolní?</obsah>
  </Prispevek>
  …
</Diskuse>

Pole, které v databázi mají hodnotu NULL, se v XML dokumentu nevyskytují vůbec. Toho pak využijeme v transformaci.

Vstupním bodem stylesheetu bude šablona pro element Diskuse, která bude hledat kořenové příspěvky, to znamená ty, které neobsahují odkaz na rodiče – element pID. To lze snadno otestovat predikátem count(pID)=0. Příspěvky budou v HTML zobrazeny jako nečíslovaný seznam, proto je zabalíme do elementu ul.

Jádro transformace bude tvořit šablona pro Prispevek. Tato šablona bude zobrazovat informace o příspěvku jako položku seznamu, její tělo tedy bude uzavřeno do elementu li. Dále bude šablona zkoumat, zda příspěvek má nějaké odpovědi a pokud ano, vytvoří nový seznam (ul), do nějž nechá rekurzivně zpracovat tyto odpovědi. Při hledání odpovědí bude porovnávat jejich pID s hodnotou ID aktuálního příspěvku.

Některé příspěvky nemají vyplněn e-mail autora, tuto skutečnost tedy budeme vyhodnocovat a pokud bude e-mail uveden, poskytneme jej přímo jako odkaz.

<?xml version=’1.0′?>
<xsl:stylesheet
    xmlns:xsl=’http://www.w3.org/1999/XSL/Transform‘
    version=’1.0′>
  <xsl:template match=“Diskuse“>
    <ul>
      <xsl:apply-templates
          select=“Prispevek[count(pID)=0]“ />
    </ul>
  </xsl:template>
  <xsl:template match=“Prispevek“>
    <li>
      <h4>
        <xsl:value-of select=“autor“ />
        <xsl:if test=“email“>
          <xsl:text> (</xsl:text>
          <a href=“mailto:{email}“>
            <xsl:value-of select=“email“ />
          </a>
          <xsl:text>)</xsl:text>
        </xsl:if>
      </h4>
      <p><xsl:value-of select=“obsah“ /></p>
      <hr/>
    </li>
    <xsl:if test=“/Diskuse/Prispevek[pID=current()/ID]“>
      <ul>
        <xsl:apply-templates
          select=“/Diskuse/Prispevek[pID=current()/ID]“/>
      </ul>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Chování při nekonzistenci dat

Pokud je v diskusi příspěvek odkazující se na neexistujícího rodiče, tento příspěvek nebude nikdy zobrazen, protože transformace postupuje od rodičů k potomkům a chybný příspěvek žádného rodiče nemá. Jinou chybou je příspěvek, který za svého přímého či nepřímého rodiče považuje sám sebe, například pokud příspěvek A odkazuje na B a zároveň B odkazuje na A. V takové situaci oba příspěvky vytvoří jakýsi ostrůvek oddělený od ostatních příspěvků, takže se k němu transformace opět nedostane a nedojde k žádnému zacyklení.

Epilog

Můžete si stáhnout celou aplikaci a vyzkoušet si ji. Ještě musím dodat, že toto řešení není úplně ideální, protože znemožňuje používání ASP.NET controlů uvnitř diskuse. Ve většině případů to však ani nepotřebujeme. Naopak velkou výhodou jsou vlastnosti stylesheetu – je přehledný, snadno upravitelný a lze využít celý sortiment XSLT a XPath funkčností nabízejících snadnou práci se strukturovanými daty.

Odkazy, zdroje

Žádný příspěvek v diskuzi

Odpovědět