Dokumentum-összeszerelés (2. rész)
Haladok tovább az első részben megkezdett feladattal: írok egy kis konzolalkalmazást, amely
- lekérdezi az SQL Servertől a Northwind mintaadatbázis Customers táblájának rekordjait
- az egyes rekordokat XML formátumúvá alakítja az előző cikkben készített dokumentum igényeinek megfelelően
- bemásolja az XML-t a mintadokumentum másolatába, és megadott néven és helyre menti az új fájlt
Hogy izgalmasabb legyen, az Open XML mellett a LINQ (Language Integrated Query) technológiát is használom. A megoldáshoz ennélfogva a Visual Studio 2008-as verziójára van szükség.
CreateLetters.exe
Hozzunk létre egy Visual Basic konzolalkalmazást, és kereszteljük CreateLetters névre. A Main() eljárást írjuk át úgy, hogy két paramétert fogadjon (a sablonfájl elérési útja, a kész dokumentumok mappájának elérési útja), vagy írja ki a használat mikéntjét:
Sub Main(ByVal CmdArgs() As String)
If CmdArgs.Length = 2 Then
' Ide jön a levelek létrehozása
Else
Console.WriteLine( _
"Használat: CreateLetters <sablon> <célmappa>" & _
", pl. CreateLetters C:\Sablon.docx C:\Levelek")
End If
End Sub
Az ügyfelek listájának lekérdezése
A LINQ hasonlít a többi Visual Studio-beli adatelérési megoldáshoz. A különbség abban van, ahogy az adatkezelő műveletek a programkódban megjelennek. Az ügyféltábla lekérdezésének lépései:
A Server Explorerben vagy a Data / Add New Data Source... paranccsal adjuk hozzá a Northwind adatbázis Customers tábláját a projekthez.
A Solution Explorerben vagy a Project / Add New Item... paranccsal adjunk a projekthez egy LINQ to SQL Classes nevű elemet, és nevezzük el Customer-nek. A Solution Explorerben megjelenik egy Customer.dbml fájl, és megnyílik az Object Relational Designer nevű tervezőfelület.
A Server Explorerből húzzuk a Customers táblát a tervezőfelület bal oldalára, ahol létrejön egy Customer nevű osztály. A többes számból egyes számba alakítás nem véletlen, ez az osztály egy konkrét ügyfelet testesít meg. (Nem ér megkérdezni, hogy az "Ügyfelek"-ből is "Ügyfél" lesz-e.) Mentsük a projekt fájljait.
Most lekérdezhetjük az ügyfelek listáját. "Vegytiszta" SQL-t használva ezt pl. a következőképpen tehetnénk meg:
SELECT CustomerID, CompanyName, ContactName, ContactTitle FROM Customers
A LINQ-kel a fenti lekérdezést az alábbi módon írhatjuk le (a kódot a Main() eljárás If ágába, a megjegyzés helyére szúrjuk be):
Dim context As New CustomerDataContext
Dim customers = _
From c In context.Customers _
Select _
c.CustomerID, c.CompanyName, _
c.ContactName, c.ContactTitleA kapott customers gyűjtemény tartalmazza valamennyi ügyfél adatait. A levelek előállításához ennek elemein fogunk végigmenni a következő ciklusban:
For Each customer In customers
' Ide jön az egyes levelek összeállítása
Next
Az XML adatfájl összeállítása
A LINQ segítségével az XML-készítés is leegyszerűsödik, méghozzá egyetlen lépésre. Az alábbi kódot írjuk be a ciklus belsejébe:
Dim customerData As XElement = _
<CustomerData>
<CustomerID><%= customer.CustomerID %></CustomerID>
<CompanyName><%= customer.CompanyName %></CompanyName>
<ContactName><%= customer.ContactName %></ContactName>
<ContactTitle><%= customer.ContactTitle %></ContactTitle>
</CustomerData>
Feltűnő a hasonlóság az ASP.NET-tel...
Új dokumentum létrehozása a sablon alapján
A parancssorban kapott első paraméter a sablonfájl elérési útja, a második a mappáé, ahova a leveleket gyűjtjük. A kövtkező lépésben lemásoljuk a sablont, az új fájl neve az ügyfélazonosítóból származik (példa: ALFKI.docx). Az alábbi kód is a ciklus belsejébe megy:
My.Computer.FileSystem.CopyFile( _
CmdArgs(0), _
CmdArgs(1) & "\" & customer.CustomerID & ".docx", True)
A dokumentumban lévő XML adatfájl felülírása
Most jött el az ideje, hogy az Open XML csomaggal foglalkozzunk. A teendők (az eleje egy korábbi bejegyzésemből már ismerős lehet):
Töltsük le az Open XML SDK-t erről a címről, és telepítsük.
Adjunk a projekthez egy új referenciát: a .NET fülön a Microsoft.Office.DocumentFormat.OpenXML bejegyzést keressük.
A modul tetején hivatkozzuk meg ezt és a System.IO-t:
Imports _
Microsoft.Office.DocumentFormat.OpenXml.Packaging
Imports System.IOA ciklus belsejében (mostantól minden kód ide kerül) nyissuk meg az újonnan lemásolt dokumentumot mint csomagot:
Dim document As WordprocessingDocument = _
WordprocessingDocument.Open( _
CmdArgs(1) & "\" & customer.CustomerID _
& ".docx", True)Keressük meg a fő dokumentumrészt és az egyedi adatokat (az XML-t) tartalmazó részt:
Dim mainPart As MainDocumentPart = _
document.MainDocumentPart
Dim dataPart As CustomXmlPart = _
mainPart.CustomXmlParts(0)A következők kicsit bonyolultnak tűnhetnek, de ez a memóriában tárolt fájlok kezelésének jellegzetessége. Egy adatfolyam-objektumot (stream) nyitunk - ez gyakorlatilag az XML fájl. A folyam módosításához egy író objektumot (writer) használunk. Amikor ezt kiürítjük (flush), az új XML fájl bekerül a DOCX dokumentumba; mentésre nincs szükség:
Dim stream As Stream = dataPart.GetStream( _
FileMode.Create, FileAccess.ReadWrite)
Dim writer As StreamWriter = New StreamWriter(stream)
writer.Write(customerData.ToString)
writer.Flush()
Kész a fejlesztés: mentsük a projektet, majd építsük meg az alkalmazást.
Próba, szerencse
Itt az ideje, hogy megírjunk néhány levelet! Másoljuk az első részben készített dokumentumot a kívánt helyre (a C:\ is tökéletesen megfelel), és hozzunk létre egy mappát a termésnek (legyen pl. C:\Levelek). Most futtassuk az alkalmazást a következő paranccsal:
CreateLetters C:\Ügyfélértesítő.docx C:\Levelek
Ha semmit sem rontottunk el (ez fontos, hiszen a rövidség kedvéért kihagytam a hibakezelést :-)), a Levelek mappában néhány másodperc múlva 91 db Word 2007-dokumentum jelenik meg, mindegyikben a Customers tábla egy-egy rekordjának megfelelő adatok.
De a legjobb rész most jön: minden további nélkül átalakíthatjuk a sablonként szolgáló dokumentumot, a megoldás helyesen fog működni. Igazából semmit nem ronthatunk el, a tartalomvezérlők törlését ugyanis korábban megtiltottuk. Íme egy áttervezett dokumentum, ahol a mezők sorrendje és az oldal grafikai megvalósítása egyaránt új:
Vége (vagy mégsem?)
Az Open XML nemcsak a dokumentumok szövegének és grafikai elemeinek bővítését és cseréjét teszi lehetővé, hanem külső adatforrások alapján, dinamikusan történő előállításukat is. És mindezt minimális programozási igény mellett. Érdemes az új fájlformátummal és a hozzá tartozó SDK-val kísérletezni; lehet, hogy én is írok még néhány további lehetőségről itt a blogban.