Dusting off the Résumé, Part 2: Converting to HTML

Earlier, I shared with you the Schema I use for writing my Résumé in XML. In this post, I want to talk about how that XML becomes something human-readable: Hyper Text Markup Language (HTML).

I wrote my first Web Page in 1993, one year after Tim Berners-Lee released the now ubiquitous language of the Internet. Back then, to get information on the Internet you were restricted to dial-up Bulletin Board Systems (BBS), Gopher and Archie for file search and retrieval, and Usenet for staying abreast of the latest computer news or just chew the fat. I was, in fact, a very early adopter, immediately seeing the potential of HTML to revolutionize the world of digital communications.

What’s more, unlike JavaScript Object Notation (JSON), which is best for HTML POST requests and related operations, because HTML is a markup language and a full subset of XML, it makes more sense to encode my work history and Curriculum Vitae (CV). This is why I continue to maintain my Résumé and the longer form CV in XML and don’t convert it to JSON.

That said, it would be fascicle to convert XML to JSON and many application already do this.

The translation from XML to HTML, however, takes more finesse. Fortunately, there’s a subset of XML known as eXtensible Stylesheet Language Transformation (XSLT). XSLT is a rudimentary rule-based language with recursion. It allows me to iterate over fields and concatenate them with commas (,), or hide fields which are deprecated as no longer relevant to the modern software job market.

As such, XSLT is a very verbose language which requires many elements to define how each XML component will be handled. Nonetheless, the full transform is included below.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:param name="amazon" select="'http://www.amazon.com/exec/obidos/ASIN/'"/>
<xsl:param name="deprecated_text" select="'Hidden text has been excized from this document!'"/>
<xsl:param name="redacted_text" select="'Redacted text has been blacked out from this document!'"/>
<xsl:param name="col_of_data" select="2"/>

<xsl:variable name="deprecated">
  <span class="deprecated">deprecated</span>
  <xsl:comment>
    <xsl:value-of select="$deprecated_text"/>
  </xsl:comment>
</xsl:variable>

<xsl:variable name="redacted">
  <span class="redacted">redacted</span>
  <xsl:comment>
    <xsl:value-of select="$redacted_text"/>
  </xsl:comment>
</xsl:variable>

<!-- Entities -->
<!-- Question: How do I get Entities to appear as entities?? -->

<!-- Pure (PCDATA) Elements -->
<!-- Names and Addresses -->

<xsl:template match="name">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="oldname">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="street">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="apartment">
  <xsl:choose>
    <!-- Use param / param-with -->
    <xsl:when test="@spellout = 'true'">
      <xsl:text disable-output-escaping="yes">Apartment&amp;nbsp;</xsl:text>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text disable-output-escaping="yes">Apt.&amp;nbsp;</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="city">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="state">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="province">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="postal">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="country">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="e-mail">
  <a href="mailto:{.}">
    <xsl:choose>
      <xsl:when test="parent::address">
        <xsl:attribute name="class">
          <xsl:text>address</xsl:text>
        </xsl:attribute>
      </xsl:when>
      <xsl:when test="parent::reference">
        <xsl:attribute name="class">
          <xsl:text>reference</xsl:text>
        </xsl:attribute>
      </xsl:when>
    </xsl:choose>
    <xsl:apply-templates/>
  </a>
</xsl:template>

<xsl:template match="phone">
  <xsl:apply-templates/> (H)
</xsl:template>

<xsl:template match="mobile">
  <xsl:apply-templates/> (M)
</xsl:template>
<!-- Keys and Headings -->

<xsl:template match="heading">
  <hr class="separator"/>
  <h2 class="heading">
    <xsl:apply-templates/>
  </h2>
</xsl:template>

<xsl:template match="key">
  <span class="text_heading">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="value">
  <xsl:apply-templates/>
</xsl:template>

<!-- Date and Time -->

<xsl:template match="year">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="month">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="day">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="hour">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="minute">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="second">
  <xsl:apply-templates/>
</xsl:template>

<!-- Descriptive Elements -->

<xsl:template match="position">
  <span class="position">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="product">
  <span class="product">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="degree">
  <span class="degree">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="course">
  <span class="course">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="author">
  <span class="author">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="author-et-al">
  <xsl:text> </xsl:text><span class="author"><em>et al</em></span>
</xsl:template>

<xsl:template match="publisher">
  <span class="publisher">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="subject">
  <p class="subject">
    <xsl:apply-templates/>
  </p>
</xsl:template>

<xsl:template match="isbn">
  <a class="isbn" href="{$amazon}{.}/">
    <xsl:apply-templates/>
  </a>
</xsl:template>

<!-- deprecated -->

<xsl:template match="deprecated">
  <!-- Ingore deprecated text -->
  <xsl:copy-of select="$deprecated"/>
</xsl:template>

<!-- Compound Elements -->
<!-- Compound Time -->

<xsl:template match="date">
  <span class="date">
    <xsl:if test="day">
      <!-- European Date Format -->
      <xsl:apply-templates select="day"/>
      <!-- day requires month, so we know that the month is next -->
      <xsl:text> </xsl:text>
    </xsl:if>
    <xsl:if test="month">
      <xsl:apply-templates select="month"/>
    </xsl:if>
    <xsl:if test="year">
      <xsl:if test="month">
        <xsl:text> </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="year"/>
    </xsl:if>
    <xsl:if test="hour">
      <xsl:if test="not(*[position() = 1 and self::hour])">
        <xsl:text> </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="country"/>
    </xsl:if>
    <xsl:if test="minute">
      <!-- Always follows hour -->
      <xsl:text>:</xsl:text>
      <xsl:apply-templates select="minute"/>
    </xsl:if>
    <xsl:if test="second">
      <!-- Always follows minute -->
      <xsl:text>:</xsl:text>
      <xsl:apply-templates select="second"/>
    </xsl:if>
  </span>
</xsl:template>

<xsl:template match="from">
  <span class="from_date">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="to">
  <span class="to_date">
    <xsl:apply-templates/>
  </span>
</xsl:template>

<xsl:template match="period">
  <xsl:choose>
    <xsl:when test="date">
      <p class="period">
        <xsl:apply-templates select="date"/>
      </p>
    </xsl:when>
    <xsl:when test="from and to">
      <p class="period">
        <xsl:apply-templates select="from"/>
        <xsl:text disable-output-escaping="yes"> &amp;ndash; </xsl:text>
        <xsl:apply-templates select="to"/>
      </p>
    </xsl:when>
  </xsl:choose>
</xsl:template>

<!-- Simple Compound Elements -->

<xsl:template match="address">
  <span class="address">
    <xsl:if test="street">
      <xsl:apply-templates select="street"/>
      <xsl:if test="apartment">
        <xsl:text>, </xsl:text>
        <xsl:apply-templates select="apartment"/>
      </xsl:if>
    </xsl:if>
    <xsl:if test="city">
      <xsl:if test="not(*[position() = 1 and self::city])">
        <xsl:text>, </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="city"/>
    </xsl:if>
    <xsl:choose>
      <xsl:when test="state">
        <xsl:if test="not(*[position() = 1 and self::state])">
          <xsl:text>, </xsl:text>
        </xsl:if>
        <xsl:apply-templates select="state"/>
      </xsl:when>
      <xsl:when test="province">
        <xsl:if test="not(*[position() = 1 and self::province])">
          <xsl:text>, </xsl:text>
        </xsl:if>
        <xsl:apply-templates select="province"/>
      </xsl:when>
    </xsl:choose>
    <xsl:if test="postal">
      <xsl:if test="not(*[position() = 1 and self::postal])">
        <xsl:text> </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="postal"/>
    </xsl:if>
    <xsl:if test="country">
      <xsl:if test="not(*[position() = 1 and self::country])">
        <xsl:text> </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="country"/>
    </xsl:if>
    <xsl:if test="e-mail">
      <xsl:if test="not(*[position() = 1 and self::e-mail])">
        <br/>
      </xsl:if>
      <xsl:apply-templates select="e-mail"/>
    </xsl:if>
    <!-- TODO: Put the phone and mobile phone on the same line. -->
    <xsl:if test="phone">
      <xsl:if test="not(*[position() = 1 and self::phone])">
        <br/>
      </xsl:if>
      <xsl:apply-templates select="phone"/>
    </xsl:if>
    <xsl:if test="mobile">
      <xsl:if test="not(*[position() = 1 and self::mobile])">
        <br/>
      </xsl:if>
      <xsl:apply-templates select="mobile"/>
    </xsl:if>
  </span>
</xsl:template>

<xsl:template match="book">
  <xsl:choose>
    <xsl:when test="not(boolean(@deprecated))">
      <!-- This is a full Book Definition -->
      <p class="bibliography">
        <xsl:if test="author">
          <xsl:for-each select="author">
            <xsl:if test="not(position() = 1)">
              <xsl:text>, </xsl:text>
            </xsl:if>
            <xsl:if test="(position() = last() and not(boolean(../author-et-al)))">
              <xsl:text> and </xsl:text>
            </xsl:if>
            <xsl:apply-templates select="."/>
          </xsl:for-each>
          <xsl:if test="author-et-al">
            <xsl:apply-templates select="author-et-al"/>
          </xsl:if>
          <xsl:text>. </xsl:text>
        </xsl:if>
        <!-- If ISBN is provided, use to link to Amazon -->
        <xsl:choose>
          <xsl:when test="isbn">
            <a class="book" href="{$amazon}{isbn}/">
              <xsl:apply-templates select="name"/>
            </a>
          </xsl:when>
          <xsl:otherwise>
            <span class="book">
              <xsl:apply-templates select="name"/>
            </span>
          </xsl:otherwise>
        </xsl:choose>
        <xsl:if test="publisher | date">
          <xsl:text>. </xsl:text>
        </xsl:if>
        <xsl:if test="address">
          <xsl:apply-templates select="address"/>
          <!-- Publisher must follow -->
          <xsl:text>: </xsl:text>
        </xsl:if>
        <xsl:if test="publisher">
          <xsl:apply-templates select="publisher"/>
          <xsl:if test="date">
            <xsl:text>, </xsl:text>
          </xsl:if>
        </xsl:if>
        <xsl:if test="date">
          <xsl:apply-templates select="date"/>
        </xsl:if>
      </p>
      <xsl:if test="subject">
        <xsl:apply-templates select="subject"/>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="institution">
  <xsl:choose>
    <xsl:when test="not(boolean(@deprecated))">
      <!-- This is a full Institutional Definition -->
      <!-- Normally a Job or School will access the elements
           independently, so this is only bibliographical. -->
      <p class="institution">
        <xsl:choose>
          <xsl:when test="@url">
            <span class="institution">
              <a href="{@url}">
                <xsl:apply-templates select="name"/>
              </a>
            </span>
          </xsl:when>
          <xsl:otherwise>
            <span class="institution">
              <xsl:apply-templates select="name"/>
            </span>
          </xsl:otherwise>
        </xsl:choose>
        <xsl:for-each select="oldname">
          <xsl:choose>
            <xsl:when test="position() = 1">
              <xsl:text> (Formerly </xsl:text>
            </xsl:when>
            <xsl:when test="not(position() = last())">
              <xsl:text>; </xsl:text>
            </xsl:when>
          </xsl:choose>
          <xsl:apply-templates/>
          <xsl:if test="position() = last()">
            <xsl:text>)</xsl:text>
          </xsl:if>
        </xsl:for-each>
        <xsl:if test="address">
          <xsl:text>, </xsl:text>
          <xsl:apply-templates select="address"/>
        </xsl:if>
      </p>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="bookref">
  <xsl:variable name="ref" select="@ref"/>
  <xsl:variable name="book" select="//book[@name = $ref]"/>
  <xsl:choose>
    <xsl:when test="not(boolean($book/@deprecated))">
      <xsl:choose>
        <xsl:when test="$book/@url">
          <a class="bookref" href="{$book/@url}">
            <xsl:apply-templates select="$book/name"/>
          </a>
        </xsl:when>
        <xsl:when test="$book/isbn">
          <a class="bookref" href="{$amazon}{$book/isbn}/">
            <xsl:apply-templates select="$book/name"/>
          </a>
        </xsl:when>
        <xsl:otherwise>
          <span class="bookref">
            <xsl:apply-templates select="$book/name"/>
          </span>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="instref">
  <xsl:variable name="ref" select="@ref"/>
  <xsl:variable name="inst" select="//institution[@name = $ref]"/>
  <xsl:choose>
    <xsl:when test="not(boolean($inst/@deprecated))">
      <!-- This is an Inline Institution, therefore treat as character formatting -->
      <xsl:choose>
        <xsl:when test="$inst/@url">
          <span class="instref">
            <a href="{$inst/@url}">
              <xsl:apply-templates select="$inst/name"/>
            </a>
          </span>
        </xsl:when>
        <xsl:otherwise>
          <span class="instref">
            <xsl:apply-templates select="$inst/name"/>
          </span>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:if test="$inst/address">
        <xsl:text>, </xsl:text>
        <xsl:apply-templates select="$inst/address"/>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<!-- Simple Compound Markups -->

<xsl:template match="buzzword">
  <xsl:choose>
    <xsl:when test="not(boolean(@deprecated))">
      <xsl:choose>
        <xsl:when test="@url">
          <span class="buzzword">
            <a href="{@url}">
              <xsl:apply-templates/>
            </a>
          </span>
        </xsl:when>
        <xsl:otherwise>
          <span class="buzzword">
            <xsl:apply-templates/>
          </span>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="language">
  <xsl:choose>
    <xsl:when test="not(boolean(@deprecated))">
      <span class="language">
        <xsl:apply-templates/>
      </span>
      <!-- Level is Required -->
      <xsl:text> (</xsl:text>
      <xsl:value-of select="@level"/>
      <xsl:text>)</xsl:text>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="status">
  <p class="status">
    <span class="text_heading">
      <xsl:value-of select="key"/>
      <xsl:text>: </xsl:text>
    </span>
    <xsl:apply-templates select="value"/>
  </p>
</xsl:template>

<xsl:template match="skill">
  <xsl:choose>
    <xsl:when test="not(boolean(@deprecated))">
      <p class="skill">
        <span class="text_heading">
          <xsl:value-of select="key"/>
          <xsl:text>: </xsl:text>
        </span>
        <!-- Use for-each to get buzzwords or languages because they
             cannot appear in the same skill set, so if one for-each
             has not element, it is not executed and saves me the trouble
             of writing an xsl:if or writing xsl:for-each
             select="*[self::buzzword | self::langauge]" -->
        <xsl:for-each select="buzzword[not(boolean(@deprecated))]">
          <xsl:if test="not(position() = 1)">
            <xsl:text>, </xsl:text>
          </xsl:if>
          <xsl:apply-templates select="."/>
        </xsl:for-each>
        <xsl:for-each select="language[not(boolean(@deprecated))]">
          <xsl:if test="not(position() = 1)">
            <xsl:text>, </xsl:text>
          </xsl:if>
          <xsl:apply-templates select="."/>
        </xsl:for-each>
      </p>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="curriculum">
  <xsl:choose>
    <xsl:when test="not(boolean(@deprecated))">
      <p class="curriculum">
        <xsl:if test="child::key">
          <span class="text_heading">
            <xsl:value-of select="key"/>
            <xsl:text>: </xsl:text>
          </span>
        </xsl:if>
        <xsl:for-each select="course">
          <xsl:if test="not(position() = 1)">
            <xsl:text>, </xsl:text>
          </xsl:if>
          <xsl:apply-templates select="."/>
        </xsl:for-each>
      </p>
    </xsl:when>
    <xsl:otherwise>
      <!-- Ingore deprecated text -->
      <xsl:copy-of select="$deprecated"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="tools">
  <span class="tools">
    <xsl:for-each select="buzzword[not(boolean(@deprecated))]">
      <xsl:if test="not(position() = 1)">
        <xsl:text>, </xsl:text>
      </xsl:if>
      <xsl:apply-templates select="."/>
    </xsl:for-each>
  </span>
</xsl:template>

<xsl:template match="task">
  <li class="task"/>
  <p class="task">
    <xsl:apply-templates/>
  </p>
</xsl:template>

<xsl:template match="interest">
  <xsl:choose>
    <xsl:when test="@url">
      <a class="interest" href="{@url}">
        <xsl:apply-templates/>
      </a>
    </xsl:when>
    <xsl:otherwise>
      <span class="interest">
        <xsl:apply-templates/>
      </span>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="para">
  <!-- This will typically be overridden by another template -->
  <p>
    <xsl:apply-templates/>
  </p>
</xsl:template>

<xsl:template match="objective">
  <xsl:for-each select="para">
    <p class="objective">
      <xsl:apply-templates/>
    </p>
  </xsl:for-each>
</xsl:template>

<xsl:template match="summary">
  <xsl:for-each select="para">
    <p class="summary">
      <xsl:apply-templates select="."/>
    </p>
  </xsl:for-each>
  <table border="0" width="100%">
    <xsl:for-each select="task">
      <xsl:choose>
        <xsl:when test="position() mod $col_of_data = 1">
          <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text>
        </xsl:when>
        <xsl:otherwise>
          <td>
            <img src="spacer.gif" class="spacer"/>
          </td>
        </xsl:otherwise>
      </xsl:choose>
      <!-- Spacer messes the percentage up! -->
      <td class="task" width="{100 div $col_of_data}%">
        <xsl:apply-templates select="."/>
      </td>
      <xsl:if test="position() mod $col_of_data = 0">
        <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>
      </xsl:if>
    </xsl:for-each>
    <!-- Extra Close Row for odd numbers -->
    <xsl:if test="count(task) mod $col_of_data != 0">
      <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>
    </xsl:if>
  </table>
</xsl:template>

<xsl:template match="achievement">
  <!-- Do the First Paragraph Separately with Tools, then the rest -->
  <!-- Problem with indentation and blank line after -->
  <li class="achievement"/>
  <p class="achievement">
    <xsl:if test="tools">
      <!-- Double Spanning on Tools! -->
      <xsl:apply-templates select="tools"/>
      <xsl:if test="para">
        <span class="tools">
          <xsl:text>: </xsl:text>
        </span>
      </xsl:if>
    </xsl:if>
    <!-- Wacky way of saying "Don't Apply this template,
         apply the ones below! -->
    <xsl:for-each select="para[1]">
      <xsl:apply-templates/>
    </xsl:for-each>
  </p>
  <xsl:for-each select="para[position() &gt; 1]">
    <!-- Convert to Unordered List -->
    <p class="achievement">
      <xsl:apply-templates/>
    </p>
  </xsl:for-each>
</xsl:template>

<xsl:template match="title">
  <span class="title">
    <h1 class="name">
      <xsl:apply-templates select="name"/>
      <xsl:for-each select="oldname">
        <xsl:if test="position() = 1">
          <xsl:text> (</xsl:text>
        </xsl:if>
        <xsl:apply-templates/>
        <xsl:choose>
          <xsl:when test="position() = last()">
            <xsl:text>)</xsl:text>
          </xsl:when>
          <xsl:otherwise>
            <xsl:text>, </xsl:text>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </h1>
    <p class="address">
      <xsl:apply-templates select="address"/>
    </p>
  </span>
</xsl:template>

<xsl:template match="bibliography">
  <xsl:if test="not(boolean(@deprecated))">
    <xsl:if test="heading">
      <xsl:apply-templates select="heading"/>
    </xsl:if>
    <xsl:for-each select="book">
      <xsl:apply-templates select="."/>
    </xsl:for-each>
    <xsl:for-each select="institution">
      <xsl:apply-templates select="."/>
    </xsl:for-each>
  </xsl:if>
</xsl:template>

<xsl:template match="job">
  <xsl:if test="not(boolean(@deprecated))">
    <table width="100%">
      <tr>
        <td>
          <xsl:apply-templates select="institution"/>
        </td>
        <td>
          <img src="spacer.gif" class="spacer"/>
        </td>
        <td>
          <!-- To Do: Please try to if the position is
               present, extend this cell and the spacer
               to rowspan="2" -->
          <!-- Problem: Width of "Period" too small! -->
          <xsl:apply-templates select="period"/>
        </td>
      </tr>
      <xsl:if test="position">
        <tr>
          <td colspan="2">
            <xsl:apply-templates select="position"/>
          </td>
        </tr>
      </xsl:if>
      <xsl:for-each select="achievement">
        <tr>
          <td colspan="2">
            <xsl:apply-templates select="."/>
          </td>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:if>
</xsl:template>

<xsl:template match="school">
  <xsl:if test="not(boolean(@deprecated))">
    <table width="100%">
      <tr>
        <td>
          <xsl:apply-templates select="institution"/>
        </td>
        <td>
          <img src="spacer.gif" class="spacer"/>
        </td>
        <td>
          <!-- To Do: Please try to, if the degree is
               present, extend this cell and the spacer
               to rowspan="2" -->
          <!-- Problem: Width of "Period" too small! -->
          <xsl:apply-templates select="period"/>
        </td>
      </tr>
      <xsl:if test="degree">
        <tr>
          <td colspan="2">
            <xsl:apply-templates select="degree"/>
          </td>
        </tr>
      </xsl:if>
      <xsl:for-each select="achievement">
        <tr>
          <td colspan="2">
            <xsl:apply-templates select="."/>
          </td>
        </tr>
      </xsl:for-each>
      <xsl:for-each select="curriculum">
        <tr>
          <td colspan="2">
            <xsl:apply-templates select="."/>
          </td>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:if>
</xsl:template>

<xsl:template match="reference">
  <xsl:if test="not(boolean(@deprecated))">
    <p class="reference">
      <xsl:apply-templates select="name"/>
      <xsl:if test="position">
        <br/>
        <xsl:apply-templates select="position"/>
      </xsl:if>
      <xsl:if test="instref">
        <!-- Since we know the Institution-Reference requires a
             position, we will add the comma here before the
             institution definition -->
        <xsl:text>, </xsl:text>
        <xsl:apply-templates select="instref"/>
      </xsl:if>
      <xsl:if test="e-mail">
        <br/>
        <xsl:apply-templates select="e-mail"/>
      </xsl:if>
      <xsl:if test="phone">
        <br/>
        <xsl:apply-templates select="phone"/>
      </xsl:if>
      <xsl:if test="mobile">
        <br/>
        <xsl:apply-templates select="mobile"/>
      </xsl:if>
    </p>
  </xsl:if>
</xsl:template>

<xsl:template match="section">
  <xsl:if test="not(boolean(@deprecated))">
    <xsl:apply-templates select="heading"/>
    <xsl:choose>
      <xsl:when test="child::objective">
        <xsl:apply-templates select="objective"/>
      </xsl:when>
      <xsl:when test="child::status">
        <table border="0" width="100%">
          <!-- For Simplicity I will but the Spanning cells first -->
          <!-- Ideally, the XSL should use an iterative approach that
               would end a row tag when it finds a spanning, then
               span the cell across all columns, then reset the
               numbering to "1" or some odd number to continue the
               data as if the next cell was the first one.  The problem
               with this is simply that I can't reset the position()
               counter to do this.  So, voilà, the simple approach -->
          <xsl:for-each select="status[@spanning = 'true']">
            <tr>
              <td class="status" colspan="{$col_of_data}">
                <xsl:apply-templates select="."/>
              </td>
            </tr>
          </xsl:for-each>
          <xsl:for-each select="status[not(boolean(@spanning))]">
            <xsl:choose>
              <xsl:when test="position() mod $col_of_data = 1">
                <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text>
              </xsl:when>
              <xsl:otherwise>
                <td>
                  <img src="spacer.gif" class="spacer"/>
                </td>
              </xsl:otherwise>
            </xsl:choose>
            <!-- Spacer messes the percentage up! -->
            <td class="status" width="{100 div $col_of_data}%">
              <xsl:apply-templates select="."/>
            </td>
            <xsl:if test="position() mod $col_of_data = 0">
              <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>
            </xsl:if>
          </xsl:for-each>
          <!-- Extra Close Row for odd numbers -->
          <xsl:if test="count(resume/section/summary/task) mod $col_of_data != 0">
            <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>
          </xsl:if>
        </table>
      </xsl:when>
      <xsl:when test="child::summary">
        <xsl:apply-templates select="summary"/>
      </xsl:when>
      <xsl:when test="child::skill">
        <xsl:for-each select="skill">
          <xsl:apply-templates select="."/>
        </xsl:for-each>
      </xsl:when>
      <xsl:when test="child::job">
        <xsl:for-each select="job">
          <xsl:apply-templates select="."/>
          <!-- Blank Line After to separate Records? -->
        </xsl:for-each>
      </xsl:when>
      <xsl:when test="child::school">
        <xsl:for-each select="school">
          <!-- Blank Line After to separate Records? -->
          <xsl:apply-templates select="."/>
        </xsl:for-each>
      </xsl:when>
      <xsl:when test="child::interest">
        <xsl:for-each select="interest">
          <xsl:if test="not(position() = 1)">
            <xsl:text>, </xsl:text>
          </xsl:if>
          <xsl:apply-templates select="."/>
        </xsl:for-each>
      </xsl:when>
      <xsl:when test="child::reference">
        <xsl:for-each select="reference">
          <xsl:apply-templates select="."/>
        </xsl:for-each>
      </xsl:when>
    </xsl:choose>
  </xsl:if>
</xsl:template>

<xsl:template match="/">
  <html>
  <head>
  <link href="Résumé.css" rel="stylesheet" type="text/css"/>
  <xsl:apply-templates select="resume/title"/>
  </head>
  <body>
    <xsl:for-each select="resume/section[not(boolean(@deprecated))]">
      <xsl:apply-templates select="."/>
    </xsl:for-each>
    <xsl:apply-templates select="resume/bibliography[not(boolean(@deprecated))]"/>
    <hr class="separator"/>
    <xsl:if test="resume/@url">
      <table width="100%">
        <tr>
          <td width="33%"></td>
          <td width="67%">
            <p class="online">
              <xsl:text>Text available on-line at </xsl:text>
              <a type="online" href="{resume/@url}">
                <xsl:value-of select="resume/@url"/>
              </a>
              <xsl:text>.</xsl:text>
            </p>
          </td>
        </tr>
      </table>
    </xsl:if>
  </body>
  </html>
</xsl:template>
</xsl:stylesheet>

Résumé.xsl, the eXstensible Stylesheet Language Transform of an XML Résumé.

Once the XML has been translated to HTML, it’s still rather rough. However, one can use Cascaded Style Sheets (CSS) to transform that raw XML into something much more pleasant to look at. It also hides Deprecated elements to they don’t appear in the final, rendered page. It sets the font, and colors the hard breaks, and formats everything in neat boxes.

Because the XSLT adds class modifiers to a number of the tags, the CSS can be rather richly defined and very specific to each document element.

body
{
    FONT-SIZE: 11pt;
    FONT-FAMILY: 'Times New Roman'
}
td
{
vertical-align : top;
}
span..buzzword
{
    FONT-STYLE: italic;
    FONT-FAMILY: 'Courier New'
}
span.institution
{
}
p.summary
{
    TEXT-ALIGN: justify
}
hr.separator
{
    BORDER-RIGHT: blue thick groove;
    BORDER-TOP: blue thick groove;
    BORDER-LEFT: blue thick groove;
    BORDER-BOTTOM: blue thick groove
}
H2.heading
{
    BORDER-TOP: medium none;
    FONT-SIZE: 12pt;
    COLOR: blue;
    FONT-FAMILY: Helvetica;
    LETTER-SPACING: 6pt;
    TEXT-ALIGN: center
}
H1.name
{
    FONT-WEIGHT: bolder;
    FONT-SIZE: 20pt;
    TEXT-ALIGN: center
}
SPAN.title
{
    COLOR: #ff9900;
    FONT-STYLE: italic;
    FONT-FAMILY: Helvetica;
    TEXT-ALIGN: center
}
P.address
{
    TEXT-ALIGN: center
}
SPAN.address
{
}
SPAN.text_heading
{
    FONT-WEIGHT: bolder
}
SPAN.position
{
    FONT-STYLE: italic;
    FONT-FAMILY: Arial
}
SPAN.product
{
    FONT-STYLE: italic
}
SPAN.degree
{
    FONT-STYLE: italic;
    FONT-FAMILY: Arial
}
SPAN.course
{
}
SPAN.author
{
}
A.isbn
{
    VISIBILITY: inherit
}
SPAN.date
{
}
SPAN.from_date
{
}
SPAN.to_date
{
}
P.period
{
    COLOR: #800080;
    FONT-STYLE: italic;
    TEXT-ALIGN: right
}
SPAN.publisher
{
}
P.bibliography
{
}
A.book
{
    VISIBILITY: inherit;
    FONT-STYLE: italic
}
SPAN.book
{
    FONT-STYLE: italic
}
SPAN.language
{
}
P.subject
{
}
SPAN.instref
{
    FONT-STYLE: italic
}
P.institution
{
    FONT-WEIGHT: bolder;
    FONT-SIZE: 12pt;
    FONT-FAMILY: Arial
}
A.address
{
    VISIBILITY: inherit
}
P.status
{
}
P.skill
{
}
P.curriculum
{
}
SPAN.tools
{
    FONT-WEIGHT: bolder;
    COLOR: #4c8000
}
P.task
{
}
SPAN.interest
{
}
A.interest
{
    VISIBILITY: inherit
}
P.objective
{
}
HEAD
{
    FONT-SIZE: 11pt;
    FONT-FAMILY: 'Times New Roman'
}
P.reference
{
}
P.online
{
    TEXT-ALIGN: right
}
A.online
{
    VISIBILITY: inherit
}
SPAN.redacted
{
    color: black;
    background-color: black;
    TEXT-DECORATION: line-through;
    font-style: italic
}
SPAN.deprecated
{
    DISPLAY: none;
    TEXT-DECORATION: line-through
}
TD.task
{
    VERTICAL-ALIGN: top;
    LIST-STYLE-TYPE: disc
}
TD.status
{
    VERTICAL-ALIGN: top;
}
img.spacer
{
width : 10px;
}
li.task
{
list-style-type : disc;
}
li.achievement
{
list-style-type : disc;
}

Résumé.css, the Cascaded Style Sheet used to transform the Résumé.

Once the CSS is applied, the final Résumé can be generated. Although I still had to perform some tweaking to the HTML, mainly to fix deprecated sections to eliminate trailing commas and other cleanups, for the most part, once the CSS was applied, the Résumé looked nearly perfect. The only change to the above CSS, because CSS on this site is site-wide, was that I put everything within a resume2002 class so that the CSS didn’t apply to every post, only to those inside a Résumé block.

You can see what the final, 2002 version, with a couple modern edits for some of my subsequent publications, of the Résumé looks like here.

I am working on an update to the XML Résumé thanks to figuring out how to get around the Xmplify bug with including Entity definition in an XML Schema. Apparently, if you Entities in your XML, you need to use a DTD to define your XML, you can’t use XML Schema. Fortunately, I did have the full DTD version of my XML specification, so that was easy to solve in the short term while Xmplify is fixed to allow XML Schema with entity-only DOCTYPE sections at the top of XML documents.

I have one final note about the code included in the Résumé in XML posts. In part 1, I neglected to take into account multiple spaces would get reduced to one, meaning that the formatting lost all of the proper indentation and made the code hard to read. I was hasty when I generated the HTML-compatable markup for the two documents there. When I generated the above documents, however, I generated the correct HTML using the simply four lines of Python below, once for each file.

>>> with open('Résumé.xml', 'rb') as f:
...     xml = f.read().decode('latin_1')
... 
>>> with open('xml.html', 'w') as f:
...     f.write(html.escape(xml).replace('\n', '
').replace(' ', ' '))
... 
>>>

Python code to turn XML into into readable HTML

I would love to show you the 2020 version of my Résumé but, alas, I’m still working on it and still have 2002–2012 to cover. I did write entries for 2010–2012, but then realized that I combined the description of two distinct projects into one, so I have to rewrite that section and then finish the first eight years of entries. That killed my Friday when this post was supposed to go out.

I figure that will take me another week, and have about five more projects to cover, but won’t know for sure until I go through the remaining Year End Reviews. But I do have the complete last eight years and, again, if you like what you see, I’m still happily available for hire.

How to make your own Surgical Mask

What follows is a series of TikTok videos my dear friend Lena Volkova demonstrated on her account. Unfortunately, I’ve yet to find a way to remove the cranky, white border around the videos to keep it in line with the esthetique with the site, its theme, and the official category of this post as Cosplay. Nonetheless, I feel this is important so f’d up CSS be damned! We need to stop SARS-CoV-2! So, without further à Deux, here is her A/B Reusable Mask Tutorial: A Mask with a Pocket for Filtration Media Such as a Surgical or N95 Mask.!

Pretreat the Fabric

This is part of a larger effort to help those out in need.

If you’re planning on helping out with making surgical face masks, you need to pretreat and disinfect your fabrics prior to sewing.

To pretreat fabrics:
    • Prepare a cold water bath to soak your fabric.
        ■ 100% cotton is preferable for masks.
    • You can add two tablespoons of salt for dye runoff.
        ■ This is optional.
    • Add one cup of distilled, white vinegar to your cold water bath.
        ■ Vinegar works both as a color stabilizer and disinfectant.
    • Add your fabric to the bath and let saturate thoroughly.
    • Set your timer and let it soak for thirty minutes.
    • Rinse with cold water and let air dry.

@lena.volkova
@lena.volkova

Sewing your own surgical masks. Part 1 — pretreating fabric. This is part of a larger effort to help out those in need. ##coronavirus ##helpers

♬ Hold On – Moguai,Cheat Codes

Of course, first you need to find some fabric you have lying around. Perhaps some old, cotton clothes or linen sheets you could sacrifice? Either way, this pretreatment step is important as it helps keep the color from bleeding while in use and of course disinfects.

Patterning

The pattern for this A/B Mask style mask and instructions can be found on craftpassion.com.

    • Start by folding your fabric—you need to cut two of each fabric (inner and outer layer).
        ■ This is easy if the fabric is folded onto itself.
    • Using your pattern, measure out an additional one inch from the sides.
    • And three eighths around the rest of the mask.
    • Cut your fabric—this is your outer layer.
    • The inner layer is a half-inch shorter on the side than the outer layer.
        ■ Use your outer layer as a template to measure.
    • Cut out your inner layer fabric.
        ■ Set it aside.

@lena.volkova
@lena.volkova

Part 2 — A/B reusable mask turtorial: patterning. This mask has a pocket for filtration media such as a surgical or N95 mask ##coronavirus ##helper

♬ You – Petit Biscuit

For those wondering, the Face Mask Pattern can be found here.

Assembly, Part 1

    • Separate your fabrics, but keep the layers together as a pair.
    • Finish the raw edge that is curved.
        ■ This will face the inside pocket and needs to be secured.
    • Any overlocking or zigzag stitch will do.
        ■ You don’t need a serger.
    • With the right side facing inward, pin together and sew along the curved edge.
        ■ Use a quarter-inch seam allowance.
    • Do this for both inner and outer layers.
    • Open your fabric, then flatten the finished edges onto the fabric and sew the finished edges onto the fabric.
    • Align the inner and outer layers.
    • Fold the side edge of the inner layer onto itself twice—you’re rolling in the raw edge.
        ■ Pin in place.

@lena.volkova
@lena.volkova

Part 3 — A/B reusable mask turtorial: assembly. This mask has a pocket for filtration media such as a surgical or N95 mask ##coronavirus ##helper

♬ Classical – Piano Classics: Masters of Relaxing Solo Piano Music

I’ll be honest, I keep thinking about investing in a sewing machine. I know at Nova Labs, I could borrow one of their machines, but I’m nervous about doing seamwork in a public space. Plus, I need to find some decent patterns to so—like an A/B Surgical Mask!

Assembly, Part 2

    • Sew the roll into place along the inward edge of the roll.
    • With the right sides of the fabric facing together, align the layers and sew the top and bottom edges together.
    • Clip the curved edge of the fabric where both layers meet.
        ■ It should be approximately a quarter-inch inward.
    • Turn the mask inside out.
    • Roll the raw top and bottom edges inward onto itself to cover the raw edge.
        ■ Pin in place.
    • Do the same for the side edges.
        ■ There should be approximately a half-inch of fabric remaining on both sides.
    • Sew into place along the inner edge.

@lena.volkova
@lena.volkova

Part 4 — A/B reusable mask turtorial: assembly 2. This mask has a pocket for filtration media such as a surgical or N95 mask ##coronavirus ##helper

♬ Classical – Classical Chill Out

Remember to leave those sewing margins! You’re almost done.

Finishing

    • Fold the side flap inward, under the inner layer pocket.
    • Sew in place—both sides.
    • Insert your filter medium—cloth, a surgical mask, or an N95 mask.
    • Make sure the nose wire aligns with the nose part of the mask.
    • If using elastic, take a sixteen-inch length of one sixteenth elastic cord.
        ■ There’s a tolerance of plus or minus one to two inches for sizing.
    • Feed it through the channels you made in the sides, and secure it with a double knot.
        ■ Hide the channels.
    • To wear: Hold the mask in one hand and slip the bottom elastic over your head, followed by the top.
    • Adjust accordingly to endure a proper fit.
    • To wash: hot water, like scrubs.
        ■ Can be autoclaved.
        ■ Remove media as appropriate.
        ■ Sanitize elastic as appropriate.

@lena.volkova
@lena.volkova

Part 5– Reusable A/B mask tutorial: finishing. Mask has a pouch for removable filters like surgical masks or N95 masks. ##coronavirus ##helpers

♬ Piano Piano – Piano Solo – Charlie Glass Piano Man

I never cease to be amazed as the skills and achievements of my friend Lena. I am so astonished I can call her my friend, but then she’s one of the nicest, funniest people you’re likely to meet.

Thank you Lena. Hopefully when SARS-CoV-2 abates, we can cosplay together again!

Coloring through CSS

Someone mentioned some of the links were hard to see on my site because I was only changing the text color and not the link attributes. Now, this should be a lot easier to do because instead of coloring my text blocks manually, I will be coloring them with Cascading Style Sheets (CSS).

Using the palet of CSS, I came up with a number of potential color schemes.

For Coder posts, I’m using this theme.

For Musical posts, I’m using this theme.

For Gaming posts, I’m using this theme.

For Speech Making posts, I’m using this theme.

For Author posts, I’m using this theme.

For Vegetarianism and Diet posts, I’m using this theme.

For Thespian posts, I’m using this theme.

For Science posts, I’m using this theme.

For Polyglot posts, I’m using this theme.

For Electric Car posts, I’m using this theme.

For Foreign Travel posts, I’m using this theme.

For Doctor Who posts, I’m using this theme.

For Aspiration Aviation posts, I’m using this theme.

For Equal Rights Amendment posts, I’m using this theme.

For Cosplay posts, I’m using this theme.

For NPVIC posts, I’m using this theme.

But what am I using this theme for?

With this template, I can check all the themes before they’re deployed to see how they look, at least in terms of text and hyperlinks. I will add CSS for Spoilers and other special text like #CO2Fre and #CO2Fre1, but for now this is what I’m working with. And I’m available for hire.