RSS kanál je standardním doplňkem dnešních webzinů. Přesto mezi nimi existují černé ovce, které tuto technologii zarputile ignorují, nebo dokonce odmítají. Vytvořme pro ně tedy parazitní RSS kanál v prostředí Visual Studio .NET.

V tomto článku budou popsány techniky, které lze využít nejen na vytváření RSS kanálů, ale obecně pro jakékoli sbírání dat z www stránek. Myslím si, že čtenáři Intervalu jsou na úrovni a nebudou tyto dovednosti zneužívat proti vůli autorů webů. Já jsem si jako testovací web vybral magazín Interval.cz, a to i přesto, že již má svůj vlastní RSS kanál.

Princip

Výsledkem práce bude webová aplikace, kterou můžete umístit například na svém počítači, nebo kdekoli na svém serveru. Jejím prvním úkolem je stažení obsahu vybrané (nejspíše titulní) stránky webzinu, která (pokud možno) obsahuje seznam článků či spotů včetně nějakých případných doplňkových informací (perex, jméno autora či datum vydání). Z obsahu stránky je pomocí knihovny SgmlReader získán XmlDocument, z něhož již XML transformací vytáhneme požadované data a zpracujeme do RSS kanálu. To je vše. Takže jdeme na to!

Stažení dat a SgmlReader

Založíme nový projekt typu ASP.NET Web Application a v něm vytvoříme stránku rss.aspx. Její HTML kód bude velmi jednoduchý, bude obsahovat pouze komponentu Xml, do které ho se budou načítat vstupní data pro kanál, a která bude nad těmito daty provádět transformaci. Proto vytvoříme transformační stylesheet data.xslt, který prozatím ponecháme prázdný, a nařídíme komponentě, aby jej používala pomocí atributu TransformSource. Musíme také nastavit výstupní MIME typ stránky na text/xml, abychom světu sdělili, že naše stránka generuje data ve formátu XML.

<%@ Page language=“c#“ Codebehind=“rss.aspx.cs“
  ContentType=“text/xml“ Inherits=“interval.rss“ %>
<asp:Xml id=“data“ TransformSource=“data.xslt“
  runat=“server“></asp:Xml>

Několikrát zde byla zmíněna knihovna SgmlReader. O co kráčí? Jak víme, .NET Framework poskytuje sadu tříd pro zpracování XML dokumentů. Celá řada webů je však psána ve starém (a často nevalidním) HTML, a má dost daleko do XHTML (což je vlastně implementace XML). Obsah těchto webů tedy nelze těmito třídami přímo zpracovat. SgmlReader je třída, která dědí z XmlReader, takže poskytuje standardní rozhraní pro čtení Xml dokumentů, avšak spokojí se i s poměrně „spraseným kódem“. Volitelně lze zapnout logování nalezených chyb, ovšem ty mohou být ignorovány, resp. vhodným způsobem kompenzovány. SGML je standard, ze kterého vychází HTML i XML a je to poměrně starý a velmi tolerantní značkovací jazyk. SgmlReader je ještě tolerantnější. Nejdříve si tedy tuto knihovnu stáhněte, rozbalte, a projekt SgmlReaderDll.csproj ze složky SgmlReader\SgmlReaderDll přidejte do naší aplikace (solution). Nezapomeňte tento projekt také přidat mezi reference našeho ASP.NET projektu.

Třída SgmlReader jako svůj vstup očekává TextReader, takže budeme muset data z HTTP odpovědi dekódovat nějakým kódováním, abychom získali z binárních dat jejich textovou reprezentaci. Jak zjistíme, v jakém kódování je HTML stránka zapsána? To je docela ošemetná záležitost, protože tato informace se může ukrývat buď v XHTML hlavičce, nebo ve známém META elementu. Programově tyto informace získat není právě hračka. Jsme v bezradné situaci. Abychom mohli data předat SgmlReaderu, potřebujeme v nich najít specifikaci kódování, takže musíme nejdříve stránku analyzovat, a k tomu nejlépe slouží právě SgmlReader. Je to vlastně bludný kruh.

Naštěstí tento úkol je řešitelný, a řešení je ukryto opět v této nesmírně užitečné knihovně. Zapátráme-li důkladněji ve jmenném prostoru Sgml, možná zavadíme pohledem o třídu HtmlStream. Ten název zní velmi slibně, nemyslíte? Tato třída dědí z TextReaderu (vzpomeňte si, jde o vyžadovaný vstupní formát pro SgmlReader), a její konstruktor má dva parametry: jeden typu Stream (lze přímo napojit výstup z WebResponse), a druhý se nazývá defaultEncoding a jde o kódování znaků, které je použito implicitně, pokud není nalezena explicitní definice ve stránce. Celá třída má podstatnou nevýhodu: je deklarována jako interní, takže ji nelze použít v našem projektu. Zdrojové kódy máme (SgmlParser.cs), není tedy nic snazšího, než přepsat v hlavičce deklarace této třídy klíčové slovo internal na public. Pak projekt SgmlReaderu zkompilujte.

Nyní tedy známe způsob, jak z kódu libovolné stránky můžeme získat XmlReader. Napišme si tedy funkci, která nám stáhne XmlDocument z URL adresy předané parametrem jako řetězec. Takovou funkce bude jistě znovupoužitelná v úplně jiných projektech. Přejděme do kódu stránky a nadeklarujme potřebné jmenné prostory:

using Sgml;
using System.Net;
using System.IO;
using System.Xml;
using System.Text;

A nyní již slíbená metoda:

public XmlDocument GetPage(string url)
{
  // http požadavek
  WebRequest request = WebRequest.Create(url);
  WebResponse response = request.GetResponse();
  TextReader stream = new HtmlStream(
      response.GetResponseStream(),
      Encoding.GetEncoding(„iso-8859-2“));
  // zpracování SgmlReaderem do stringu
  SgmlReader reader = new SgmlReader();
  reader.InputStream = stream;
  reader.DocType = „HTML“;
  StringWriter str = new StringWriter();
  XmlTextWriter xml = new XmlTextWriter(str);
  reader.Read();
  while(!reader.EOF)
  {
    xml.WriteNode(reader,true);
  }
  xml.Flush();
  xml.Close();
  response.Close();
  stream.Close();
  // načtení stringu do XmlDocumentu
  XmlDocument data = new XmlDocument();
  data.LoadXml(str.ToString());
  return data;
}

Možná vás překvapilo, že data z XmlReaderu do XmlDocumentu cestují poněkud krkolomně uzel po uzlu přes XmlWriter do stringu, a ten je pak celý načten do DOM struktury. Proč to, když by jistě bylo mnohem jednodušší a také výkonnější využít přetíženou metodu XmlDocumentu Load, jejíž jedna varianta přímo čte z XmlReaderu? Tedy jednoduše takto:

  data.Load(reader);

Je to proto, že XmlDocument není z mi neznámého důvodu se SgmlReaderem zcela kompatibilní. Zkuste a uvidíte. XmlDocument zřejmě neuzná posloupnost načtených XML uzlů za správnou. Možná jsem něco přehlédl, a problém lze vyřešit jinak, ovšem spolehlivě mi fungoval pouze dotyčný obchvat přes string.

Nyní naši metodu použijeme k načtení stránky z Intervalu a jejímu předání do Xml komponenty data. Tuto akci provedeme v těle oblíbené metody Page_Load.

private void Page_Load(object sender, System.EventArgs e)
{
  data.Document = GetPage(„http://interval.cz/“);
}

Transformace

Konečně jsme v poslední etapě projektu. Stránku máme ve snadno zpracovatelném tvaru, napsání XSL transformace je již trivialita. Webová stránka je nyní vydána na milost (či nemilost) vašim XPath výrazům. Je jen na Vás, kolik informací z ní zvládnete vydolovat. Následující příklad generuje jen pár základních RSS elementů, ovšem vy samozřejmě můžete jít dále a vytvořit pro svou RSS čtečku poněkud komplexnější stravu.

<?xml version=“1.0″ encoding=“UTF-8″ ?>
<xsl:stylesheet version=“1.0″
  xmlns:html=“http://www.w3.org/1999/xhtml“
  xmlns:xsl=“http://www.w3.org/1999/XSL/Transform“
  exclude-result-prefixes=“xsl html“>
  <xsl:output method=“xml“ indent=“yes“ />
  <xsl:template match=“/“>
    <rss version=“2.0″>
      <channel>
        <title>
          <xsl:value-of select=“//html:title“ />
        </title>
        <xsl:for-each
          select=“//html:div[@class=’anotation‘]“>
          <item>
            <title>
              <xsl:value-of
                select=“.//html:h2″ />
            </title>
            <link>
              <xsl:text>http://interval.cz/</xsl:text>
              <xsl:value-of
                select=“.//html:h2/html:a/@href“ />
            </link>
            <description>
              <xsl:value-of
                select=“.//html:p“ />
            </description>
          </item>
        </xsl:for-each>
      </channel>
    </rss>
  </xsl:template>
</xsl:stylesheet>

K dispozici jsou vám kompletní zdrojové kódy aplikace.

Odkazy a zdroje

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

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

Odpovědět