(Print this page)

Wiki revisited – Generating a Word document
Published date: Monday, September 12, 2016
On: Moer and Éric Moreau's web site

Back in February 2008, I showed how to create a wiki-like help system.

Since 2008, I have implemented that feature in most applications because the user can really customize the documentation to fit their needs. The system has proven to be useful in many occasion.

Not only users are more than happy to be able create and improve the help by themselves, they have also asked for some improvements. These required are the same as those listed in the “Extending this system” section of the 2008 article. I have seen them coming! One of the improvements is the ability to be able to jump from one topic to another (this is why I have a listbox control on the left of the screen). Lately, a customer also asked me to generate a Word document containing all the topics from the wiki system.

That last request will be subject of this article.

Downloadable demo

This month downloadable demo contains both VB and C#. The solution has been created using Visual Studio 2015 but the same code can be used in earlier version as well.

Figure 1: The demo application in action

No Office automation here

I won’t use Office automation here. I will use a library I have been using for many years now. I will use Aspose.Words for .Net which is a commercial (not free) library.

It is true that I could have achieve the same results using Office Automation but I had so many bad experiences trying to automate Office applications (mostly Excel) that I have stopped trying a long time ago. Most issues were coming from the various versions to support, the users trying to work on documents at the same time my application was trying to work with them, tools not available on a server, and so on.

You can download a trial version from the link here above. The required DLL is also included in the zip. The trial is not time-bombed or feature limited. Instead, you will see a big red message in the generated document.

Code reused from the 2008 article

I have reused some code from the article published in 2008. But I have also simplified the demo. It is now a single standalone form. It will be easier for you to reuse. If you want to see how to integrate it as part of a larger application, refer to 2008 article.

So I have reused the same toolbar and the same RichTextBox control and the code related to them.

Persisting the wiki

For the simplicity of this article, I didn’t want to have a database. I have preferred to keep the mechanism I was using in the demo of 2008: a dataset persisted in a .XML document. Easy enough for the requirements of the demo application.

That means that when the application starts, the OpenHelpDataset method is called. This method will load a file named Help.xml from the current directory into an in-memory dataset. If the file does not exist, a dataset with the correct structure is created. The Dataset is then bound to the listbox to let the user jumps from one topic to another. The code is:

Private Sub OpenHelpDataset()
    If Not File.Exists("Help.xml") Then
        mdataset = New DataSet

        Dim dt As New DataTable
        With dt
            .TableName = "HelpTable"
            With .Columns
                .Add("Code", GetType(String))
                .Add("HelpText", GetType(String))
            End With
        End With
        mdataset.Tables.Add(dt)
        mdataset.DataSetName = "HelpDataset"
    Else
        mdataset = New DataSet
        mdataset.ReadXml("Help.xml")
    End If

    listBox1.ValueMember = "Code"
    listBox1.DataSource = mdataset.Tables(0)
End Sub

When the application is closing, the SaveHelpDataset method is called. The code is much simpler because we are using a feature built-in the Dataset object:

Private Sub SaveHelpDataset()
    mdataset.WriteXml("Help.xml")
End Sub

Navigating through the topics

When you want to create a new topic, you can click the New button (the first one from the toolbar). That will just clear the 2 textboxes like this:

Private Sub NewToolStripButton_Click(ByVal sender As Object, ByVal e As EventArgs) Handles NewToolStripButton.Click
    txtTitle.Clear()
    txtWiki.Clear()
End Sub

You then have to fill the title and the help topic itself and click the Save button from the toolbar (the second button). Notice that the title becomes the key of the topic.

The code for the save topic reads like this:

Private Sub SaveData()
    If String.IsNullOrWhiteSpace(txtTitle.Text) Then
        'Validate that we have a title
        MessageBox.Show("You need to provide a title for your topic!", "Wiki demo", MessageBoxButtons.OK, MessageBoxIcon.Error)
        txtTitle.Focus()
        Return
    End If

    Dim dr() As DataRow = mdataset.Tables("HelpTable").Select("Code = '" + txtTitle.Text + "'")

    If dr.Length > 0 Then
        'This topic already exist, update it
        dr(0).Item("HelpText") = txtWiki.Rtf
    Else
        'This topic is new, create it
        Dim drNew As DataRow = mdataset.Tables("HelpTable").NewRow
        drNew.Item("Code") = txtTitle.Text
        drNew.Item("HelpText") = txtWiki.Rtf
        mdataset.Tables("HelpTable").Rows.Add(drNew)
    End If
End Sub

When a topic is selected in the Listbox control on the left, the LoadFormData method is called. This method tries to find the selected item in the dataset and fill the textboxes with the values:

Private Sub LoadFormData(ByVal pCurrentForm As String)
    Dim dr() As DataRow = mdataset.Tables("HelpTable").Select("Code = '" + pCurrentForm + "'")

    If dr.Length > 0 Then
        'This topic already exist
        txtTitle.Text = dr(0).Item("Code").ToString
        txtWiki.Rtf = dr(0).Item("HelpText").ToString
    Else
        'This topic is new
        txtTitle.Text = "New topic"
        txtWiki.Text = "No help is currently available for this topic"
    End If
End Sub

This code so far should be very easy and know from you. The fun part starts here!

Generating the Word document

As already said, we won’t be using Office automation to generate the Word document. Instead, I rely on Aspose.Words for .Net.

So when you click the “Generate Word doc” button, a lot of things are happening:

  • A new Aspose.Words document object is created
  • A new document builder is created. This builder is a helper object to create your various parts in your document.
  • Some header information is added to the newly created document. Those headers are sent as HTML code (using InsertHTML) just to show how easy it is to create new parts (paragraphs in this case) using simple HTML code.
  • Then a table of contents is inserted using the InsertTableOfContents method. The switches are explained in the documentation
  • Then we can loop through the various rows of the dataset
    • The title is added to the document using a “Heading1” formatting so that it is automatically added to the table of contents
    • The text of the topic, which is in RTF, is first converted from RTF to the Document format and then added to the document
  • The table of content is updated (otherwise it remains empty)
  • The document is saved in Word format
  • To document is opened in Word

The full code of this method reads like this:

Private Sub GenerateBooklet()
    'Creates an Aspose.Words document object
    Dim doc As New Document()
    'Creates a DocumentBuilder helper object attached to the current document
    Dim builder As New DocumentBuilder(doc)

    'Using HTML, creates some titles
    builder.InsertHtml($"<p>Help guide for <b>{AssemblyTitle}</b></p>")
    builder.InsertHtml($"<p>For the version {AssemblyVersion}</p>")
    builder.InsertHtml($"<p>{AssemblyCopyright}</p>")
    builder.InsertHtml($"<p>Document created on {DateTime.Now.ToString("yyyy-MM-dd @ HH:mm", CultureInfo.InvariantCulture)}</p>")
    builder.InsertHtml($"<p></p>")
    builder.InsertHtml($"<p></p>")
    builder.InsertHtml($"<p></p>")
    builder.InsertHtml($"<p><u>Table of content</u></p>")

    'Insert a table of contents at the beginning of the document.
    builder.InsertTableOfContents("\o ""1-3"" \h \z \u")

    For Each row As DataRow In mdataset.Tables(0).Rows
        '// Start on a New page.
        builder.InsertBreak(BreakType.PageBreak)

        '// Insert a TC field at the current document builder position.
        builder.ParagraphFormat.StyleIdentifier = StyleIdentifier.Heading1
        builder.Writeln(row.Item("Code").ToString())

        Dim rtfdoc As Document = RtfStringToDocument(row.Item("HelpText").ToString())
        If rtfdoc IsNot Nothing Then
            builder.InsertDocument(rtfdoc, ImportFormatMode.KeepSourceFormatting)
        End If
    Next

    'The newly inserted table of contents will be initially empty.
    'It needs to be populated by updating the fields in the document.
    doc.UpdateFields()

    'Save the generated Word document
    Dim strFileName As String = Path.Combine(Path.GetTempPath(), "UserGuide.docx")
    doc.Save(strFileName)

    'Open the Word document in Word
    Process.Start(strFileName)
End Sub

There are some helper methods and properties that you will find in the downloadable demo.

Need to generate a PDF instead?

Would you like to produce a pdf file instead to prevent user from easily modifying the generated document? It couldn’t more easy. Just change the file extension (from .docx to .pdf) and you are done. This is really a nice feature from the Aspose component.

Conclusion

If I am not mistaken, it is the first time I use a commercial component in one of my article.

Of course, almost everything here could have been done using Office automation but I had my lot of troubles with it in the last years and I have decided a long time ago to never push that again.

Now my users can easily generate a Word (or PDF) document from there wiki-based help system.


(Print this page)