Showing posts with label WordProcessingML. Show all posts
Showing posts with label WordProcessingML. Show all posts

Section breaks in Office Open XML and WordML

Identifying the section breaks in pure Office Open XML (OOXML) document is the ultimate nightmare: the only indication of a section break is the presence of w:sectPr element within the last paragraph of the section. To complicate matters further, the last section in the document could be represented by a w:sectPr element that is a sibling to the w:p elements … and of course you could have elements without sections, in which case there would be no w:sectPr element anywhere within the XML. Just try to imagine writing an XSLT translation that would perform OOXML-to-HTML translation and split the Word text into DIVs (a DIV for each section).

Fortunately, the task is much easier if you use WordML, which contains auxiliary hints in the wx namespace; in our case, the wx:sect element, which encloses all the paragraphs within a section.

For example, the following Word text …… generates this WordML markup (to get the corresponding OOXML, remove the wx:sect elements).

Spelling errors might break your WordML translations

The code that generates a single PRE block from multiple Word paragraphs worked perfectly … until Word decided I made a spelling error in one of the listings. Based on where it thinks the error is, Word can generate the w:proofErr element between w:p elements, thus breaking my code which assumed that the w:p elements are adjacent:

To fix this problem, I had to change following-sibling::*[1] expression into following-sibling::w:p[1] throughout the affected code. The fixed templates are included below:

<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']">
<xsl:if test="not(preceding-sibling::w:p[1]/w:pPr/w:pStyle/@w:val = 'code')">
<pre class='{w:pPr/w:pStyle/@w:val}'>
<xsl:apply-templates select='.' mode="pre" />
</pre>
</xsl:if>
</xsl:template>

<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']" mode="pre">
<xsl:apply-templates />
<xsl:if test="following-sibling::w:p[1]/w:pPr/w:pStyle/@w:val = 'code'">
<xsl:text>&#x0a;</xsl:text>
<xsl:apply-templates mode="pre" select="following-sibling::w:p[1]" />
</xsl:if>
</xsl:template>

<xsl:template match="*" mode="pre" />

WordML: Translate spaces in fixed-font text

I had problems with Blogger formatting, so I’ve decided to translate all fixed-font spaces in my Word texts into non-breakable spaces in translated Blogger-ready HTML. To do this conversion, I had to find the font of the current range … but it could be stored in the range properties, character style or paragraph style, and the range property could use a proportional font that overrides the character- or paragraph style fixed font.

I’ve defined the xsl:key instructions that extract the paragraph or character style fonts …

<xsl:key name="parafont" match="w:rFonts/@w:ascii" 
use="ancestor::w:style[@w:type = 'paragraph']/@w:styleId" />
<xsl:key name="rangefont" match="w:rFonts/@w:ascii"
use="ancestor::w:style[@w:type = 'character']/@w:styleId" />

… and used them in pretty complex (as it has to handle so many cases) xsl:choose statement in the w:t (text-within-range) template:

<xsl:template match="w:t/text()">
<xsl:variable name="pfont"
select="key('parafont',ancestor::w:p/w:pPr/w:pStyle/@w:val)" />
<xsl:variable name="rfont"
select="key('rangefont',ancestor::w:r/w:rPr/w:rStyle/@w:val)" />
<xsl:variable name="font" select="w:rPr/w:rFonts/@w:ascii" />
<xsl:variable name="xlate" select="translate(.,' ','&#160;')" />
<xsl:choose>
<xsl:when test="contains($font,'Courier')">
<xsl:value-of select="$xlate" /></xsl:when>
<xsl:when test="string($font) != ''">
<xsl:value-of select="." /></xsl:when>
<xsl:when test="contains($rfont,'Courier')">
<xsl:value-of select="$xlate" /></xsl:when>
<xsl:when test="string($rfont) != ''">
<xsl:value-of select="." /></xsl:when>
<xsl:when test="contains($pfont,'Courier')">
<xsl:value-of select="$xlate" /></xsl:when>
<xsl:otherwise><xsl:value-of select="." /></xsl:otherwise>
</xsl:choose>
</xsl:template>

WordML: Extract font from paragraph style

The paragraph styles in WordProcessingML have paragraph properties (w:pPr element) and range properties (w:rPr element). The font of a paragraph style is stored in the range properties (w:rPr/w:rFonts/@w:ascii element), as shown in the following snapshot from XML Notepad:

The way to extract font name from paragraph style based on the style name is very similar to the character style case: define an xsl:key definition that matches paragraph style (w:type = 'paragraph') with the given name and extract the font name.

<xsl:key name="parafont" match="w:rFonts/@w:ascii" →
use="ancestor::w:style[@w:type = 'paragraph']/@w:styleId" />

WordML: generate PRE element from multiple paragraphs

In my Word-to-Blogger converter, I wanted to convert a block of paragraphs with the code style into a single PRE element. As Word does not generate a grouping of paragraphs of the same style, I had to develop a pretty convoluted solution:

  • A special template matches paragraphs with the code style.
  • The template performs the translation only if the preceding paragraph does not have the same style. This ensures the subsequent paragraphs with the code style don’t generate extra PRE elements.
  • The template generates the PRE element and sends the current element through another translation using pre mode.
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']">
<xsl:if test="not(preceding-sibling::w:p[1]/w:pPr/w:pStyle/@w:val = 'code')">
<pre class='{w:pPr/w:pStyle/@w:val}'>
<xsl:apply-templates select='.' mode="pre" />
</pre>
</xsl:if>
</xsl:template>

The paragraph matching with mode=’pre’ is quite simple:

  • Child elements are processed (producing translated paragraph text).
  • If the following sibling has the code style (we haven’t reached the end of the PRE block), a newline element is appended with the xsl:text instruction and the sibling (the next code paragraph) is translated with mode=’pre’.
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']" mode="pre">
<xsl:apply-templates />
<xsl:if test="following-sibling::*[1]/w:pPr/w:pStyle/@w:val = 'code'">
<xsl:text>&#x0a;</xsl:text>
<xsl:apply-templates mode="pre" select="following-sibling::*[1]" />
</xsl:if>
</xsl:template>

The default template for mode=’pre’ is empty, ensuring that non-paragraph entities accidentally translated with mode=’pre’ do not generate output text.

<xsl:template match="*" mode="pre" />

The solution could be made easier if I would have used the wx:pBdrGroup element that Word inserts around my code paragraphs (I’ve configured a border on the paragraph style), but the wx:pBdrGroup-based approach would fail if someone decided to change the border of the code style.

WordML: extract font from character style

The font of a character style (the style of a range of text, not the whole paragraph) is stored in the w:styles/w:style/w:rPr/w:rFonts/@w:ascii element, as shown in the following snapshot from XML Notepad:

To get the character font from the style name, use the following xsl:key definition:

<xsl:key name="rangefont" match="w:rFonts/@w:ascii" →
use="ancestor::w:style[@w:type = 'character']/@w:styleId" />

You should check the style type in the key definition to ensure that the key matches only the character styles.

Later on, you can use the rangefont key to check the font in a range-matching template. For example, to check whether the current range has courier font, use the following xsl:choose block:

<xsl:choose>
<xsl:when test="contains(w:rPr/wx:font/@wx:val,'Courier')">
<!-- fixed-font specified in the range -->
</xsl:when>
<xsl:when test="contains(key('rangefont',w:rPr/w:rStyle/@w:val),'Courier')">
<!-- fixed-font specified in the character style -->
</xsl:when>
<xsl:otherwise>
<!-- not a fixed font -->
</xsl:otherwise>
</xsl:choose>

Open XML text file in Microsoft Word

The Word-to-Wiki converter macro I’ve described in one of the previous posts (developed in Word 2003) worked perfectly, but when I wanted to add a Word-to-Blogger macro (along the lines of Word-to-Wiki concept, but with a different XSLT), things got complex. I didn’t want to have any whitespace between P tags generated by XSLT (Blogger interprets whitespace line breaks as implicit <br /> tags), so I wanted to generate XML, not HTML … only to find out that the default text converter used by Microsoft Word (wdOpenFormatAuto) …

Documents.Open FileName:=TxtPath, ConfirmConversions:=False, _
ReadOnly:=False, AddToRecentFiles:=False, _
Format:=wdOpenFormatAuto, Encoding:=65001

… removes the XML tags (leaving only the text nodes) when importing XML files as text. Next I’ve tried the the wdOpenFormatText converter, only to find out that it cannot handle Unicode text. Great news … Finally I’ve managed to get exactly what I needed with the wdOpenFormatUnicodeText converter and msoEncodingUTF8 encoding:

Documents.Open FileName:=TxtPath, ConfirmConversions:=False, _
ReadOnly:=False, AddToRecentFiles:=False, _
Format:=wdOpenFormatUnicodeText, Encoding:=msoEncodingUTF8

When boolean logic has a maybe value

When doing Word-to-Blogger conversion, I wanted to identify a group of code paragraphs and convert them into a single PRE element (Word-to-MediaWiki is simpler, as you just prepend a single space to the text and MediaWiki merges all code lines together). The original idea was pretty simple:
  • Match w:p elements with w:pPr/w:style/@w:val = 'code';
  • Check if the previous w:p element also has code style, if not, emit the PRE element and handle the whole group of code paragraphs.

The initial code worked ...

<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']">
  <xsl:if test="preceding-sibling::w:p[1]/w:pPr/w:pStyle/@w:val != 'code'">
    <pre class='{w:pPr/w:pStyle/@w:val}'>
      <xsl:apply-templates select='.' mode="pre" />
    </pre>
  </xsl:if>
</xsl:template>

... but only until I've decided to add a border around the code style in Word. The border creates a wx:pBdrGroup in WordProcessingML ...

... and the xsl:if test fails if there is no preceding w:p element. To make the code work, I had to reverse the test condition and add a not at the beginning:

<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']">
  <xsl:if test="not(preceding-sibling::w:p[1]/w:pPr/w:pStyle/@w:val = 'code')">
... rest of code ...
  </xsl:if>
</xsl:template>

Automatic generation of HTML/WikiMarkup from Word

After developing the XSLT translations that convert WordProcessingML (Microsoft Word XML) into desired HTML or MediaWiki markup, I wanted to integrate them tightly into Word, so I wrote a Word macro that automatically:
  • Performs the conversion using the specified XSLT file;
  • Opens the new document as text;
  • Selects the converted text and copies it to the clipboard;
  • Reopens the original file;
  • Informs the user that the clipboard contains converted text, ready to be pasted into Blogger or MediaWiki.

Here is the source code for the macros:

Sub SaveAsWiki()
'
' SaveAsWiki Macro
' Save Word document as Wiki markup
'
    ConvertWithXSL "createWikiMarkup.xsl"
    MsgBox "The Wiki Markup is on the clipboard", vbOKOnly, "Done"
End Sub

Sub SaveAsBlogger()
'
' SaveAsBlogger: save word document as blogger-optimized HTML markup
'
    ConvertWithXSL "createBloggerMarkup.xsl"
    MsgBox "The Blogger Markup is on the clipboard", vbOKOnly, "Done"
End Sub

Sub ConvertWithXSL(XSLFile As String)
'
' ConvertWithXSL: converts the Word document with specified XSL
'
    Dim XPath, DocName, TxtPath
    
    XPath = ActiveDocument.AttachedTemplate.Path & _
            Application.PathSeparator & XSLFile
    
    TxtPath = Environ("TEMP")
    If TxtPath <> "" Then TxtPath = TxtPath & "\"
    TxtPath = TxtPath & "wmk.txt"
    
    DocName = ActiveDocument.FullName
    If MsgBox("The conversion process will lose all changes you've made. " & _
              "You have to save the document before running the conversion. " & _
              "Did you do it?", vbYesNo, "Warning") <> vbYes Then Exit Sub
              
    With ActiveDocument
        .XMLSaveDataOnly = False
        .XMLUseXSLTWhenSaving = True
        .XMLSaveThroughXSLT = XPath
        .XMLHideNamespaces = False
        .XMLShowAdvancedErrors = False
        .XMLSchemaReferences.HideValidationErrors = False
        .XMLSchemaReferences.AutomaticValidation = True
        .XMLSchemaReferences.IgnoreMixedContent = False
        .XMLSchemaReferences.AllowSaveAsXMLWithoutValidation = True
        .XMLSchemaReferences.ShowPlaceholderText = False
    End With
    ActiveDocument.SaveAs _
        FileName:=TxtPath, FileFormat:=wdFormatXML, _
        AddToRecentFiles:=False
    ActiveDocument.Close
    
    Documents.Open FileName:=TxtPath, ConfirmConversions:=False, _
        ReadOnly:=False, AddToRecentFiles:=False, _
        Format:=wdOpenFormatAuto, Encoding:=65001
    Selection.WholeStory
    Selection.Copy
    ActiveDocument.Close
    
    Documents.Open FileName:=DocName
End Sub

XSLT: Generate heading from WordProcessingML

Microsoft Word (and WordProcessingML) treats all paragraphs equally (all of the are represented as w:p elements) and it's the job of the XSLT programmer to separate headings (the paragraphs where the paragraph style contains w:pPr/w:outlineLvl/@w:val) from the regular text.

I've started by defining a key that would extract the outline level from the paragraphs style associated with the current w:p element:
<xsl:key name="outline" match="w:outlineLvl" use="ancestor::w:style[@w:type = 'paragraph']/@w:styleId" />
If successful, this key would return the w:outlineLvl element whose ancestor w:style element has the specified w:styleId attribute (and, yes, it took me five minutes to figure out what I've been doing when I've revisited the code after three months). The key is then used in a template that uses xsl:element to create a Hx or P element:
<xsl:template match="w:p">
  <xsl:variable name="paraStyle" select="w:pPr/w:pStyle/@w:val" />
  <xsl:variable name="outLvl"
    select="key('outline',w:pPr/w:pStyle/@w:val)/@w:val" />
  <xsl:variable name="elName">
    <xsl:choose>
      <xsl:when test="$outLvl">h<xsl:value-of select="$outLvl + 1" /></xsl:when>
      <xsl:otherwise>p</xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <xsl:element name="{$elName}">
... rest of translation code ...
  </xsl:element>
</xsl:template>

XSLT: Use specific matches instead of xsl:choose

The if-then-else construct in XSLT 1.0 is "somewhat baroque", it's thus easier (and probably also faster) to use other methods to select various alternatives, for example the specific matches in the xsl:template match parameter.

In one of my recent WordProcessingML (Microsoft Word XML) XSLT projects I had to transform paragraphs with the cite style into blockquote, those with code style into pre and the rest of them into regular paragraphs. Instead of using a complex xsl:choose structure, I've defined three templates:
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'cite']">
  <blockquote class="{w:pPr/w:pStyle/@w:val}">
    <xsl:apply-templates />
  </blockquote>
</xsl:template>
 
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'code']">
  <pre class="{w:pPr/w:pStyle/@w:val}">
    <xsl:apply-templates />
  </pre>
</xsl:template>
 
<xsl:template match="w:p">
... regular code goes here ...
</xsl:template>

XSLT: Extract file name from path

When transforming the WordProcessingML documents, I wanted to output an IMG element that would contain the HREF attribute equal to the picture name used in the Word file (assuming the picture is linked, not just inserted). However, the picture name in the Word file usually contains whole path (even when the picture and the Word document are in the same directory), so I needed a function that would extract the file name from the path. As XSLT 1.0 has very limited function set (and MSMXL still doesn't support XSLT 2.0), I had to write the function myself using a named template:
<xsl:template name="fileName">
  <xsl:param name="path" />
  <xsl:choose>
    <xsl:when test="contains($path,'\')">
      <xsl:call-template name="fileName">
        <xsl:with-param name="path" select="substring-after($path,'\')" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="contains($path,'/')">
      <xsl:call-template name="fileName">
        <xsl:with-param name="path" select="substring-after($path,'/')" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$path" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The function recognizes Windows- and Unix-style paths (forward and backward slashes are recognized as path delimiters).

Word to MediaWiki conversion with XSLT

I've tried two Word to MediaWiki converters: the set of macros described in the Word2MediaWikiPlus extension and the OpenOffice converter. The OpenOffice converter does not work too well (for example, if you have a paragraph style with COURIER font, it's not transformed into MediaWiki code markup) and I needed some extensions that would be hard to cram into Word macros used by Word2MediaWikiPlus, so I decided to implement the converter as an XSLT translator (usable in Word 2003/2007) that should be easy(er) to modify for someone fluent in XSLT. You can find the current (pre-alpha) sources on SourceForge and download the alpha release. If I'm missing a functionality you desperately need, let me know.

Soft breaks in WordProcessingML

The WordProcessingML has an “interesting” way of representing soft breaks (Ctrl-Enter in Word): if a range (w:r, also called run) has a w:br child, it represents a soft break at that position (see also Section 2.3.3 of Part 4 of ECMA-376). Here is the XML Notepad display of a sample three-line paragraph (with two soft breaks):To process the soft break in a range with XSLT, use templates similar to these:
<xsl:template match="w:r">
  <xsl:apply-templates />
</xsl:template>

<xsl:template match="w:t"><xsl:value-of select="text()" /></xsl:template>

<xsl:template match="w:br|w:cr"><br /></xsl:template>

Using WordProcessingML to Generate Clean HTML from Word

Generating clean and simple HTML output from Microsoft Word is very difficult if you rely on its "Save As Web Page" feature. In my InformIT article, Using WordProcessingML to Generate Clean HTML from Word, I'm describing how you can use WordProcessingML together with XSL transformations to generate clean, strict (X)HTML-compliant documents from Word sources.