Search code examples
xsltms-wordbibliography

How to check a word bibliography reference entry and display an error if it is not filled in by editing the XSL style


With reference to the following to the following guide provided by MS: MS guide to editing bibliography styles and the IEEE reference style IEEE xsl style file. I am customising the IEEE bibliography style file for word 365 and want to display a message to a user if the user does not fill in an important field. For the book type the following fields are required to be filled in. The following snippet changes to the Book part works to display the required Book fields:

<!-- Variable containing all necessary data for a certain style of bibliography. -->
  <xsl:variable name="data">
   <general>
      <stylename>IEEE - Reference Order</stylename>
      <version>2009.05.23</version>
      <description>An implementation of the IEEE style.</description>
      <URL></URL>
      <author>Yves Dhondt ([email protected])</author>
      <comments></comments>
      <display_errors>yes</display_errors>
      <citation_as_link>yes</citation_as_link>
    </general>
    <importantfields>
      <source type="Book">
        <b:ImportantField>b:Title</b:ImportantField>
        <b:ImportantField>b:StandardNumber</b:ImportantField>
        <b:ImportantField>b:Edition</b:ImportantField>
        <b:ImportantField>b:URL</b:ImportantField>
      </source>
  </xsl:variable>

But my changes to do display an error when a user forgets to enter an important field results in an empty bibliograhy:

<!-- Check if required book fields are populated -->
<xsl:template match="b:Source[b:SourceType = 'Book']">
    <xsl:variable name="Edition">
        <xsl:when test="string-length($Edition)=0">
            <xsl:text>Enter Revision</xsl:text>
            <xsl:value-of select="$Edition" />
        </xsl:when>
    </xsl:variable>
</xsl:template>

Either my xsl is wrong, likely as I am new to xsl or I am not getting the correct placement in the overall xsl file or both!


Solution

  • [NB, I've done a bit more work on this, but it's still just a stepping stone - see at the bottom of the Answer for a new version of the key piece of code]

    This may get you started (I am also not that familiar with XSLT) but finding a good place to make the necessary modifications is tricky because of the general approach used in the existing XSL code. There are some notes below.

    Look for the following template

    <xsl:template name="format-bibliography-table-column">
    

    Below that, look for this code:

        <!-- Else go for the source type element if available. -->
        <xsl:when test="string-length(msxsl:node-set($data)/bibliography/source[@type = $sourcetype]/column[@id = $columnId]/format) > 0 ">
          <xsl:value-of select="msxsl:node-set($data)/bibliography/source[@type = $sourcetype]/column[@id = $columnId]/format"/>
        </xsl:when>
    

    and change it to

       <xsl:when test="string-length(msxsl:node-set($data)/bibliography/source[@type = $sourcetype]/column[@id = $columnId]/format) > 0 ">
          <xsl:if test = "msxsl:node-set($data)/general/display_errors = 'yes'">
            <xsl:choose>
              <xsl:when test="$sourcetype = 'Book'" >
                <xsl:choose>
                  <xsl:when test="string-length($source/b:Edition) > 0" />
                  <xsl:otherwise>
                    <xsl:choose>
                      <!-- Put an asterisk before the source id ([1] etc.).
                           If you do more tests, you may prefer to modify the code so that only one asterisk is inserted.
                           Probably useful to put the Source's tag in the output too.
                      -->
                      <xsl:when test="$columnId = 1">
                        <xsl:text>&lt;b&gt;*&lt;/b&gt;</xsl:text>
                      </xsl:when>
                      <xsl:otherwise>
                        <xsl:text>&lt;b&gt;Edition is missing. &lt;/b&gt;</xsl:text>
                      </xsl:otherwise>
                    </xsl:choose>
                  </xsl:otherwise>
                </xsl:choose>
              </xsl:when>
            </xsl:choose>
          </xsl:if>
          <xsl:value-of select="msxsl:node-set($data)/bibliography/source[@type = $sourcetype]/column[@id = $columnId]/format"/>
    

    and try that. If you have a missing or empty Edition field in a Book citation you should see AN asterisk before the ID ("[1]" etc., then "Edition is missing. " in bold at the very beginning of the relevant entry in the Bibliography when you next generate it.

    I've chosen that particular place in the code because I think this stylesheet will by default use the format-bibliography-table-column template and not the format-bibliography-as-paragraphs template, and Word itself passes the Source Type ("Book" etc.) in a b:SourceType element, not (in most cases) the b:Type element.

    (Yves Dhondt's "BibWord" system can be used in a way that re-purposes b:Type to hold the Source Type because that allowed him to introduce additional Source Types, but AFAIK that feature is not actually used when Word calls this StyleSheet.)

    If you don't see the change in a suitable test source, you may need to copy this code, with one change, to the 3 other places where the Source Type is checked.

    One of those is in the xsl:when test immediately above this one, where you could put similar code but you would change

              <xsl:when test="$sourcetype = 'Book'" >
    

    to

              <xsl:when test="$type = 'Book'" >
    

    and the other two places are in the equivalent section in the format-bibliography-as-paragraphs template (where the existing code is also slightly different).

    Some notes and warnings:

    (For XSLT experts: Word requires XSLT 1.0 and XPath 1.0, plus some extensions such as msxsl:node-set() . Can't use XSLT 2 or later. As far as I can tell it is also not possible to use the msxsl:script environment to develop your own extensions - Word won't run them.)

    You have to install a copy of the Stylesheet on every computer where you need to be able to regenerate this bibliography.

    The reason it's quite difficult to find the correct place to make this change is that the XSL constructs the HTML in two phases - one, where it selects the format it needs to use depending on the Source Type and the type and location of the output ((a paragraph, column 1 of a table or column 2 of a table). The "format is just a "pattern" - later on, the actual values from the Source are inserted into the pattern, cf. a printf() function call in the C language. e.g. the code I've written just inserts text into the variable called "format". It doesn't output any HTML.

    I've only had time to get this one thing working - presumably you want to test the presence of other "ImportantItems". Not sure how I would extend this code to do that, but it would probably make sense to pick up the set of ImportantItems related to a particular Source Type via an xsl:for-each.

    Worth noticing that the reason why trying to insert your code in a separate template that tries to match a source type is that the StyleSheet is what is sometimes called a "fill in the blanks" or "navigational" Stylesheet, where, in essence, a single template starts at the top of the XML document it receives and picks out what it needs, calling other templates as required. You can see this because there's only one "match" in the entire StyleSheet - <xsl:template match="/">. Also, as others have noted, populating a variable "Edition" won't output anything to HTML unless you do that in a separate step.

    Updated code as follows, with notes after that.

          <xsl:if test = "msxsl:node-set($data)/general/display_errors = 'yes'">
            <xsl:variable name="missing">
              <xsl:for-each select="msxsl:node-set($data)/importantfields/source[@type = $sourcetype]/b:ImportantField">
                <xsl:variable name="importantfield" select="." />
                <xsl:if test="string-length($source/*[name() = $importantfield]) = 0" >
                  <missingfield>
                    <xsl:value-of select="$importantfield"/>
                  </missingfield>
                </xsl:if>
              </xsl:for-each>
            </xsl:variable>
            <xsl:if test="$missing !=''">
              <!-- soe hard coded assumptions about columns here. Should really add to the metadata in data/bibliography/columns and work with that-->
              <xsl:choose>
                <xsl:when test="$columnId = 1">
                  <xsl:text>&lt;b&gt;*&lt;/b&gt;</xsl:text>
                </xsl:when>
                <xsl:when test="$columnId = 2">
                  <xsl:text>&lt;b&gt;Fields missing from Source with ID &quot;</xsl:text>
                  <xsl:value-of select="$source/b:Tag" />
                  <xsl:text>&quot;: </xsl:text>
                  <xsl:variable name="missingfieldcount" select="count(msxsl:node-set($missing)/missingfield)" />
                  <xsl:for-each select="msxsl:node-set($missing)/missingfield">
                    <xsl:value-of select="."/>
                    <xsl:if test="position() != last()">
                      <xsl:text>; </xsl:text>
                    </xsl:if>
                  </xsl:for-each>
                  <xsl:text>. &lt;/b&gt;</xsl:text>
                </xsl:when>
              </xsl:choose>      
            </xsl:if>
          </xsl:if>          
          <xsl:value-of select="msxsl:node-set($data)/bibliography/source[@type = $sourcetype]/column[@id = $columnId]/format"/>
    

    Note: The detection of empty/missing Authors, Editors etc. isn't right, because test="string-length($source/*[name() = $importantfield]) probably won't work at all where importantfield contains a multi-step path (e.g. b:Author/b:Author/b:NameList ) and the test for emptiness is probably incorrect. I'm sure someone more familiar with XSL could fix those problems, but it's possible that yet more complexity is required to do the checks properly.

    Since I'm not that person, I'd probably try to make it simpler for myself by introducing a new set of "things to test" along the lines of the "importantfields" list but with a view to making it much simpler to test and easier to output text without all those "b:" s.