All posts by Alison

Full time OfficeWriter Product Owner, part-time pivot table enthusiast.

Unfolding a Word document with WordApplication

Problem

In the course of debugging, it can often be difficult to visualize the structure of documents as they are manipulated by WordApplication.

Solution

Our documentation has an overview of how WordApplication represents a Word document and how to insert elements using WordApplication. Additionally, there is a code sample that will produce a formatted text representation of the element hierarchy in a given document: Unfolding a Word document with WordApplication Sample.

For example, given the following document:

Our output generated by the code would look like this:

UnfoldingWordDocument

Using Report Models as DataSources in OfficeWriter Designer

Problem

Prior to version 3.9.2, OfficeWriter designer did not support reports with queries based on Report Models. Publishing such a report from OfficeWriter Designer caused some fields to show gibberish or not show at all.

For more information about Report Models visit Microsoft web-site at http://technet.microsoft.com/en-us/library/cc678411.aspx.

Solution

From version 3.9.2 and above, OfficeWriter supports Report Model DataSources. Reports created using Visual Studio (2005 and 2008) or ReportBuilder version 2.0, that use a report model as a datasource are now parsed correctly by the OfficeWriter designer.

Note: Support for newer versions of Reporting Services (2008, 2008 R2) and Report Builder (3.0) have been added in later version of OfficeWriter. For more information, refer to the change log in the OfficeWriter Docs.

Reports may contain both regular SQL queries and semantic queries (Report Model-based queries). This support is seamless to the user with no change to the toolbar. However, a feature was added to improve the user experience.

In Report Models, all fields are mapped to entities (instead of tables). We added a feature that allows the user to know to which entity a certain field belongs. For example, if the user encounters a field First Name, it will have a way of knowing whether this is an employee or a customer first name. When a report model is the datasource, its fields will be displayed as a submenu of their entities (rather than the usual flat list of fields), as in the image below:

Figure 1.

Below is the same query, but in the unmapped view:

Figure 2.

The RDL alone does not contain sufficient information to map the fields to entities. Therefore, when a Report Model-based query is selected from the Select Query drop down, we attempt to retrieve the model (as an XML file) from the server. If the address to the model on the server is embedded in the RDL, we try to retrieve it immediately. If we are not able to get the model, or if there is no address embedded in the RDL, the following window will open:

Figure 3.

This dialog offers the following options:

  • Continue will continue without retrieving the model. This will not affect the functionality of the report, the only effect is that the fields will not be mapped to entities, but will be displayed in a single list (as in Figure 2).
  • Retry will attempt to retrieve the model from the server specified in the RDL if the address exists.
  • Browse… will open a window (as in Figure 4 below) that will allow the user to browse to a server and choose a model from a list of available models on the server.

Figure 4.

Note that each model has a unique ID which is not visible to the user. Two models, even if they are created based on the same database with the same entities and fields, will have different IDs. Therefore, mapping the fields of a report created with one model against another model will fail. However, each model preserves its ID when deployed to different servers, so fields can be mapped using a model from another server if it was deployed from the same source model.

Once the model is retrieved, the fields are mapped to entities under the Insert Fields drop-down list (as in Figure 1). Formulas created in Visual Studio or ReportBuider are not related to any entity and will show as regular fields at the bottom of the Insert Field drop-down.

This change applies to the client-side OfficeWriter designer. The server-side components of OfficeWriter require no special modification to work with Report Models. However, we recommend always using matching versions of the designer and the server-side installation of OfficeWriter.

How to add page numbers or other fields with WordApplication

Problem

WordApplication does not currently have support for inserting fields other than MergeFields and Hyperlinks. You want to insert a different kind of field, such as the current page number.

Solution

WordWriter has a MergeField class and a Hyperlink class which allow you to programmatically insert those fields. However, those are currently the only two fields which are included in the WordWriter API. In order to insert a field such as a page number into a newly generated document in WordApplication, you must first create a document with Microsoft Word and copy the field from there.

Inserting Footers with Fields

If you wish to create a footer which includes a field such as the page number, you must first create a document in Microsoft Word. Add all the text, fields, and formatting that you wish to appear in the final document to this file. For the purposes of this article, this file will be called ‘footer.doc’.*

In order to use your newly created footer, you must open the document in your WordApplication and copy the footer into the document you are generating programmatically:

//Create the document you want to insert a footer into
Document doc = wa.Create();


//Or open an existing file, instead of creating one
//Document doc = wa.Open("myFile.doc");


//Open the document with the footer you want to copy
Document footerDoc = wa.Open(Page.MapPath("footer.doc"));


//Get the footers for both documents
Element footerSource = footerDoc.Sections[0].get_Footer(Section.HeaderFooterType.All);
Element footerDestination = doc.Sections[0].get_Footer(Section.HeaderFooterType.All);

//Insert the footer into the destination document
footerDestination.InsertAfter(footerSource);
In this way, you can copy the footer into a newly generated document or one that was opened from a different file. For more information about retrieving footers or headers and inserting them as Elements, see the documentation for the Section and the Element classes.

Inserting Headers with Fields

Copying the header is very similar to copying the footer, except that you would insert the header Element instead of the footer. So if you have a document called ‘header.doc’ that you wish to copy the header from, you can say:

//Create the document you want to insert a header into
Document doc = wa.Create();


//Or open an existing file, instead of creating one
//Document doc = wa.Open("myFile.doc");


//Open the document with the header you want to copy
Document headerDoc = wa.Open(Page.MapPath("header.doc"));


//Get the headers for both documents
Element headerSource = footerDoc.Sections[0].get_Header(Section.HeaderFooterType.All);
Element headerDest = doc.Sections[0].get_Header(Section.HeaderFooterType.All);


//Insert the header into the document
headerDest.InsertAfter(headerSource);

Inserting Fields into the Body of a Document

If you instead want to insert a field into the body of the document along with programmatically generated content, you can retrieve only the Field Element from a pregenerated file. For example, if you want to insert a ‘Date’ field into the body of a document, you must first create a new file in Microsoft Word. Add a Date field to the body of this new document by going to Insert->Field and choosing Date. Save the document. For the purposes of this article, the file will be called ‘datefield.doc’.

In your WordApplication, when you want to insert the field, you will open the datefield.doc document, retrieve the field, and insert it into your own document:

//Open the document with the date field in it
Document fieldDoc = wa.Open(Page.MapPath("datefield.doc"));


//Get the date field from the body of the document. We use an index of 0
//because it is the first (and only) field element in the document.
Element dateField = fieldDoc.get_Elements(Element.Type.Field)[0];


//Insert the date field into the body of another document (called 'doc').
doc.InsertAfter(dateField);

In this way, you can programmatically add Microsoft Word fields to a newly generated document. For more information about how to specify what type of Element you want to retrieve from the document, see the documentation for the Elements property of the Element class and refer to the different types of Elements that you can ask for.

How to keep cell references absolute when using ExcelTemplate

Problem

When using ExcelTemplate to import data, a new row is inserted into the worksheet for each row of data in the data source. Any cell references to the cells where the new rows are being inserted will be updated to reflect that new rows have been inserted. This includes relative cell references (e.g. A5) and formulas (e.g. SUM(A5:A7)).

This is native Excel behavior: whenever a row (or column) is inserted or deleted, all cell references to that row/column will be updated.

In some cases, having the formulas or references updated may not be the desired behavior.

Solution

There are two ways to keep cell references absolute:

Use ‘$’ to denote absolute references

In Excel, absolute references are denoted with ‘$’. If a reference is absolute, then ExcelTemplate will not update the reference when rows are inserted. Here are some examples of absolute references:

  • $B5 – The column A is absolute and will not change, even if a new column is inserted between columns A and B. Rows will still update if new rows are inserted.
  • B$5 – The row 5 is absolute and won’t change if rows are inserted or deleted. If a column were to be inserted, then the column reference would update.
  • $B$5 – The cell reference is absolute and will refer to B5 even if rows/columns are inserted/deleted.

Use INDIRECT to preserve formula references

If a cell reference is pre-pended with INDIRECT, Excel treats the reference as a string and does not change it. For example, the formula =AVERAGE(INDIRECT(“Sheet1!E1:E10”)) will always refer to that particular range of cells.

Additional Reading

How to use SetCultureInfo with date functions and formatting

Problem

The SetCultureInfo method allows you to override the server’s default locale when generating a new Word file from a template with WordWriter. This is especially useful for the display of dates. It is important, however, to understand that any date formatting must be applied to the Word Template Merge Field instead of in the ASP.NET script.

Solution

To see a standard field code for a DateTimeObject merge field, right-click on the field, and choose Toggle Field codes:

 {MERGEFIELD DateTimeObject} 

You can see that there are no formatting switches applied to this field. In this situation, the default format of “yyy-MM-dd HH:mm:ss” (4-digit year, month, day, military hour, minute, second) will be applied. To set the culture info to a particular language, such as German:

CultureInfo deDe = new CultureInfo("de-DE");
wt.CultureInfo = deDe;

For more information see our documentation on WordTemplate.CultureInfo.

Best coding practices

Unexpected date formatting behavior can be prevented by keeping the following in mind: The Word template is expecting to receive the date as a System.DateTime object. If it is passed a String instead of a System.DateTime object, it will not be able to apply the formatting codes and apply the correct Culture settings.

How to check if a cell is empty

Solution

A cell can contain a value and/or a formula. To check if a cell is empty use the Cell.Value and Cell.Formula properties to look for the following conditions:

XLS Files XLSX and XLSM Files
Language Cell.Value Cell.Formula Cell.Value Cell.Formula
C# null (empty string) (empty string) OR null (empty string)
VB.NET Nothing Nothing (empty string) OR Nothing Nothing
ASP/COM * (empty string) (empty string) (empty string) (empty string)

 

Example

//--- myCell is a Cell object 
if (string.IsNullOrEmpty(myCell.Value) && string.IsNullOrEmpty(myCell.Formula))
{
//--- Cell is empty
}


How to apply a banded style on a table with WordTemplate

Problem

In a template document created in Word 2003, table styles behave correctly when new rows are added by WordWriter. However, if a new template document is created in Word 2007 with the new table styles, these styles do not persist as expected when saved in the binary (.doc) format. This can easily be demonstrated with the banded coloring styles which are displayed as alternating row coloring.

After processing with WordTemplate, the additional rows in the table will have a solid background color instead of a banded styling. MS Word itself shows the same behavior when adding rows to a table styled with the new 2007 styles and saved to the 97-2003 binary file format (.doc). This appears to be a compatability issue at the file format level, with Word not fully supporting the new table styles in the binary file format.

Banded styles behave as expected with OOXML (.docx) files, which have been supported by WordTemplate since version 4.0.

Solution

Option 1: Save the file as .doc before applying table formatting

When Word 2007 opens a .doc file, it opens in compatibility mode which only makes available features that are supported for that format. However, when Word 2007 creates a new document, it doesn’t know which format the user will choose to save it in, so all options are shown, including those which aren’t compatible with the binary format.

The easiest solution would be to save the file as 97-2003 binary file format (.doc) before applying any table formatting. Once the file has been saved, only the styles compatible with the binary format will be available and you can use them freely. If you have already created a template file with 2007 formatting, simply reapply the styling to the tables after the file has been saved in binary format.

Option 2: Use the OOXML file format

Alternatively, with WordWriter 4.0 and above, the other good solution is to use the OOXML (.docx) file format. The resulting populated table will be as you expect when using that file format. The feature is supported in the file format level, so you can place your data marker in such formatted table and it will expand and maintain the selected style.

How to use merge documents together with Document.Append

Problem

Prior to WordWriter 4.5.0, the only way to merge entire documents was to use InsertAfter. Starting in WordWriter 4.5.0, Document.Append() was introduced as an improved way to merge documents together.

This post covers the behavior of Document.Append().

Solution

The default behavior of Document.Append() is creating a section-page break between the original document and the inserted document:

 documentOne.Append(newDocument); 

To merge the two documents so they appear continuous, simply change the section break type to be continuous:

int mySectionsCount = thisDocument.Sections.Length;
thisDocument.Append(otherDocument);
thisDocument.Sections[mySectionsCount].Break = Section.BreakType.Continuous;

Example:

For a more complicated example, here we want to insert into our current work document (thisDocument) a header from one template document and body content from another template document. Note that we want both the header and the body to be on the same page, so we change the type of the section break at the tail of the header to continuous.

// adding header document
Document headerDocument = Wapp.Open(_headerSource);
thisDocument.Append(headerDocument);


// adding body document
Document bodyDocument = Wapp.Open(_bodySource);
int sectionCount = thisDocument.Sections.Length;
thisDocument.Append(bodyDocument);
thisDocument.Sections[sectionCount].Break = Section.BreakType.Continuous;

Note that if the initial document (the one you append into) is empty, we get a blank page at the beginning of the document. To fix that we need to delete the first empty section:

 thisDocument.Sections[0].DeleteElement(); 

All other InsertAfter operations that don’t deal with sections should stay unchanged.

Inserting a subreport into a WordWriter report

Problem

You need to insert a subreport into a larger report using WordWriter.

Solution

Option 1:

Generate each subreport separately and then use the WordApplication API to merge the subreports into the larger report. The subreports could be appended to the main report document with Document.Append, or if you need to insert the subreport into a particular place, you can use Element.InsertAfter or Element.InsertBefore.

Option 2

Flatten your data set and use WordWriter Grouping and Nesting. Grouping and Nesting would allow you repeat sections of a document for a particular subgroup in the data.

NOTE: Both these solutions require WordWriter Enterprise Edition. For more information about the features offered in Enterprise Edition, check out the OfficeWriter Change Log page.

How to create a chart with different chart types

Problem

ExcelWriter supports creating custom charts, including charts that contain several different chart types. For example, a chart wtih two area series and one line series.

Solution

This code snippet creates a chart with two area series and one line series:

Chart chart = ws.Charts.CreateChart(ChartType.Area.StandardArea,ws.CreateAnchor(5, 0, 0, 0));
chart.SeriesCollection.CreateSeries("A1:A3");
chart.SeriesCollection.CreateSeries("B1:B3", ChartType.Area.StandardArea, AxisType.Primary);
chart.SeriesCollection.CreateSeries("C1:C3", ChartType.Line.StandardLine, AxisType.Primary);