JT Turner
JT Turner

Reputation: 502

Make Two columns out of one XML XSLT to HTML

I have XML coming from a source that I can't change. They send it as a list but the lists can also have embedded lists. Here is an example keeping it short:

<LinkList>
  <ListHeader>A</ListHeader>
  <ArticleLink chunkiid="13121">A Test 1</ArticleLink>
  <ArticleLink chunkiid="13122">A Test 2</ArticleLink>
  <ArticleLink chunkiid="13123">A Test 3</ArticleLink>
  <LinkList>
    <ListHeader>
      <ArticleLink chunkiid="13124">A Inner List</ArticleLink>
    </ListHeader>
    <ArticleLink chunkiid="13125">A Inner Test 1</ArticleLink>
    <ArticleLink chunkiid="13126">A Inner Test 2</ArticleLink>
  </LinkList>
  <ArticleLink chunkiid="13127">A Test 4</ArticleLink>
  <ArticleLink chunkiid="13128">A Test 5</ArticleLink>
</LinkList>

Keep in mind it is much bigger in real life. I want to split this into two columns using XSLT or CSS or JQuery. The current output if I leave it one column looks like this:

<b>A</b>
<ul>
    <li><a href="Article.aspx?id=13121">A Test 1</a></li>
    <li><a href="Article.aspx?id=13122">A Test 2</a></li>
    <li><a href="Article.aspx?id=13123">A Test 3</a></li>
    <li>
        <b><a href="Article.aspx?id=13124">A Inner List</a></b>
        <ul>
            <li><a href="Article.aspx?id=13125">A Inner Test 1</a></li>
            <li><a href="Article.aspx?id=13126">A Inner Test 1</a></li>
        </ul>
    </li>
    <li><a href="Article.aspx?id=13127">A Test 4</a></li>
    <li><a href="Article.aspx?id=13128">A Test 5</a></li>
</ul>

I have been able to split it using XSLT but I can only do it on the bottom list so if there are inner lists it sometimes make one side or the other longer. I am doing a count in to a variable and then applying a template like this in XSLT:

<xsl:template match="LinkList">
  <xsl:if test="ListHeader = $CurrentAlphaIndex">
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates select="ListHeader"/>
    <xsl:variable name="OneSideCount">
      <xsl:value-of select="ceiling((count(child::*) - 1) div 2)" />
    </xsl:variable>
    <div class="col-50">
      <div class="layout-inner-2">
        <ul>
          <xsl:apply-templates select="*[not(self::ListHeader) and (position() - 1) &lt;= $OneSideCount]">
            <xsl:sort select="."/>
          </xsl:apply-templates>
        </ul>
      </div>
    </div>
    <div class="col-50">
      <div class="layout-inner-2">
        <ul>
          <xsl:apply-templates select="*[not(self::ListHeader) and (position() - 1) &gt; $OneSideCount]">
            <xsl:sort select="."/>
          </xsl:apply-templates>
        </ul>
      </div>
    </div>
    <div class="spacer">&amp;nbsp;</div>
  </xsl:if>
</xsl:template>

I left out some of the templates as I didn't want this to long. I figure this shows how I am breaking it up right now but from my example you can see the right side is longer because of the inner list.

Is there any way to leave it a single list and break it up into two columns using CSS or JQuery? If not is there a way I can count all links in the XML and split it up using a position() that also includes children nodes?

Update What I mean by two columns is having half the records on one side of the page and half on the other. Sorry the XSLT is using div tags only because that is the standard on this site set by the client and their designer.

Upvotes: 0

Views: 1466

Answers (1)

user357812
user357812

Reputation:

Without extension functions just for fun:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:param name="CurrentAlphaIndex" select="'A'"/>
    <xsl:param name="pColumns" select="2"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="LinkList">
        <xsl:if test="ListHeader = $CurrentAlphaIndex">
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="ListHeader"/>
            <xsl:variable name="vArticleLinks"
                 select="descendant-or-self::LinkList/ArticleLink"/>
            <xsl:variable name="OneSideCount"
                 select="ceiling(count($vArticleLinks) div $pColumns)"/>
            <xsl:apply-templates
                 select="$vArticleLinks[position() mod $OneSideCount = 1]"
                 mode="partition">
                <xsl:with-param name="OneSideCount"
                                select="$OneSideCount"/>
                <xsl:with-param name="pArticleLinks"
                                select="$vArticleLinks"/>
            </xsl:apply-templates>
            <div class="spacer">&amp;nbsp;</div>
        </xsl:if>
    </xsl:template>
    <xsl:template match="ArticleLink" mode="partition">
        <xsl:param name="pArticleLinks" select="/.."/>
        <xsl:param name="OneSideCount" select="0"/>
        <xsl:variable name="vOffSet"
                      select="(position() - 1) * $OneSideCount"/>
        <div class="col-50">
            <div class="layout-inner-2">
                <ul>
                    <xsl:apply-templates select="$pArticleLinks"
                                         mode="filter">
                        <xsl:with-param name="OneSideCount"
                             select="$OneSideCount"/>
                        <xsl:with-param name="pOffSet"
                             select="(position() - 1) * $OneSideCount"/>
                        <xsl:sort/>
                    </xsl:apply-templates>
                </ul>
            </div>
        </div>
    </xsl:template>
    <xsl:template match="ArticleLink" mode="filter">
        <xsl:param name="pOffSet" select="0"/>
        <xsl:param name="OneSideCount" select="0"/>
        <xsl:if test="position() > $pOffSet
                         and
                      $pOffSet + $OneSideCount >= position()">
            <xsl:apply-templates select="."/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Output:

<ListHeader>A</ListHeader>
<div class="col-50">
    <div class="layout-inner-2">
        <ul>
            <ArticleLink chunkiid="13125">A Inner Test 1</ArticleLink>
            <ArticleLink chunkiid="13126">A Inner Test 2</ArticleLink>
            <ArticleLink chunkiid="13121">A Test 1</ArticleLink>
            <ArticleLink chunkiid="13122">A Test 2</ArticleLink>
        </ul>
    </div>
</div>
<div class="col-50">
    <div class="layout-inner-2">
        <ul>
            <ArticleLink chunkiid="13123">A Test 3</ArticleLink>
            <ArticleLink chunkiid="13127">A Test 4</ArticleLink>
            <ArticleLink chunkiid="13128">A Test 5</ArticleLink>
        </ul>
    </div>
</div>
<div class="spacer">&amp;nbsp;</div>

With extensions (better performance):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:msxsl="urn:schemas-microsoft-com:xslt"
 exclude-result-prefixes="msxsl">
    <xsl:param name="CurrentAlphaIndex" select="'A'"/>
    <xsl:param name="pColumns" select="2"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="LinkList">
        <xsl:if test="ListHeader = $CurrentAlphaIndex">
            <xsl:apply-templates select="@*|ListHeader"/>
            <xsl:variable name="vrftArticleLinks">
                <xsl:for-each
                 select="descendant-or-self::LinkList/ArticleLink">
                    <xsl:sort/>
                    <xsl:copy-of select="."/>
                </xsl:for-each>
            </xsl:variable>
            <xsl:variable name="vArticleLinks"
                 select="msxsl:node-set($vrftArticleLinks)/*"/>
            <xsl:variable name="OneSideCount"
                 select="ceiling(count($vArticleLinks) div $pColumns)"/>
            <xsl:for-each
                 select="$vArticleLinks[position() mod $OneSideCount = 1]">
                <div class="col-50">
                    <div class="layout-inner-2">
                        <ul>
                            <xsl:apply-templates
                             select=".|following-sibling::*[
                                          $OneSideCount > position()
                                       ]"/>
                        </ul>
                    </div>
                </div>
            </xsl:for-each>
            <div class="spacer">&amp;nbsp;</div>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Related Questions