Povezivanje Word-a i XML-a

Sve češće se susrijećemo sa zahtijevom da se neki template dokumenti koje smo formirali u Wordu popunjavaju podacima iz glavne baze podataka.  Danas se ovaj posao najčešće radi na način što se iz baze podataka mehanički izvrši “copy” jednog po jednog podatka i uradi “paste” tih podataka na odgovarajuća mjesta u template-u dokumenta. Template dokumenta su uglavnom napravljena tako da kada se otštampaju zadovoljavaju formu fakture, otpremnice ili sličnih dokumenata koji su najčešće u operativnom opticaju.

Ovaj zamoran posao možemo značajno ubrzati bez izrade nekih posebnih izvještaja i bez nabavke specijalizovanog razvojnog alata. Ono što nam je potrebno je Word 2007 i dokumenat formiran upotrebom ovog tekst procesora.  Za ilustraciju ove mogućnosti smo uzeli obični tekst nekog formalnog dokumenta iz područja poslovne korespodencije (u suštini, to može biti bilo kakav dokumenat koji treba da se napuni podacima iz baze podataka). Unutar ovog teksta ćemo postaviti konkretna polja koja su predmet našeg interesovanja (koja u stvari predstavljaju ona polja u onim našim fakturama i otpremnicama zbog kojih sve ovo radimo – polja čiji sadržaj kopiramo iz glavne baze). Kroz ovaj primjer  nam je bitno da shvatimo kako da polja formiramo i automatski popunimo i kako cjelokupni mehanizam funkcioniše.

Kreiranje word template dokumenta sa Content control-ama

Prvo treba da formiramo polja u našem word dokumentu, odnosno – da odredimo prostor u našem dokumentu u koji ćemo prenositi podatke iz baze. Za realizaciju te aktivnosti trebaju nam alatke koje se obično nalaze u developer “tab”-u word ribona (ako vam ovaj tab nije na raspolaganju, uradite WordOptions->Popular -> TopOptionForWorkingWithWord i izvršite “check” na check box-u ispred teksta “ShowDeveloperTabInRibbon”):

Sada smo u mogućnosti da na nekoliko mjesta u ranije definisanom word template-u umetnemo TextContent Control po specifikaciji koja slijedi a koja je ključ komunikacije izmedju našeg dokumenta i glavne baze podataka.

Uzećemo naš demo primjer dokumenta. Želimo da u produžetku reda u kojem je ispisano “Ime i prezime” postavimo kontrolu koja će iz baze podataka automatski prenjeti potrebno ime i prezime. Potrebno je da u produžetku teksta “Ime i prezime” našeg word templatea postavimo kursor i aktiviramo iz developer tab-a  TextContent kontrolu:

Tekst koji će se ispisivati preko TextContent Controls u template-u kada se kontrola postavi u template treba da bude smisleno definisan kako bi nas asocirao na to koji podatak će se u njega prenositi. Svako od ovako definisanih tekstualnih polja treba pojedinačno da selektujemo i da za njega aktiviramo Properties kontrolu kako bi ste ovim poljima dodjelili korektno ime (prikazano na sljedećim slikama).

Kroz ovaj primjer u dokumentu definišemo 4 Content Control-e i za svaku smo definisali neko prepoznatljivo ime na isti način kako je prikazano za polje “ime_i_prezime”.

Sada možemo sačuvati i zatvoriti template dokumenat. Na ovaj način je izvršena separacija word dokumenat podataka na izmjenljive i fiksne podatke. Na nivou dokumenta se može postaviti zaštita na način da se samo sadržaj TextContentControl-a može mjenjati (čak i više od toga – možemo definisati ko i odakle te promjene vrši).

Cilj u nastavku ovog  rada je upravo da “kažemo” word-u ko će (taj “ko” je posebni XML file) i na koji način napuniti definisana polja. Iako se word “ne sekira” oko toga kako se pune definisana polja u dokumentu i kako im se odredjuju vrijednosti, zbog konfora u radu krajnjeg korisnika potrebno je odgovoriti na prethodni zahtjev.

Prije realizacije tog dijela, jedna digresija koja će nam biti od koristi. Možda ne znate, a možda vam je već poznato: Office 2007 (a to znači i Word docx fajlovi, Word docm fajlovi itd)  su u stvari komprimovani ZIP fajlovi  (“packages”)  koji sadrže XML djelove (“parts”). To znači da se njihova struktura može pogledati kroz npr. WinZIP arhiver.   Ukoliko promjenite ekstenziju vašeg word dokumenta u kojem ste radili sa docx u zip, i kada ovaj dokumenat otvorite WinZip alatom, možemo uočiti da dobijamo strukturu XML file-a.

Kreiranje XML file-a koji sadrži definicije polja za povezivanje

Pošto naslućujemo da je XML fajl – komponenta iz koje se trebaju puniti naša polja, slijedeća stvar koju treba da uradimo je da omogućimo na neki način rukovanje XML-om. Taj XML fajl bi trebao imati na neki način definisana polja koja su potrebna našem template dokumentu. Taj XML fajl treba nekako podmetnuti našem template dokumentu.  Najjednostavnija mogućnost je da “izvana” izvršimo ažuriranje u template-u definisanih polja – ConcentControl-a. Za realizaciju obadva ova posla ćemo iskoristiti jedan jednostavni besplatni alat – uradićemo download “Word 2007 Content ControlToolkit” od firme CodePlex.  Poslije instalacija ove aplikacije, njenog aktiviranja i pronalaženja našeg dokumenta preko standardne opcije File -> Open, u centralnom dijelu GUI-a će biti prikazane naše 4 ContentControl-e koje smo u prethodnim koracima kreirali.

Primjetićemo i da je kolona XPath u ovom trenutku prazna.

Sljedeće što želimo da uradimo je da definišemo tzv  Custom XML Part (odnosno – XML fajl) za ovaj Word dokumenat i da povežemo pojedinačne XML čvorove sa svakom pojedinačnom Content Control-om.  Na desnoj polovini GUI-a Word 2007 Content Control Toolkit možemo vidjeti da u ovom trenutku nemamo korisničku XML komponentu u dokumentu

Kao što i naslućuje – aktiviraćemo link  “Click here to create a new one.”  za kreiranje novog XML dijela. ContentControlToolkit nas prebacuje u tzv Edit view što omogućuje da jednostavnije upravljam XML strukturama. U ovom editoru ćemo ukucati nekoliko definicija koje odgovaraju potrebama parametara koje smo definisali kroz template dokumenat. Unjećemo tekst koji je prikazan na sljedećoj slici.

Sačuvajmo ovaj XML tekst u fajl sistemu. Nazovimo XML dokumenat koji smo ukucali kroz Word 2007 Content Control Toolkit item1.xml.

Za mnogo komplikovanije strukture, u mogućnosti smo da izvršimo upload ranije definisane XML strukture iz file sistema.  Vrijednosti koje ukucavamo unutar svakog od XML nodova su upravo one koje se prikazuju kao imena ContentCointrol-a unutar našeg Word template-a. Ukoliko pritisnemo tab “bind view” dobićemo spisak definisanih imena kroz XML i moćićemo da vidimo našu poznatu strukturu:

Povezivanje XML file-a sa custom controls u template dokumentu

Sada vršimo fizičko povezivanje djelova XML fajla sa poljima definisanim u template-u a koja je alat ContentControlToolkit prepoznao. Svaki pojedinačni nod sa desne strane GUI-a prenesimo na njegovog parnjaka  iz Word Template-a na lijevoj strani GUI-a. Kada se svi parametri prenesu, primjetićete da je izvršeno ažuriranje i Xpath kolone u Content Controls.

Sačuvajmo uradjene promjene i izadjimo iz alata. Ukoliko sada pokušamo ponovo da pogledamo sadržaj našeg dokumenta koristeći WinZIP tool uz prethodnu promjenu ekstenzije sa docx na zip, primjetićemo da se pojavila jedna nova grana  – novi folder u listi CustomXml. Ovaj folder će sadržavati naše definicije koje smo upravo realizovali.

Prethodne aktivnosti na povezivanju XML dokumenta i word template-a smo mogli realizovati i primjenom 2 makro poziva iz worda. Naime….

Da bi content controls i korisnički definisani XML file međusobno mogli da komuniciraju, trebamo kreirati makro koji će ih povezivati. U Word 2007 template dokumentu ćemo kreirati makro CreateCustomXMLPart preko kojeg ćemo izvršiti dodjelu  XML dokumenta našem word dokumentu.

///Initial CustomXMLPart
Sub CreateCustomXMLPart()
    ActiveDocument.CustomXMLParts.Add
    ActiveDocument.CustomXMLParts(12).Load ("\\customXml\item1.xml")
End Sub

Naredni word makro –  LoadCustomXMLPart je namjenjen za mapiranje content control-a koje smo kreirali u našem word dokumentu:

///Load CustomXMLPart
Sub LoadCustomXMLPart() 
    Dim strXPath1 As String 
    strXPath1 = "/root/ime_i_prezime" 
    ActiveDocument.ContentControls(1).XMLMapping.SetMapping strXPath1 
    Dim strXPath2 As String 
    strXPath2 = "/root/iznos_fakture" 
    ActiveDocument.ContentControls(2).XMLMapping.SetMapping strXPath2 
    ........
End Sub

Sada smo spremni da izvršimo mapiranje jednog od nodova XML file-a sa content control-ama u wordu. Kada se pokrenu makroi u izvršavanje, sva tekstualna polja  definisana kroz content control-e u word template dokumentu će biti napunjena podacima koji postoje u XML dokumentu.

To znači da na više različitih načina možemo izvršiti povezivanje XML-a i Word dokumenta.

Ovo medjutim, još nije kraj priče jer na ovaj način nije postignut značajniji automatizam. Dobili smo samo mogućnost da umjesto što smo radili copy iz baze podataka  i paste u word template sada kopiramo sadržaj iz baze u XML file koji se zatim automatski puni u word template. Mi bi nekako želili da se XML dokumenat napuni automatski bez naše intervencije. Stoga idemo na sljedeći korak….

Pošto nam je cilj da čitamo podatke iz npr, SQL Server-a i da se za svaki slog koji ispunjava određeni kriterijum generiše novi word dokumenat (naša faktura ili otpremnica), treba da napišemo makro koji će povezati podatke iz SQL Server baze podataka sa template dokumentom.

Private Sub Document_Open()
    Call BindData
End Sub

Sub Sub BindData() 
    Dim part As CustomXMLPart 
    Set part = ActiveDocument.CustomXMLParts.SelectByNamespace("")(1)
    Dim ctlImePrez As ContentControl 
    Dim ctlIznos As ContentControl
    ........
    Set ctlImePrez = ActiveDocument.ContentControls(1)
    Set ctlIznos = ActiveDocument.ContentControls(2) 
    ........
    ctlImePrez.XMLMapping.SetMapping "/root/ime_i_prezime", "", part 
    ctlIznos.XMLMapping.SetMapping "/root/iznos_fakture", "", part 
    ........
End Sub

Definisanje OredrInformation Class

Namjena OrderInformation klase je da formira XML strukturu poput one koju smo definisali u item1.xml tako da budemo u mogućnosti da podatke iz baze automatski povežemo sa template dokumentom..

  public string Address
        {
            get { return _address; }
            set { _address = value; }
        }
  public string City
        {
            get { return _city; }
            set { _city = value; }
        }
        .....
  .........
// Build the XML structure
 public string ToXml()
        {
            XmlDocument xmlDoc = new XmlDocument();

            XmlElement root = xmlDoc.CreateElement("Customer");
            xmlDoc.AppendChild(root);
            AppendChild(xmlDoc, root, "Address", _address);
            AppendChild(xmlDoc, root, "City", _city);
            AppendChild(xmlDoc, root, "Region", _region);
            AppendChild(xmlDoc, root, "PostCode", _postcode);
            AppendChild(xmlDoc, root, "Country", _country);
            AppendChild(xmlDoc, root, "CustomerID", _customerID.ToString());
            AppendChild(xmlDoc, root, "OrderID", _orderID.ToString());
            AppendChild(xmlDoc, root, "ShippedDate", _shippedDate.ToString());
            AppendChild(xmlDoc, root, "HomePhone", _homePhone);
            AppendChild(xmlDoc, root, "LastName", _lastName);
            AppendChild(xmlDoc, root, "FirstName", _firstName);
            AppendChild(xmlDoc, root, "Title", _title);

            return xmlDoc.InnerXml;
        }
private void AppendChild(XmlDocument xmlDoc, XmlElement root, 
                       string nodeName, string nodeValue)
        {
            XmlElement newElement = xmlDoc.CreateElement(nodeName);
            newElement.InnerText = nodeValue;
            root.AppendChild(newElement);

Definisanje GenerateDocument klase

Glavna funkcija GenerateWord() je implementirana na način da generiše novi dokumenat čije je ime identično Id-u imena klijenta iz template dokumenta koji se formira ovom prilikom  sa stvarnim podatkom. U narednoj klasi, System.IO.Package od .NET 3.0 je iskorišten da izmjeni sadržaj template dokumenta, mjenjajući ga sa sadržajem XML fajla  i na kraju da za te stvarne podatke iz template dokumenta ganeriše stvarni word dokumenat sa konkretnim – stvarnim podacima..

///Generate Word document.            
public void GenerateWord( OrderInformation orderInfo)
        {
            string templateDoc = "LetterFormTemplate.docm";
            string filename = string.Format("{0}.docm", orderInfo.CustomerID);
           // Copy a new file name from template file
            File.Copy(templateDoc, filename, true);
           // Open the new Package
            Package pkg = Package.Open(filename, FileMode.Open, FileAccess.ReadWrite);
           // Specify the URI of the part to be read
            Uri uri = new Uri("/customXml/item1.xml", UriKind.Relative);
            PackagePart part = pkg.GetPart(uri);
           // Load XML 
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(orderInfo.ToXml());
           // Open the stream to write document
            StreamWriter partWrt = new StreamWriter
                       (part.GetStream(FileMode.Open, FileAccess.Write));
            doc.Save(partWrt);

            partWrt.Flush();
            partWrt.Close();           
        }

Sve je pripremljeno. Sada još samo da implementiramo zahtjeve.

Čitaj podatke sa SQL Server-a kako bi kreirao novu instancu OrderInformation. Pretpostavimo da radimo na bazi Northwind koja je “podignuta” i spremna da prihvati naš zahtjev. U ovom primjeru ćemo izvršiti upit koji treba da formira dokumenat – fakturu za korisnika Customer  – klijenta sa brojem 10205:

public OrderInformation GetOrderInformation()
        {
            string sqlQuery;
            sqlQuery = "SELECT o.OrderID, o.CustomerID, o.ShippedDate, ";
            sqlQuery = sqlQuery + "e.LastName, e.FirstName, e.Title,e.Address, _
                       e.City, e.Region, e.PostalCode,e.HomePhone,e.Country ";
            sqlQuery = sqlQuery + "FROM Orders o ";
            sqlQuery = sqlQuery + "INNER JOIN Employees e ON _
                               e.EmployeeID = o.EmployeeID ";
            sqlQuery = sqlQuery + "WHERE o.OrderID = 10250";
            using (SqlConnection conn = new SqlConnection_
               (@"Integrated Security=SSPI;Persist Security Info=False;_
               Initial Catalog=Northwind;Data Source=localhost"))
            {
                SqlCommand cmd = new SqlCommand(sqlQuery, conn);
                cmd.Connection.Open();

                SqlDataReader sdr = cmd.ExecuteReader();
                sdr.Read();
                OrderInformation orderInfo = new OrderInformation();
                orderInfo.OrderID = sdr.GetInt32(0);
                orderInfo.CustomerID = sdr.GetString(1);
                orderInfo.ShippedDate = sdr.GetDateTime(2);
                orderInfo.LastName = sdr.GetString(3);
                orderInfo.FirstName = sdr.GetString(4);
                orderInfo.Title = sdr.GetString(5);
                orderInfo.Address = sdr.GetString(6);
                orderInfo.City = sdr.GetString(7);
                orderInfo.Region = sdr.GetString(8);
                orderInfo.PostCode = sdr.GetString(9);
                orderInfo.HomePhone = sdr.GetString(10);
                orderInfo.Country = sdr.GetString(11);

                return orderInfo;
            }
        }

Pročitaj podatke iz XML fajla koji sadrži veći broj korisnika – Customer-s:

Parsiraj  XML podatke,  sačuvaj ih u listu OrderInformation:

public List ListOrders()
{
   XmlDocument customers = new XmlDocument();
   Hashtable nodeData = new Hashtable();

   customers.Load("Data.xml");
   List<orderinformation /> orderList = new List<orderinformation />();
   foreach (XmlNode customer in customers.DocumentElement.ChildNodes)
   {
       foreach (XmlElement element in customer.ChildNodes)
       {
            {
               nodeData.Add(element.Name, element.InnerText);
           }
       }
       if (nodeData.Count > 0)
       {
           OrderInformation orderInfo = new OrderInformation();
           orderInfo.OrderID = Convert.ToInt32(nodeData["OrderID"]);
           orderInfo.CustomerID = nodeData["CustomerID"].ToString();
           orderInfo.ShippedDate = Convert.ToDateTime(nodeData["ShippedDate"]);
           orderInfo.LastName = nodeData["LastName"].ToString();
           orderInfo.FirstName = nodeData["FirstName"].ToString();
           orderInfo.Title = nodeData["Title"].ToString();
           orderInfo.Address = nodeData["Address"].ToString();
           orderInfo.City = nodeData["City"].ToString();
           orderInfo.Region = nodeData["Region"].ToString();
           orderInfo.PostCode = nodeData["PostCode"].ToString();
           orderInfo.HomePhone = nodeData["HomePhone"].ToString();
           orderInfo.Country = nodeData["Country"].ToString();
           // Add To List 
           orderList.Add(orderInfo);
           nodeData.Clear();
       }       
   }
   return orderList;
}

Pokretanje aplikacije GenerateWord2007.exe

Ova aplikacija je implementirana kroz 2 metode za generisanje novog word dokumenta čiji se podaci čitajuu sa SQL servera ili iz XML fajla.

Iz SQL Servera

private void button1_Click(object sender, EventArgs e)
     {
         OrderInformation orderInfo;
         GenerateDocument doc = new GenerateDocument();
         orderInfo =  doc.GetOrderInformation();
         doc.GenerateWord(orderInfo);
     }  

Iz XML File-a

private void button2_Click(object sender, EventArgs e)
     {
         GenerateDocument doc = new GenerateDocument();
         List orderList = new List();
         orderList = doc.ListOrders();
         foreach (OrderInformation orderInfo in orderList)
         {
             doc.GenerateWord(orderInfo);
         }
     }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s