Tag Archives: Office

Word dokumentum generálása adatkötéssel – 5. rész: A kód neve

Az előző részben láttuk, hogyan kapcsolódik a DOCX állományban lévő customXml part kétirányú adatkötéssel a content control vezérlőkhöz. Ha ezzel a módszerrel akarunk programozottan dokumentumot előállítani, akkor már csak egyetlen lépés van hátra: olyan kódot kell írnunk, amely belenyúl az Open XML állományba és felülírja a korábban már odatett XML partot, aminek tartalma azután az adatkötéseken keresztül automatikusan megjelenik a felhasználó számára.

A feladat megoldásában a System.IO.Packaging névtér nagy segítségünkre lesz. A Package osztály segítségével meg tudjuk nyitni a DOCX fájlt ZIP tudomány nélkül, csak az elérési útját kell ismernünk:

    // A sablon dokumentum megnyitása.
Package package = Package.Open( path, FileMode.Open, FileAccess.ReadWrite );

A következő lépés az XML part megkeresése. Az egyes part-okra a csomagon belüli URI-jük segítségével hivatkozhatunk. A Word 2007 Content Control Toolkit által generált Item1.xml itt található:

    // Az XML part útvonala a sablon dokumentumban.
    Uri partUri = new Uri( "/customXml/Item1.xml", UriKind.Relative );

Lekérdezhetjük, hogy egyáltalán létezik-e ilyen part a dokumentumban:

    // Ellenőrizzük, hogy a sablonban van-e XML part.
    if( package.PartExists( partUri ) )
    {
        // itt folytatjuk...

Ha létezik, akkor el is kérhetjük, mégpedig egy PackagePart formájában:

    // Az XML part elkérése.
    PackagePart xmlPart = package.GetPart( partUri );

Az XML part tartalmát streamként tudjuk elérni. Ha például egy content nevű változóban megtalálható a beírandó XML UTF-16 formátumban, akkor annak beírására használhatjuk például az alábbi kódot:

    // Az XML part tartalmát kezelő stream elkérése.
    using( Stream xmlStream = xmlPart.GetStream() )
    {
        // A stream hosszának beállítása, hogy rövidebb szöveg esetén a régi tartalom levágódjon.
        xmlStream.SetLength( content.Length );

        // Az új tartalom beírása a streambe. Unicode kell, mert az XML stringben utf-16 szerepel.
        using( StreamWriter writer = new StreamWriter( xmlStream, Encoding.Unicode ) )
        {
            writer.Write( content );
            writer.Flush();
            // writer.Close(); automatikus a Dispose miatt.
        }

        // Az XML part mentése.
        // xmlStream.Close(); automatikus a Dispose miatt.
    }

Nincs is más hátra, mint a dokumentum módosításait elmenteni a diszkre:

    // A dokumentum változásainak mentése.
    package.Flush();
    package.Close();

Miért vacakoltam az UTF-16 kódolással? Azért, mert a beírandó XML előállítására szerintem az a legegyszerűbb megoldás, ha készítünk például egy Contact osztályt, annak beállítjuk úgy a tulajdonságait, ahogy azokat a Word dokumentumban látni szeretnénk, majd egyszerűen XmlSerializer segítségével kisorosítjuk:

    XmlSerializer serializer = new XmlSerializer( typeof( Contact ) );

    using( StringWriter writer = new StringWriter( CultureInfo.InvariantCulture ) )
    {
        serializer.Serialize( writer, this );
        result = writer.ToString();
        // writer.Close(); automatikus a Dispose miatt.
    }

Mivel itt StringWritert használok, az eredmény a .NET Framework sztringjeinek alapértelmezett kódolása, azaz "utf-16" lesz, amivel tapasztalataim szerint a Word nem birkózik meg. Persze biztosan lehet egyszerűbben is…

Egyetlen dologra hívnám fel a figyelmet – azon kívül természetesen, hogy a fenti kód a probléma megoldásának igen egyszerű módja: kódból módosítunk egy Word dokumentumot, de mindezt úgy tesszük, hogy nem használjuk hozzá a Word objektum modelljét. Sőt, nem használjuk a Word egyik komponensét sem, azaz a fenti kód működik akkor is, ha nincs Word a gépen! Ez igen fontos fegyvertény az Open XML mellett, hiszen aki próbált már szerver oldalon Office dokumentumot előállítani az biztosan belefutott abba a problémába, hogy az Office-t nem kiszolgáló oldali automatizálásra tervezték. Ez még a 2007-es verzióra is igaz, de nem az Open XML-re!

Ezt a tudásunkat felhasználva akár a SharePointot is kiegészíthetjük, készíthetünk például egy olyan új funkciót a Névjegyalbum listákba, amely lehetővé teszi a névjegy lista elemek alapján megcímzett, fejléces levelek egy kattintással történő előállítását. Ehhez nem kell mást tennünk, mint…

(folytatjuk)

Advertisements

Word dokumentum generálása adatkötéssel – 4. rész: Kapcsolat

Ahogy az előző részben láttuk, a content control-ok vagy más néven a structured document tags szolgáltatás az Office dokumentumokban lehetővé teszi a formázás és az adatok elkülönítését. Két korlátozó körülményt azonban mindenképpen meg kell említenünk:

  1. Ez a szolgáltatás egyelőre csak a Word 2007-ben érhető el, az Office család más termékeiben nincs ilyen lehetőség.
  2. Elég fájó, de sajnos nincs táblázat vezérlő, sőt az ismétlődéseket (pl. felsorolásokban) sem tudja kezelni ez a funkció. Mivel ez sokaknak fáj, ezért várhatóan az Office 14-ben vagy talán már egy javítócsomagban megkapjuk ezt is.

Nem beszéltünk még arról, hogy hol tárolódnak az adatok és hogyan kapcsoljuk azokat a vezérlőkhöz. Az adatok a DOCX állományon belül egy önálló XML fájlban tárolódnak, alapértelmezés szerint a customXml mappában.Itt nyers XML-t kell elképzelni, semmi Open XML specifikus elem nincs benne, az viszont praktikus, ha van egy önálló, egyedi névtere. Ha például a dokumentumunk egy levél, amelyben a címzett és a feladó adatait programozottan akarjuk kitölteni, akkor a szükséges adatokat betehetjük a customXmlitem1.xml fájlba az alábbi formában:

    <?xml version="1.0" encoding="utf-16"?>
    <Contact xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                     xmlns="http://www.msdnkk.hu/samples/Contact">
        <FirstName>György</FirstName>
        <LastName>Balássy</LastName>
        <Company>MSDN Kompetencia Központ</Company>
        <City>Budapest</City>
        <Zip>1117</Zip>
        <Address>Magyar Tudósok krt. 2.</Address>
        <Sender>Feladó Ferenc</Sender>
    </Contact>

Ennek az XML fájlnak az írása és olvasása megoldható a korábban már említetett Packaging API segítségével, hiszen ez is egy Open XML document part.

Hogyan kapcsoljuk ezt a fájlt a csomag többi részéhez? A word_relsdocument.xml.rels fájlt kell kiegészítenünk az alábbiakkal:

    <?xml version="1.0" encoding="utf-8"?>
    <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
        <!-- Sok Relationship elem van itt már... -->
        <Relationship 
            Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml" 
            Target="../customXml/item1.xml" 
            Id="R0fbcbf7dffdc49b6" />
    </Relationships>

A [Content_Types].xml fájlhoz nem kell nyúlnunk, mert abban már szerepel egy definíció, amely magában foglal minden XML kiterjesztésű állományt. Kiegészíthetjük viszont egy XML properties résszel:

    <?xml version="1.0" encoding="utf-8" ?>
    <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
        <Default Extension="xml" ContentType="application/xml" />
        <!-- A dokumentum többi része itt Override elemekben -->
        <Override 
            PartName="/customXml/itemProps1.xml" 
            ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml" />
    </Types>

Az XML properties fájlban további tulajdonságokat adhatunk meg, például egy ún. data store ID-t:

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <ds:datastoreItem 
        ds:itemID="{70372b4c-0b6e-4f5d-9fa2-39118fe7a889}" 
        xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" />

Ha több customXml partunk is van, akkor még egy ds:schemaRefs elem segítségével összekapcsolhatjuk a data store-t egy adott XML sémával. Ennek akkor van jelentősége, ha egyébként a dokumentumban több azonos sémájú XML part is van, mert így meg tudjuk adni, hogy pontosan melyikhez kívánunk kapcsolódni.

A tárolást tehát megoldottuk, következő probléma, hogy az egyes vezérlőket hogyan kapcsoljuk az XML fájlban lévő elemek valamelyikéhez. A megoldás a cikksorozat címében oly sokszor említett adatkötés. Nem is akármilyen, hanem kétirányú! Tehát akár kódból módosítjuk az XML állományt, akár a felhasználó ad Wordben új értéket valamelyik vezérlőnek, a másik automatikusan frissül, ezzel semmilyen további munkánk nincsen!

Az adatkötés megadása a worddocument.xml fájlban történik az egyes w:sdtPr elemekbe ágyazott w:dataBinding elemekkel. Itt két dolgot kell megadnunk:

  1. Egy névtér aliast a w:prefixMappings attribútumban.
  2. A névtér aliast használó és az XML dokumentumban egy elemet azonosító XPath kifejezést a w:xpath attribútumban.
  3. Ha több azonos névterű XML partunk is van, akkor még egy data store ID-t, ami alapján a Word megtalálja majd az XML partot. Ezt a w:storeItemID attribútumban lehet megadni.

Végül például egy Keresztnév  értéket bekérő vezérlő, amely a fent bemutatott XML dokumentum FistName mezőjéhez van kötve így festhet:

    <w:sdt>
        <w:sdtPr>
            <w:dataBinding 
                w:prefixMappings="xmlns:ns0='http://www.msdnkk.hu/samples/Contact'" 
                w:xpath="/ns0:Contact[1]/ns0:FirstName[1]" 
                w:storeItemID="{70372b4c-0b6e-4f5d-9fa2-39118fe7a889}" />
            <w:rPr>
                <w:i />
            </w:rPr>
            <w:alias w:val="Keresztnév" />
            <w:tag w:val="Keresztnev" />
            <w:id w:val="370331103" />
            <w:placeholder>
                <w:docPart w:val="DefaultPlaceholder_22675703" />
            </w:placeholder>
            <w:text />
        </w:sdtPr>
        <w:sdtContent>
            <w:r w:rsidR="00136851" w:rsidRPr="00136851">
                <w:rPr>
                    <w:i />
                </w:rPr>
                <w:t>Keresztnév</w:t>
            </w:r>
        </w:sdtContent>
    </w:sdt>

A jó hír az, hogy mindezt nem kell kézzel odaheggesztenünk minden egyes vezérlőhöz. Bár sem a Wordben, sem pedig a Visual Studioban nincs erre eszköz, szerencsére a CodePlexről letölthetjük a Word 2007 Content Control Toolkitet, ami sokat segíthet a munkában. Egyetlen képernyős alkalmazásról van szó és mivel Windows Formsban készült, kiválóan olvasható Reflectorral (megjegyzem tanulni is lehet belőle, például nagyon érdekes, hogyan vezérli ez a külső alkalmazás a Word kurzorát):

Content Control Toolkit

Az alkalmazás használata nagyon egyszerű:

  1. Készítsük el Wordben a sablon dokumentumot és tegyük bele a vezérlőket a Developer fülről.
  2. Töltsük be a Word dokumentumot a Content Control Toolkit (CCT) eszközbe, ami után bal oldalon megjelennek a vezérlők.
  3. A jobb alsó Create a new Custom XML Part linkre kattintva adjunk új XML partot a dokumentumhoz.
  4. Készítsük el azt az XML dokumentumot például Visual Studioban, amit egyszerűen elő tudunk állítani később kódból, és amilyen formátumú fájlt majd a DOCX állományba szeretnénk helyezni.
  5. Másoljuk be az XML tartalmát az Edit View fülön lévő mezőbe.
  6. Váltsunk vissza Bind View nézetre, ahol megjelenik az XML hierarchia.
  7. Jelöljük ki a bal oldali listában valamelyik vezérlőnket (ez kell), majd a jobb oldali hierarchiában fogjuk meg a hozzá illő XML elemet és dobjuk rá a vezérlőre. Ekkor elkészül az adatkötés. Ha inkább kézzel szerkesztenénk a paramétereken, akkor kattintsunk duplán bármelyik vezérlő sorára és a felbukkanó ablakban adjuk meg az adatokat.
  8. Végül ne felejtsük el menteni a dokumentumot.

Ezzel még csak az adatkötés paraméterezése készült el, illetve az eszköz megoldja azt is, hogy a DOCX állományban létrejöjjön egy XML part. Ami még hátra van, az a DOCX állományban lévő XML tartalmának módosítása kódból. De ezt majd legközelebb, addig is ezt érdemes kipróbálni!

(folytatjuk)

Word dokumentum generálása adatkötéssel – 3. rész: Kontroll

Bár a Word 2007 preferált fájlformátuma a DOCX teljes mértékben nyitott és XML alapú, elmondhatjuk, hogy a tartalom programozott közvetlen előállítása mégsem igazán egyszerű, mert az adat és a megjelenítés erősen keveredik a WordML nyelvben. Már a Word 2003-ban megjelent az a lehetőség, hogy a dokumentumhoz XSD sémát rendelhetünk, amely alapján a szöveg egyes részeit nyers XML-ben exportálhatjuk, de sajnos ez nem teszi lehetővé a tartalom generálását, ahogyan azt korábban láttuk.

Szerencsére ezen kívül van egy harmadik XML alapú lehetőség is, amely a Word 2007-tel vált elérhetővé.

2. Structured document tags

A Word 2007 vezette be az ún. content control vezérlőket vagy más néven a structured document tag-eket, azaz SDT-ket (ez borzasztóan magyartalanul hangzik, de sajnos nem tudom, hogy mi lett a magyar terminológia 😦 ). A módszer lényege, hogy a dokumentum egyes részeibe nem csak passzív helyőrzőket, hanem aktív vezérlőket teszünk, melyek tartalma a dokumentumban tárolt (ugyebár ZIP fájlról van szó) XML fájlhoz kapcsolódik.

Ha bekapcsoljuk a Developer fület, a Controls csoportban találjuk ezeket a vezérlőket:

Vezérlők a Developer fülön

Összesen 7 féle létezik:

  • Rich Text
  • Text
  • Picture
  • Combo Box
  • Drop-Down List
  • Date Picker
  • Building Block Gallery

A nevek magukért beszélnek. Ha ugyanebben a csoportban átkapcsolunk Design Mode-ba, beszúrhatjuk ezeket a vezérlőket a dokumentumunk szövegébe. Például ha szükséges, hogy a felhasználók megadjanak egy dátumot, amely mondjuk egy szerződés keltét jelzi, beszúrhatunk egy Date Picker vezérlőt, amely így fog megjelenni:

Date Picker

Fontos tehát, hogy aktív vezérlőről van szó, amely nem csak a fejlesztők, hanem a felhasználók számára is megkönnyíti a munkát. A szalagon a Properties gombra kattintva még egy beállító ablakot is kapunk:

Date Picker Properties

Két fontos opció elérhető minden vezérlőnél:

  • A Title mezőben megadott érték megjelenik a felhasználóknak, közvetlenül a vezérlő felett, ezt tehát érdemes barátságosan kitölteni:

Title értéke

  • A Tag mezőben megadott érték a felhasználói felületen nem jelenik meg, ez nekünk fejlesztőknek lehet hasznos.

Az így beállított vezérlő egy w:sdt elem formájában mentődik el a document.xml fájlba:

    <w:sdt>
        <w:sdtPr>
            <w:rPr>
                <w:lang w:val="en-US"/>
            </w:rPr>
            <w:alias w:val="Szerződés dátuma"/>
            <w:tag w:val="SzerzodesDatum"/>
            <w:id w:val="107276272"/>
            <w:placeholder>
                <w:docPart w:val="DefaultPlaceholder_22675705"/>
            </w:placeholder>
            <w:showingPlcHdr/>
            <w:date>
                <w:dateFormat w:val="yyyy.MM.dd."/>
                <w:lid w:val="hu-HU"/>
                <w:storeMappedDataAs w:val="dateTime"/>
                <w:calendar w:val="gregorian"/>
            </w:date>
        </w:sdtPr>
        <w:sdtContent>
            <w:r w:rsidRPr="005B7BC2">
                <w:rPr>
                    <w:rStyle w:val="PlaceholderText"/>
                    <w:lang w:val="en-US"/>
                </w:rPr>
                <w:t>Click</w:t>
            </w:r>
            <w:proofErr w:type="gramEnd"/>
            <w:r w:rsidRPr="005B7BC2">
                <w:rPr>
                    <w:rStyle w:val="PlaceholderText"/>
                    <w:lang w:val="en-US"/>
                </w:rPr>
                <w:t xml:space="preserve"> here to enter a date.</w:t>
            </w:r>
        </w:sdtContent>
    </w:sdt>

A w:sdtPr a properties, azaz a tulajdonságokra vonatkozó rész, a w:sdtContent pedig a felhasználó által megadott tartalom. A tulajdonságok között megtaláljuk a korábban megadott alias és tag értékeket. Itt található még a w:showingPlcHdr elem is, amely megmutatja, hogy a w:sdtContent részben nem a felhasználó által megadott érték, hanem még az alap helyőrző, placeholder szerepel. A w:date elembe a Date Picker vezérlőre vonatkozó egyedi beállításokat menti a Word.

Az w:sdtContent elem most elég bonyolultnak tűnik, de már értjük, hogy miért: run elemeket tartalmaz, mert arra lehet formázási beállításokat megadni. Ha a felhasználó kiválaszt egy dátumot, mindez sokkal átláthatóbb lesz, marad a nyers text:

    <w:sdtContent>
        <w:r w:rsidR="001B1478">
            <w:t>2008.04.15.</w:t>
        </w:r>
    </w:sdtContent>

Mi az, amit tehát ezzel elértünk? Definiáltuk, hogy a dokumentum fontos részein milyen formátumú tartalomnak kell megjelennie és a felhasználó által megadott adatot úgy perzisztáltuk WordML-ben, hogy az a megjelenítéstől teljesen független, ráadásul a korábbiaknál egyszerűbben található meg programozottan a dokumentumban.

A jó hír az, hogy még arra sincs szükség, hogy a Word által generált XML-ben w:sdt elemeket keresgéljünk, ugyanis ezeknek a vezérlőknek a tartalmát adatkötésen keresztül is elérhetjük.

(folytatjuk)

Word dokumentum generálása adatkötéssel – 2. rész: Új remény

Ahogy az előző részben megállapítottuk, a Word 2007 alapértelmezett Open XML fájl formátumainak programozott módon történő közvetlen kezelése nem éppen kellemes élmény. Bár kapunk támogatást a fájl kibontásához és a részek eléréséhez, sőt az egyes részek tartalmát is tudjuk szerkeszteni, ez utóbbinak a megalkotása során váratlan akadályokba ütközhetünk. Mindennek pedig az az oka, hogy a Word ML nyelvben a tartalom, azon belül is a sok sallang között a dokumentum információtartalma és a megjelenítés bizony elég erősen keveredik.

A szétválasztásra szerencsére van lehetőség, és a megoldás lelke természetesen az XML.

1. Custom XML

Ha már a Word XML támogatása szóba került, mindenképpen meg kell említenünk a Custom XML szolgáltatást, amely már megvolt a Word 2003-ban is és lényegében nem is változott a 2007-es verzióban. Ennek a funkciónak a lényege, hogy a dokumentumunkhoz hozzárendelünk egy XSD sémát, majd definiáljuk, hogy melyik szövegrész melyik XML elemnek felel meg. Végül a Word Mentés ablakában kiválasztjuk, hogy nem az egész dokumentumot akarjuk menteni Word formátumban címestül, bekezdésestül, formázásostul, hanem csak az információk alapján előállított nyers XML-t.

Ennek a szolgáltatásnak tehát az a célja, hogy a szövegből ki tudjuk nyerni a lényegi információt, mellőzve minden körítést. A módszer sajnos nem alkalmas dokumentum előállítására, a mentéskor előálló XML és az eredeti dokumentum között nincs kapcsolat és nem tudok olyan módszerről, ami lehetővé tenné, hogy egy adott XML tartalmát jelenítse meg a Word a sablon dokumentumunkban. Krisztián számára viszont jó hír, hogy tökéletesen kezeli a táblázatokat 😉

Vegyük például az alábbi dokumentumot, amely egy tanfolyami ajánlatot tartalmaz:

Egy általános Word dokumentum

Ha az a feladatunk, hogy ebből kellene kinyernünk az oktató nevét, óraszámot illetve az érintett témákat, akkor használhatjuk a Custom XML szolgáltatást. Ehhez először készítenünk kell egy XSD sémát. Bevallom nem sűrűn gyártok sémát, az XSD szintaktika nem megy fejből, ezért inkább gyártottam egy XML dokumentumot Visual Studioval, pont olyan felépítésűt, amire szükségem van a Word feldolgozásnál is:

    <?xml version="1.0" encoding="utf-8"?>
    <Tanfolyam>
        <Oktato>Oktató neve</Oktato>
        <Oraszam>111</Oraszam>
        <Tartalom>
            <Tema>
                <Cim>Elsé téma</Cim>
                <Szint>könnyű</Szint>
            </Tema>
            <Tema>
                <Cim>Második téma</Cim>
                <Szint>közepes</Szint>
            </Tema>
            <Tema>
                <Cim>Harmadik téma</Cim>
                <Szint>nehéz</Szint>
            </Tema>
        </Tartalom>
    </Tanfolyam>

Miután ez megvolt, megkértem a Studiot, hogy generáljon ebből XSD-t:

VS: Create Schema

Íme az eredmény, amit így nem kellett megírnom:

    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema attributeFormDefault="unqualified" 
elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Tanfolyam"> <xs:complexType> <xs:sequence> <xs:element name="Oktato" type="xs:string" /> <xs:element name="Oraszam" type="xs:unsignedByte" /> <xs:element name="Tartalom"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" name="Tema"> <xs:complexType> <xs:sequence> <xs:element name="Cim" type="xs:string" /> <xs:element name="Szint" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>

Megvan tehát a séma, irány a Word! Először is elő kell varázsolnunk a szalagra a Developer fület, amit a Word options ablakban tehetünk meg:

Word options

Ha megjelent a Developer tab, bökjünk rá az XML csoportban található Structure gombra, majd a megjelenő jobb oldali XML Structure munkaablakban a Templates and Add-Ins… linkre:

XML Structure munkaablak

A felugró Templates and Add-Ins ablakban az Add Schema gombra kattintva válasszuk ki a korábban előállított XSD fájlunkat és adjuk meg a hozzá tartozó névtér URI-t. Ez lesz a Word által generált XML dokumentum alapértelmezett névtere, tehát jól gondoljuk ki, mit adunk meg:

Templates and Add Ins ablak

Innentől kezdve nincs is más dolgunk, mint a szöveg egyes részeit kijelölni és a munkaablak alsó részében rábökni az alkalmazandó XML elem nevére, aminek hatására lila jelölők tűnnek fel a dokumentumban, ha a Show XML tags in the document opciót bekapcsoljuk:

XML séma alkalmazása

Az ablak legalsó opcióját érdemes bekapcsolva hagyni: a List only child elements of current element azt jelenti, hogy a Word követi merre jár a kurzorunk a dokumentumban és csak azokat az elemeket kínálja fel, amik a séma alapján ott elérhetőek. Ez sokat segít.

Célszerű figyelni a munkaablak felső részében a sárga bejegyzéseket, amelyek felett tooltipben megjelenik, ha valamelyik elemet nem a sémának megfelelően használjuk.

Hasznos beállítási lehetőségeket találunk, ha a legalsó XML Options linkre kattintunk:

XML options

A Save data only bekapcsolásával érhetjük el, hogy csak az adatok mentődjenek, a formázás ne, sőt megadhatunk egy XSL transzformációt is, amit mentésnél alkalmaz a Word.

A validálási lehetőségek között szerintem célszerű bekapcsolni az Ignore mixed content opciót. Ezt bekapcsolva a Word eltekint az általunk XML elemként megjelölt részek közötti egyéb szövegtől. E nélkül a sémában a szabadszöveges részeket is definiálnunk kellene.

Érdemes kipróbálni, hogy a Word felismeri a táblázatokat, így ha később a táblázatba újabb sorokat szúrunk be, azokra is alkalmazni fogja az előző sor XML séma beállításait. Ha bekapcsoljuk a Show placeholder text for all empty elements opciót, akkor még az is látszik messziről, hogy a szöveg mely részét kell kitöltenie a felhasználónak (ez persze nem csak táblázatnál működik):

Táblázat és placeholder

Ha készen vagyunk a dokumentum felcímkézésével el is menthetjük és odaadhatjuk a felhasználóknak. Miután ők megszerkesztik a szöveget csak arra kell figyelniük, hogy a Save As dialógus ablakban Word 2003 XML Document típust válasszanak ki és jelöljék be a Save data only opciót:

Save As

Fontos, hogy Word 2003 XML Document a típus neve és nem 2007! Íme az eredmény:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <Tanfolyam xmlns="www.msdnkk.hu">
        <Oktato>Balássy György</Oktato>
        <Oraszam>24</Oraszam>
        <Tartalom>
            <Tema>
                <Cim>Bevezetés a SharePoint objektum modell használatába</Cim>
                <Szint>könnyű</Szint>
            </Tema>
            <Tema>
                <Cim>SharePoint webszolgáltatások</Cim>
                <Szint>könnyű</Szint>
            </Tema>
            <Tema>
                <Cim>Alkalmazás oldalak</Cim>
                <Szint>közepes</Szint>
            </Tema>
            <Tema>
                <Cim>Feature Framework</Cim>
                <Szint>közepes</Szint>
            </Tema>
            <Tema>
                <Cim>nincs címe</Cim>
                <Szint>nincs szintje</Szint>
            </Tema>
            <Tema>
                <Cim>Eseménykezelők</Cim>
                <Szint>közepes</Szint>
            </Tema>
            <Tema>
                <Cim>Webkijelzők készítése</Cim>
                <Szint>közepes</Szint>
            </Tema>
            <Tema>
                <Cim>Munkafolyamatok fejlesztése</Cim>
                <Szint>nehéz</Szint>
            </Tema>
        </Tartalom>
    </Tanfolyam>

Innen már aztán gyerekjáték akármilyen alkalmazásban feldolgozni az adatokat, hiszen ez standard XML, semmilyen formázást, Word ML-t vagy egyéb Word specifikus részt nem tartalmaz. Aki nem hiszi, innen letöltheti a cikkhez tartozó fájlokat. Persze az igazán szép az lenne, ha rá tudnánk kényszeríteni a felhasználókat, hogy mindig ebben a formátumban mentsenek, én erre sajnos nem találtam módszert 😦 Ha valaki tud, írja meg!

Sajnos ahogy azt korábban említettem, ezzel az XML-es módszerrel nem tudunk dokumentumot generálni. Csak arra jó, hogy a szövegből kinyerjük az adatokat, tudomásom szerint nem lehet megfordítani a folyamatot. Szerencsére van egy másik, szintén XML alapú szolgáltatás a Wordben, ami nem csak a dokumentum előállítását, de az adatkötésen alapuló generálást is biztosítja.

(folytatjuk)

 

Word dokumentum generálása adatkötéssel – 1. rész: A fáraó átka

Gyakori feladat, hogy valamely alkalmazásunkból Office dokumentumot kell előállítanunk. Mivel az Office 2007 alapértelmezett fájl formátuma, az Office Open XML éppen ebben a hónapban kapta meg a szükséges szavazatokat, hogy az ECMA (ECMA 376, 2006. december) után ISO szabvánnyá is válhasson, aktuális a téma!

Az egyszerűség kedvéért koncentráljunk a Wordre. Talán már köztudott, hogy az új DOCX fájl valójában egy ZIP állomány, benne sok apró fájllal, melynek többsége XML. Ki is próbálhatjuk, készítsünk például egy igen egyszerű dokumentumot:

Példa dokumentum

Mentsük el a dokumentumot mondjuk Szia.docx néven, majd nevezzük át Szia.docx.zip-re és akár a Windows segítségével csomagoljuk ki. Elég sok fájlt fogunk találni benne, hogy a lényegre térjek, a dokumentum szövege a worddocument.xml fájlban található:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <w:document xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                            xmlns:o="urn:schemas-microsoft-com:office:office" 
                            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" 
                            xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" 
                            xmlns:v="urn:schemas-microsoft-com:vml" 
                            xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" 
                            xmlns:w10="urn:schemas-microsoft-com:office:word" 
                            xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" 
                            xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
        <w:body>
            <w:p w:rsidR="00A268F4" w:rsidRDefault="00632D74">
                <w:r>
                    <w:t xml:space="preserve">Szia Word 2007! </w:t>
                </w:r>
            </w:p>
            <w:sectPr w:rsidR="00A268F4" w:rsidSect="00E20B73">
                <w:pgSz w:w="11906" w:h="16838"/>
                <w:pgMar w:top="1417" w:right="1417" w:bottom="1417" w:left="1417" w:header="708" w:footer="708" w:gutter="0"/>
                <w:cols w:space="708"/>
                <w:docGrid w:linePitch="360"/>
            </w:sectPr>
        </w:body>
    </w:document>

A lényeg ott van középen, az xml:space=preserve attribútumnak köszönhetően még az utolsó szóköz is megmaradt.

A .NET Framework 3.0 verziójától kezdve van lehetőségünk arra, hogy ezt a tömörített fájlt, az ún. package-et programozottan kezeljük a System.IO.Packaging névtérben lévő Packaging API segítségével. Az API támogatása addig terjed, hogy meg tudja nyitni a package-et és segít elérni benne az egyes részeket, az ún. package part-okat. A part-ok tartalmát streamek formájában tudjuk elérni és mivel többségük formátuma XML, ezért a System.Xml névtérben található jól ismert osztályokkal gyerekjáték (?) a tartalom módosítása.

Ezek ismeretében hogyan foghatunk hozzá egy Word dokumentum előállításához? Több lehetőség is van:

  1. Az API segítségével létrehozunk egy új package-et, majd abban új part-okat, és elkezdjük írni az XML-t. Mivel ebben az esetben tiszta lappal indulunk, ismernünk kell a Word ML nyelvet, amely értelmet ad a fenti XML-nek is. Ez nehezen járható út, de egyszerűbb esetekben működhet.
  2. Másik lehetőség, hogy Worddel létrehozunk egy ízlésünknek megfelelő dokumentumot és abban megjelöljük azokat a részeket, amiket cserélni szeretnénk. Ez már egyszerűbb, mert nem kell az egész dokumentumot megírnunk, csak a változó részeket kell megkeresnünk és átírnunk. Sajnos azonban ez sem triviális, mindjárt meglátjuk, miért…

Vegyük például a fenti dokumentumot és ne változtassuk meg a tartalmát, csak futtassunk egy helyesírás ellenőrzést, mégpedig angol nyelven. Szegény Word nem fogja megérteni a Szia szót, ráadásul és ezt a Word ML-ben is megjeleníti, nálam például ez lett az új document.xml (a sémákat kihagyva):

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <w:document ...>
        <w:body>
            <w:p w:rsidR="00A268F4" w:rsidRPr="00144B7E" w:rsidRDefault="00632D74">
                <w:pPr>
                    <w:rPr>
                        <w:lang w:val="en-US"/>
                    </w:rPr>
                </w:pPr>
                <w:proofErr w:type="spellStart"/>
                <w:r w:rsidRPr="00144B7E">
                    <w:rPr>
                        <w:lang w:val="en-US"/>
                    </w:rPr>
                    <w:t>Szia</w:t>
                </w:r>
                <w:proofErr w:type="spellEnd"/>
                <w:r w:rsidRPr="00144B7E">
                    <w:rPr>
                        <w:lang w:val="en-US"/>
                    </w:rPr>
                    <w:t xml:space="preserve"> Word 2007! </w:t>
                </w:r>
            </w:p>
            <w:sectPr w:rsidR="00A268F4" w:rsidRPr="00144B7E" w:rsidSect="00E20B73">
                <w:pgSz w:w="11906" w:h="16838"/>
                <w:pgMar w:top="1417" w:right="1417" w:bottom="1417" w:left="1417" w:header="708" w:footer="708" w:gutter="0"/>
                <w:cols w:space="708"/>
                <w:docGrid w:linePitch="360"/>
            </w:sectPr>
        </w:body>
    </w:document>

Bár nem változtattuk meg a dokumentum tartalmát, a belső szerkezete mégis megváltozott. Ahhoz, hogy ezt megértsük, kicsit dekódolnunk kell a fenti tag-eket:

  • A w:p egy paragraph, azaz egy bekezdés leírása.
  • A bekezdés tulajdonságai, jelen esetben, hogy angol nyelvű, a w:pPr elemben, paragraph properties elemben találhatóak.
  • A bekezdésen belül ún. run elem található, erre vonatkozik a w:r. A run tulajdonságait a run properties elem, a w:rPr tartalmazza.
  • A szöveg a run elemen belül text elemekben található, ez a w:t.

Ezek közül a legfontosabb a run. A run ugyanis a legkisebb formázható egység a Wordben. Másként fogalmazva: nem szövegre alkalmazzuk a formázásokat, hanem vagy bekezdésre, vagy run-ra. Tehát ha van egy összefüggő szövegünk, majd azt elkezdjük formázgatni, run-okra fog szétesni. Nézzünk erre is egy példát előbb Wordben:

Szia - tarkán

Majd lássuk ugyanezt a bekezdést Word ML-ben is:

    <w:p w:rsidR="00A268F4" w:rsidRDefault="00DF7BD5">
        <w:r>
            <w:t>S</w:t>
        </w:r>
        <w:r w:rsidRPr="00DF7BD5">
            <w:rPr>
                <w:b/>
            </w:rPr>
            <w:t>z</w:t>
        </w:r>
        <w:r w:rsidRPr="00DF7BD5">
            <w:rPr>
                <w:color w:val="FF0000"/>
            </w:rPr>
            <w:t>i</w:t>
        </w:r>
        <w:r w:rsidRPr="00DF7BD5">
            <w:rPr>
                <w:i/>
            </w:rPr>
            <w:t>a</w:t>
        </w:r>
    </w:p>

Egy kis segítség a megértéshez: a w:color talán nyilvánvaló, a hozzá tartozó FF0000 érték webfejlesztőknek különösen ismerős lehet, ez RGB-ben a piros. A w:b bold-ot, azaz félkövér formázást, a w:i pedig italic, azaz dőlt betűs formázást jelent.

Miért van szükség arra, hogy a run-on belül még újabb w:t text elemek is legyenek? Azért, mert ott nem csak szöveg lehet, lehetnek láthatatlan karakerek is. Íme két szöveg, közöttük pedig egy sortörés (nem új bekezdés!):

Sortöréssel

A sortörés egy speciális w:br tagként jelenik meg Word ML-ben:

    <w:p w:rsidR="00A268F4" w:rsidRDefault="003A21DF">
        <w:r>
            <w:t>Szia</w:t>
        </w:r>
        <w:r>
            <w:br/>
            <w:t>Word!</w:t>
        </w:r>
    </w:p>

Ráadásul teljesen jogos egy szöveget több run-ra bontani akkor is, ha azok semmiben nem különböznek egymástól. Sőt, nem csak formázáskor, hanem egyéb tulajdonságok változásakor is több run-ra tagolódik a szöveg, ahogy ezt az első példában a helyesírás ellenőrzésnél láttuk. Megváltozott az XML szerkezete, pedig mi nem is változtattunk a szövegen, de a Word igen!

Összefoglalva: bár a Word dokumentum belső formátuma XML, aminek a feldolgozásához kapunk API-t, a feladatnak az a része, hogy megtaláljuk a felülírandó szöveget az XML-ben nem egyszerű, mert az XML szerkezete változhat. Ezek alapján elmondhatjuk, hogy a kettes számmal jelölt megoldási módszerünk is nehezen megvalósíthatónak bizonyult.

Szerencsére van ennél is jobb megoldás, mégpedig…

(folytatjuk)

 

OBA “state of the art”: addin, ribbon, task pane – Site Source

Múlt héten egy barátom jóvoltából (ezúton is köszönet érte) kezembe akadt a 6 Microsoft® Office Business Applications for Office SharePoint® Server 2007 című MS Press könyv. Bevallom elsőre vegyes érzelmeim voltak a könyvvel kapcsolatban, mert bár a SharePoint programozás érdekel, az "OBA" hallatán feláll a szőr a hátamon.

Először is: Gondolt már valaki arra, micsoda nyelvi környezetszennyezést okoz az, ha egy ilyen mondat kerül a címlapra: "Practical OBAs for working developers"?

Másodszor pedig: Az Office szerencsére már nagyon régóta programozható platform mind szerver, mind pedig kliens oldalon és az én meglátásom szerint az OBA-hullám nem más, mint ennek egy új marketinges megközelítése, ahol minden "super-easy". Ettől még számomra továbbra is kliens és szerver oldali, részben burkolt COM és felügyelt interfészek összekalapálásáról van szó. Úgy látszik a könyv borító tervezője egyetérthet velem, mert szögeket rajzolt a címlapra 🙂

Azt belátom, hogy most már kezd az Office eljutni oda, hogy a felügyelt interfészeknek és a Visual Studio támogatásnak köszönhetően valóban lehet alkalmazás hosztként is használni, ez igaz. Legalábbis ezt mondják és a könyvbe beleolvasva én is hinni kezdtem a dologban…

Lassan másfél éve, hogy utoljára Office kliens programozással bajlódtam, akkor még keményen ott volt benne a COM és az ActiveX, nem volt viszont designer támogatás, helyette lehetett XML-t írni IntelliSense nélkül. A könyvet olvasva elhatároztam, hogy kipróbálom, milyen érzés mostanában egy Word addint írni.

Feladatként a következőt tűztem ki: írok egy olyan Word 2007 addint, ami kiegészíti a ribbont, ahol egy saját gombra kattintva megjelenik egy saját task pane. Ebben a saját task pane-ben el lehet navigálni egy SharePoint listára, a Wordben megjelennek a lista elemei, ahol az egyes mezők értékeit egyszerűen be lehet szúrni a dokumentumba. Mindezt azért, hogy ne kelljen annyit SharePoint listából Word doksikba copy paste-elnem és ALT-TAB-oznom, amitől jojózik a szemem egy idő után. Ha ehhez COM-ot vagy XML-t kell matatnom, megint másfél évre jegelem az Office programozást.

Fogtam tehát egy Visual Studio 2008-at és létrehoztam benne egy Word 2007 addin típusú projektet és elneveztem Site Source-nak. Plusz pont a VS teamnek, hogy már nincs külön VSTO, mindent megkapunk egy teljes Visual Studio 2008 telepítéssel:

New Project dialógusablak

A következő kellemes meglepetés akkor ért, amikor megláttam, hogy milyen tiszta kódvázat generál a Studio. Egyetlen osztály mindössze ennyi kóddal:

    namespace SiteSourceAddIn
    {
        public partial class ThisAddIn
        {
            private void ThisAddIn_Startup( object sender, System.EventArgs e )
            {
            }

            private void ThisAddIn_Shutdown( object sender, System.EventArgs e )
            {
            }

            #region VSTO generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InternalStartup()
            {
                this.Startup += new System.EventHandler( ThisAddIn_Startup );
                this.Shutdown += new System.EventHandler( ThisAddIn_Shutdown );
            }

            #endregion
        }
    }

Ennek fele se tréfa, dolgoztak a fiúk Redmondban. Nosza, alkossunk ribbont, tehát Solution Explorerben Add New Item és láss csodát, lehet ribbont létrehozni nemcsak XML-ben, hanem designer segítségével is:

New item templates

 

Be is húztam egy ToggleButton nevű vezérlőt a Toolboxról, ez fogja majd megjeleníteni a saját task pane-t és a szokásos módon generáltam hozzá eseménykezelőt:

Ribbon Designer

Az eredmény a Forms világban jól megszokott osztály, ahogy annak lennie kell. Kiegészítettem a ThisAddIn osztályomat, hogy meg is jelenjen:

        protected override Office.IRibbonExtensibility CreateRibbonExtensibilityObject()
        {
            return new Ribbon.RibbonManager( new Ribbon.OfficeRibbon[] { new SiteSourceRibbon() } );
        }

Hozzáadtam a projekthez egy user controlt, ebből lesz a task pane. User controlhoz már régóta van designer támogatás, így nem okozott gondot felparaméterezni a vezérlőket és bekonfigurálni az adatkötést:

User Control

Újabb egy sor a ThisAddIn osztályba az addin betöltődésekor és még egy internal változó, hogy tudjak rá hivatkozni:

        internal OfficeTools.CustomTaskPane taskPane;

        private void ThisAddIn_Startup( object sender, System.EventArgs e )
        {
            this.taskPane = this.CustomTaskPanes.Add( new SiteSourceTaskPane(), "Site Source Tasks" );
        }

Ezek után már megírhattam a ribbon eseménykezelőjét, ami megjeleníti vagy elrejti ezt a task pane-t:

        private void tbtnDisplay_Click( object sender, RibbonControlEventArgs e )
        {
            Globals.ThisAddIn.taskPane.Visible = !Globals.ThisAddIn.taskPane.Visible;
        }

A task pane SharePoint webszolgáltatások segítségével kéri le a listák tartalmát és LINQ to XML segítségével dolgozza fel. Ha valakit érdekel írjon és megmutatom azt is. Az Office programozással kapcsolatban ami érdekes, hogy végül az eredményt hogyan lehet beszúrni a dokumentumba. Így:

    Globals.ThisAddIn.Application.Selection.Text = value;

Mindenkinek ajánlom figyelmébe a Globals.ThisAddIn.Application objektumot, sok okosság érhető el rajta keresztül, ráadásul bárhonnan. Íme az eredmény, ahogy Wordben látszik:

Site Source Wordben

Összességében nagyon pozitív kép alakult ki bennem ezekről az alap programozói feladatokról Office 2007 és Visual Studio 2008 környezetben. Persze ez még csak a kezdet egy komolyabb projekt esetén.

Miután így belejöttem, belepakoltam még a SharePoint webszolgáltatás híváshoz szükséges felhasználó nevek és jelszavak kezelését a Windows Credential Manager segítségével. Az a kódom már megvolt korábban, csak most átírtam .NET 3.5-re. Az új szintaktikának köszönhetően 600-ról 390-re ment le a kódsorok száma és persze olvashatóbb is lett a kód.

Érdekel valakit az addin vagy a forráskód, közzétegyem?

Kiegészítés (2008.03.24.): A teljes forráskód megtalálható az MSDN Code Gallery SiteSource projektjében ezen a címen: http://code.msdn.microsoft.com/SiteSource
Minden visszajelzést szívesen veszek!

 

Office 2007 SP1 slipstreaming

Közel egy évvel az Office 2007 RTM után végre megjelent hozzá az első javítócsomag. Nem mondom, hogy az Office 2007 annyira használhatatlan lenne RTM formájában, mint a Vista, de azért erre is ráfért végre a service pack.

A Microsoft javítócsomagok már jó ideje lehetővé teszik, hogy beintegráljuk őket az eredeti RTM verzió telepítőjébe, így egyetlen telepítéssel rögtön a javításokkal ellátott verziót varázsolhatjuk a gépünkre. Ezt a folyamatot nevezik slipstreamingnek. Ez eddig egészen trükkösen működött, gyakorlatilag egy speciális kapcsolóval kellett elindítanunk a service pack telepítőjét, ami rátelepítette magát az eredeti install csomagra. Nem véletlen, hogy csak kevés halandó vállalkozott a feladatra.

Szerencsére ezt az Office 2007-tel teljesen újragondolták és leegyszerűsítették: az RTM telepítő tartalmaz egy Updates nevű mappát, amit mi ide beteszünk, azt a telepítő megtalálja és okosan fel is használja. Ide kell tehát tenni az SP1 fájljait is, csak hogy nem a webről letölthető .exe, hanem .msp fájlok formájában. Ehhez előbb ki kell bontanunk az .exe-t, ami az alábbi paranccsal tehető meg, a /extract kapcsolónak egy célkönyvtárat kell megadnunk, például így:

W:install>office2007sp1-kb936982-fullfile-en-us.exe /extract:"W:installOffice2007Updates"

Ehhez a művelethez admin jogra van szükségünk, mert valaki úgy gondolta, hogy a licenc elfogadásához mindenképp atyaúristeni privilégiumok szükségesek. Szerencsére később semmi más feladatunk nincs, mint elindítani a telepítőt, az SP1 automatikusan települni fog. Nekem SP1 telepítés előtt a Word 12.0.6015.5000, SP1 12.0.6212.1000 verziószámot mutatott.

Ugyanez a módszer tökéletesen működik az Office 2007 Language Pack-re is, ahhoz is létezik már SP1.