0

Got the following problem regarding stacked displaying of some XML data (tables) rows. They (XML data) are designed as a table (like one is supposed to display it as a HTML table). Most usual showed way is in the form of HTML table.

But now the way I wanted to be displayed (through XSLT) is like through some sort of "card" with folding all columns into the rows where the number of rows has to be equal with the number of fields plus the same number of corresponding headers.

I'll show you guys an example to better understand my requirements (this sort of question has already raised before but none of the answers was satisfactory .. at least not for me). Here's my .xml data table:

<persns> 
 <prsn> 
  <fname>Smith</fname> 
  <lname>Milton</lname> 
  <age>44</age> 
  <addrss>5th summer st, mntb</addrss>
  <city>Portland</city>
 </prsn>
 <prsn> 
  <fname>Ken</fname> 
  <lname>Jackson</lname> 
  <age>37</age> 
  <addrss>19th Penfield ave, brtcl</addrss>
  <city>Kelowna</city>
 </prsn>
 <prsn> 
  <fname>Susan</fname> 
  <lname>Arkland</lname> 
  <age>48</age> 
  <addrss>34th Mansfield st, sgtp</addrss>
  <city>Raleigh</city>
 </prsn>
<persns>

Which aproximately could be represented like this:

|======|========|=====|======================|==========|
|FNAME | LNAME  | AGE |        ADDRESS       |   CITY   |
|======|========|=====|======================|==========|
|Smith |Milton  | 44  |  5th smmr st, mntb   | Portland |
|------|--------|-----|----------------------|----------|
| Ken  |Jackson | 37  |19th Pnfeld ave, brtcl| Kelowna  |
|------|--------|-----|----------------------|----------|
|Susan |Arkland | 48  |34th Mansfield st,sgtp| Raleigh  |
|------|--------|-----|----------------------|----------|
|Patsy |Brighton| 35  |12th Peel st, pnslv   |Phldlphia |
|======|========|=====|======================|==========|
                      Fig.1

And, through some .xslt transformation I'd need that xml to be displayed like this:

  |================|    |================|
  |      FNAME     |    |     FNAME      |  <--- header name   
  |----------------|    |----------------|
  |      Smith     |    |      Ken       |  <--- corresponding element name
  |----------------|    |----------------|
  |     LNAME      |    |     LNAME      |  <--- header name
  |----------------|    |----------------|
  |     Milton     |    |    Jackson     |  <--- corresponding element name
  |----------------|    |----------------|                                        . . . and so on
  |       AGE      |    |      AGE       |
  |----------------|    |----------------|
  |       44       |    |      37        |
  |----------------|    |----------------|
  |     ADDRESS    |    |    ADDRESS     |
  |----------------|    |----------------|
  |5th smmr st,mntb|    | 9th Pnfeld ave,|
  |----------------|    |----------------|
  |      CITY      |    |      CITY      |
  |----------------|    |----------------|
  |    Portland    |    |    Kelowna     |
  |================|    |================|
                    Fig.2

As one would notice this displaying is with header first, then the element node right beneath, then next header, and again underneath it goes the corresponding node element and so forth. The next HTML table is again built first with the header on top following beneath with that node element and so on and so forth. And each of these blocks of elements - with corresponding headers - should be divided as HTML tables (separately).

Still being a newcomer with .xslt, I really could't figure a way of doing this, though I "made" some bad .xslt code which I'll put it down here. But, ain't working whatsoever. It yields something but not as far of what I'm really after. So here it is:

<xsl:template match="/">
 <xsl:variable name="hd_tbl">
  <head fname ="FNAME" lnme="LNAME" age="AGE" addrs="ADDRESS" oras="oras"/>
 </xsl:variable>

  <xsl:for-each select="persns/prsn | $hd_tbl/head/@*"> 
     <table id="tbl" border="1"> 
 <xsl:choose>
  <xsl:when test="(position() mod 2) != 1"> <!-- even line -->
     <tr> <th> <xsl:value-of select="./@*"/></th></tr>
  </xsl:when>
  <xsl:otherwise>
    <tr> <td> <xsl:value-of select="."/></td></tr>
  </xsl:otherwise>
 </xsl:choose>
  </table>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

So you guys please help me sort this out, as I struggling with it for quite some time now.

Thank you very much in advance.

Ferran Buireu
  • 28,630
  • 6
  • 39
  • 67
mcaS48
  • 33
  • 8
  • This is very similar to https://stackoverflow.com/questions/56453449/xls-transformation-from-xml-in-particular-table-order where I suggested two possible approaches. Pick which one you prefer: a single table with a column for each `prsn` or a table for each `prsn` to be displayed side-by-side. – michael.hor257k Feb 28 '20 at 21:55
  • If HTML is the target format, you first need to figure which HTML element structure you want (nested tables, using CSS and flex box) and then to write the XSLT to create that structure. – Martin Honnen Feb 28 '20 at 22:08

2 Answers2

2

Assuming your input is regular (i.e. every record has the same fields in the same order), you could do:

XSLT 1.0

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/persns">
    <xsl:variable name="records" select="prsn"/>
    <table border="1">
        <xsl:for-each select="prsn[1]/*">
            <xsl:variable name="field-name" select="name()"/>
            <xsl:variable name="i" select="position()"/>
            <tr>
                <xsl:for-each select="$records">
                    <th>
                        <xsl:value-of select="$field-name"/>
                    </th>
                </xsl:for-each>
            </tr>   
            <tr>
                <xsl:for-each select="$records">
                    <td>
                        <xsl:value-of select="*[$i]"/>
                    </td>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>

</xsl:stylesheet>

to produce a result of:

<table border="1">
  <tr>
    <th>fname</th>
    <th>fname</th>
    <th>fname</th>
  </tr>
  <tr>
    <td>Smith</td>
    <td>Ken</td>
    <td>Susan</td>
  </tr>
  <tr>
    <th>lname</th>
    <th>lname</th>
    <th>lname</th>
  </tr>
  <tr>
    <td>Milton</td>
    <td>Jackson</td>
    <td>Arkland</td>
  </tr>
  <tr>
    <th>age</th>
    <th>age</th>
    <th>age</th>
  </tr>
  <tr>
    <td>44</td>
    <td>37</td>
    <td>48</td>
  </tr>
  <tr>
    <th>addrss</th>
    <th>addrss</th>
    <th>addrss</th>
  </tr>
  <tr>
    <td>5th summer st, mntb</td>
    <td>19th Penfield ave, brtcl</td>
    <td>34th Mansfield st, sgtp</td>
  </tr>
  <tr>
    <th>city</th>
    <th>city</th>
    <th>city</th>
  </tr>
  <tr>
    <td>Portland</td>
    <td>Kelowna</td>
    <td>Raleigh</td>
  </tr>
</table>

which will render as a single table with a column for each record and a double row of name/value for each field:

enter image description here


P.S. I am not sure what dictates the shape of the required output. Personally, I would prefer to do:

XSLT 1.0

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/persns">
    <xsl:variable name="records" select="prsn"/>
    <table border="1">
        <xsl:for-each select="prsn[1]/*">
            <xsl:variable name="i" select="position()"/>
            <tr>
                <th>
                    <xsl:value-of select="name()"/>
                </th>
                <xsl:for-each select="$records">
                    <td>
                        <xsl:value-of select="*[$i]"/>
                    </td>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>

</xsl:stylesheet>

to get a simple transposed (pivoted) table:

enter image description here

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • @mchl.hor257k mr. hor's solution is almost good. The table's rendering is done with 1st header going all the way across the line then comes 1st record's field cell then 2nd record's first field cell and so on,and then again comes the 2nd header which again gets rendered all the way the 3rd line and so on. I need this: 1st I need a table for every xml record: 10 records,10 tables Then building this "card" tables is done as follows: 1st header comes first, then,underneath goes the first field's cell of the first .xml record then underneath comes the second header and then ... – mcaS48 Feb 29 '20 at 11:37
  • ... underneath there will be the second field's cell also of the first xml record till the first table is complete @Martin Honnen. Mr. Honnen's solution works perfectly, but it's a little bit hard for me to implement through my particular environment. But it's displaying the way it should. That's why I'd still go for first hor257k's solution, but changed the way I pointed out, if it's possible. Thank you both for these quick and thoroughly solutions you provided. Very much appreciated. – mcaS48 Feb 29 '20 at 11:42
  • The need for displaying that .xml records that way is for I need it to be placed over an android device; and there is a very little room available. That's why. And there should be a pagination so at one time only one "table" should be visible, and then as I click over some next button, another - already reanderd - table will be visible, and so on. – mcaS48 Feb 29 '20 at 11:47
  • I am afraid you have lost me at this point . You say that the headers should be " going all the way across the line" - but your question clearly shows headers being repeated for every column. Please edit your question and add the **exact HTML code** you want to get as a result of transforming the given example. – michael.hor257k Feb 29 '20 at 17:03
  • Let me tell you smthng. very important. Please check this out. Because of you (and mr. Honnen) I was able to make real progress with some struggling project.So, if it weren't for you, I was going nuts by now perhaps, as ain't got a clue in many respects. Ok, now I'm gonna tell you why this all misunderstanding. Because SO staff is limiting people for making rather longer comments. I had to be extremely short on what was trying telling you earlier. That's why you misunderstand the whole point, right? And I don't blame you for this, because it's not your "fault", is SO's !! – mcaS48 Feb 29 '20 at 18:35
  • You are not supposed to post long comments. You are supposed to edit your question so it's clear- as I suggested earlier. – michael.hor257k Feb 29 '20 at 18:47
  • You know, it's very frustrating when you just can't tell everything the way you want it to. I want to thank you oh so much for all your answers you gave me during last year. You have been extremely helpful for me (and for others as well). I appreciate your time and dedication on reply all those questions and everything. I was very interested in .xml and .xslt stuff, and I'm so sorry that I just could't improved myself better. All the best to you all. Thank you again ! – mcaS48 Feb 29 '20 at 18:57
  • wanted someth like:
    And this is displaying my xml data pretty much how I wanted. There are a couple things I need to taking care of, but basically that's the whole point here.
    – mcaS48 Feb 29 '20 at 19:16
2

If you use a flex box container for tables you can define the rest of the appearance with CSS:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output method="html" indent="yes" html-version="5"/>

  <xsl:template match="persns">
      <div class="box">
          <xsl:apply-templates/>
      </div>
  </xsl:template>

  <xsl:template match="prsn">
      <table>
          <tbody>
              <xsl:apply-templates/>
          </tbody>
      </table>
  </xsl:template>

  <xsl:template match="prsn/*">
      <tr>
          <th>{upper-case(local-name())}</th>
      </tr>
      <tr>
          <td>{.}</td>
      </tr>
  </xsl:template>

  <xsl:template match="/" expand-text="no">
    <html>
      <head>
        <title>.NET XSLT Fiddle Example</title>
        <style>
            .box { 
              display: flex; 
            }
            .box table {
              margin-right: 1em;
              border-collapse: collapse;
              border: 1px solid black;
            }
            .box table tr, .box table td, .box table th { 
              border: 1px solid black;
            }
            .box table td, .box table th {
              text-align: center;
            }
        </style>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/pPJ9hEA

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110