views:

102

answers:

4

I'd like to use an xml file

<pics> 
 <pic no="1">c:\pic1.jpg</pic>
 <pic no="2">c:\pic2.jpg</pic>
 <pic no="3">c:\pic3.jpg</pic>
 <pic no="4">c:\pic4.jpg</pic>
 <pic no="5">c:\pic5.jpg</pic>
 ....
</pics>

in an html table:

<table cellspacing="2" cellpadding="2" border="0">             
    <tr>
    <td><img src="" width="150" height="120" /></td>
    <td><img src="" width="150" height="120" /></td>
    <td><img src="" width="150" height="120" /></td>

   </tr>
   <tr>  
    <td><img src="from xml" width="150" height="120" /></td>
    <td><img src="from xml" width="150" height="120" /></td>
    <td><img src="from xml" width="150" height="120" /></td>
   </tr>
   <tr>
    <td><img src="from xml" width="150" height="120" /></td>
    <td><img src="from xml" width="150" height="120" /></td>
    <td><img src="from xml" width="150" height="120" /></td>
   </tr>                    
</table>

Whats the best way to do this?

+1  A: 

Use XSL. Example here. Btw why do you have the no attribute there ?

thelost
Of course, it's even tagged as [xslt]...
KennyTM
+2  A: 

XML:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="web_page.xsl"?>
<pics>
  <pic>
   <td>
     <no src="http://farm1.static.flickr.com/160/387667598_ea86c93d81.jpg" width="150" height="120">1</no>
   </td>
   <td>
     <no src="http://farm1.static.flickr.com/160/387667598_ea86c93d81.jpg" width="150" height="120">2</no>
   </td>
   <td>
     <no src="http://farm1.static.flickr.com/160/387667598_ea86c93d81.jpg" width="150" height="120">3</no>
   </td>
  </pic>
  <pic>
   <td>
     <no src="http://motherjones.com/files/legacy/mojoblog/funny-cats-a10.jpg" width="150" height="120">4</no>
  </td>
   <td>
     <no src="http://motherjones.com/files/legacy/mojoblog/funny-cats-a10.jpg" width="150" height="120">5</no>
  </td>
   <td>
     <no src="http://motherjones.com/files/legacy/mojoblog/funny-cats-a10.jpg" width="150" height="120">6</no>
  </td>
  </pic>
</pics>

XSLT:

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

<xsl:template match="/">
<html>
<body>
  <table> 
  <xsl:for-each select="pics/pic">
    <tr>
      <xsl:for-each select="td">
        <td><img>
          <xsl:attribute name="src">
            <xsl:value-of select="no//@src"/>
          </xsl:attribute>
          <xsl:attribute name="width">
            <xsl:value-of select="no//@width"/>
          </xsl:attribute>
          <xsl:attribute name="height">
            <xsl:value-of select="no//@height"/>
          </xsl:attribute>
        </img></td>
      </xsl:for-each>
    </tr>
  </xsl:for-each>
  </table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Try it yourself here (copy and paste my code into the appropriate boxes):

JohnB
This doesn't deal with the trickiest part (arranging the columns of the table), is over-verbose (`<img src="{name//@image}"/>` has the same result) and is buggy as you can't have an image element without an alt attribute and can't have an image element as the child of a body element.
Jon Hanna
@Jon: let me address your comments. **1.** arranging the table html is trivial, I assumed he could figure that part out, but for you I updated my example to do exactly what he is trying to achieve (test it yourself) **2.** your example is way more complicated than mine, especially for someone starting out with XSLT, mine does what it needs to do and is simple **3.** I don't see how it's buggy, you can put the `img` anywhere, my example **works!** I'm confused as to why you down voted it. Have a nice day.
JohnB
I agree with JohnB - simple is always a better solution in my opinion especially if you are just beginning to learn a new technology - trying to master the concept is hard enough without having to wade though cryptic shorthand - I always say - "keep it simple stupid"
Doug
1. The question asks for three cells per column, your answer gives a completely different result.2. Mine is just as complicated as it needs to be as far as I can see, to answer the question. I'd be happy to see simplifications, but only if they answer the question. Yours is overly complicated in using `<xsl:attribute>` where `{}` will serve. 3. You cannot put the `<img>` anywhere, as `<body>` only allows block tags (HTML5 will change this if this isn't changed before REC status), it only works because browsers are meant to have fall-back behaviour when they encounter buggy HTML.
Jon Hanna
But the `<img>` need to be inside a `<td>` anyway
JohnB
It wasn't in your first version.
Jon Hanna
@Doug, `{}` isn't just a cryptic shorthand for `<xsl:attribute>`, though one can always use `<xsl:attribute>` where one can use `{}`. `<xsl:attribute>` has a completely different usage, and the rules of how it interacts with other XSL elements can be relatively complicated as XSL goes, while `{}` is simple, though less powerful. I'd much rather teach a beginner `{}` first and then `<xsl:attribute>` than teach the more complicated form first and then explain that there is also a shorthand.
Jon Hanna
@Jon: your example is nice and I learned something from it, +1
JohnB
Thank you. Gracefully said, just when I was worried we were going to get into a row. FWIW, I think both your first and second answers are masterful in the spirit of XSLT (nice clear declaration of intent), I just have the objections I stated.
Jon Hanna
LOL. Of course, only **now** do I notice that my own had a massive bug in having "match" where it should have had "select"!
Jon Hanna
Wow! This "accepted answer" **is not a solution, but a substitution** of the input sample by other input with the job already done! Also the subsequent transformation (`pics` by `table`, `pic` by `tr`, `td` as-is, `no` by `img`) is obscured by a "brick" template.
Alejandro
+2  A: 

There's a bug in the suggested output, as <img/> elements must have alt attributes in every version of HTML in which they are present.

Anyway the following does this but without those attributes that can be done from CSS instead (just to keep size down). Adding them back in if desired is trivial:

<xsl:template match="pics">
    <table>
        <xsl:apply-templates select="pic[position() mod 3 = 1]"/>
    </table>
</xsl:template>
<xsl:template match="pic[position() mod 3 = 1]">
    <tr>
        <td>
            <xsl:if test="2 &gt; count(following-sibling::pic)">
                <xsl:attribute name="colspan">
                    <xsl:value-of select="3 - count(following-sibling::pic)"/>
                </xsl:attribute>
            </xsl:if>
            <img src="{.}" alt="" />
        </td>
        <xsl:apply-templates select="following-sibling::pic[3 &gt; position()]" />
    </tr>
</xsl:template>
<xsl:template match="pic">
    <td><img src="{.}" alt=""/></td>
</xsl:template>

The above assumes you want the path from the file used directly, adding code to transform it in some way (say taking just the last part of the path using substring-after()) isn't a difficult extension, assuming said transform isn't complicated itself.

Edit:

Myself and JohnB are going into further territory here, the above suffices to answer the original question.

Added to give a fuller answer to JohnB's question. The following is the equivalent code using for-each instead of apply-templates. In theory both a sequential and a state-machine base implementation of an XSLT processor should deal with this identically, though you may find differences in practice (if you told me they were different with a given processor I'd bet a small amount on it being slightly faster with sequential processing and slightly slower with state-machine processing, but I'd only bet a very small amount).

Note that we can't reuse the default template for pic. On the plus-side, if we have a different default template for pic elsewhere (if this were part of a much more complicated stylesheet), we don't need to be clever to differentiate between them, which is the main time that I personally would lean toward for-each.

<xsl:template match="pics">
    <table>
    <xsl:for-each select="pic[position() mod 3 = 1]">
            <tr>
                <td>
                    <xsl:if test="2 &gt; count(following-sibling::pic)">
                        <xsl:attribute name="colspan">
                            <xsl:value-of select="3 - count(following-sibling::pic)"/>
                    </xsl:attribute>
                    </xsl:if>
                    <img src="{.}" alt="" />
                </td>
                <xsl:for-each select="following-sibling::pic[3 &gt; position()]">
                    <td><img src="{.}" alt=""/></td>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>
Jon Hanna
+1 very clever way to generate the columns! I've never done anything this fancy with XSLT. But don't you need a loop around the `<td>`?
JohnB
We don't need a loop, because the apply-templates in the table will take every third pic in order and then the appropriate template be run on it, which in turn deals with the next two.Or to look at it another way, apply-templates is a loop, though sometimes a loop on only one item.There is very little (if anything) you can do with for-each that you can't do with apply-templates, and vice-versa. The difference is normally about which seems more natural at the time, which depends on coder style to some extent. I lean to apply-templates as arguably more declarative, and more conducive to reuse
Jon Hanna
@JohnB I've added a for-each implementation and an explanation of the difference to more fully answer that question.
Jon Hanna
As I said to Dimitre Novatchev, I find these solutions hard to understand since I can't understand the way xslt works.So thanks alot and I hope that one day I'll get it.
Asaf
+1  A: 

Here is a typical solution that adheres to the spirit of XSLT (no <xsl:for-each>), as short as possible, and parameterized on the desired number of columns in the table.

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:param name="pNumCols" select="3"/>

 <xsl:template match="pics">
  <table cellspacing="2" cellpadding="2" border="0">
   <xsl:apply-templates select="pic[position() mod $pNumCols = 1]"/>
  </table>
 </xsl:template>

 <xsl:template match="pic">
   <tr>
    <xsl:apply-templates mode="process" select=
    "(. | following-sibling::pic)[not(position() > $pNumCols)]"/>
   </tr>
 </xsl:template>

 <xsl:template match="pic" mode="process">
   <td><img src="{.}" width="150" height="120" /></td>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document (based on the provided XML document, but with more pictures that are really colorful and interesting):

<pics>
 <pic no="1">http://col.stb.s-msn.com/i/D7/6A19748C9AA58B938F42099543D2E.jpg&lt;/pic&gt;
 <pic no="2">http://col.stb.s-msn.com/i/1F/35A8478AC24EEF95933B5F0E4E394.jpg&lt;/pic&gt;
 <pic no="3">http://col.stb.s-msn.com/i/76/3ADA01320CEC8B31D53FACC0C11E.jpg&lt;/pic&gt;
 <pic no="4">http://col.stb.s-msn.com/i/92/51BF117987A3279571F06BEB4AE39D.jpg&lt;/pic&gt;
 <pic no="5">http://col.stb.s-msn.com/i/9B/9A6E876BA2F7EAE82392C7E7F6C1C.jpg&lt;/pic&gt;
 <pic no="6">http://col.stb.s-msn.com/i/50/8CC964E5503A7F61F8AD22A12024.jpg&lt;/pic&gt;
 <pic no="7">http://col.stb.s-msn.com/i/C4/F7EF634B7084DA69AAB5AAD05C8922.jpg&lt;/pic&gt;
 <pic no="8">http://col.stb.s-msn.com/i/FB/C8367425D67FA391A5E0F8A3E0276B.jpg&lt;/pic&gt;
</pics>

the wanted result is produced (see it also in a browser :) ) :

<table cellspacing="2" cellpadding="2" border="0">
   <tr>
      <td>
         <img src="http://col.stb.s-msn.com/i/D7/6A19748C9AA58B938F42099543D2E.jpg" width="150" height="120"/>
      </td>
      <td>
         <img src="http://col.stb.s-msn.com/i/1F/35A8478AC24EEF95933B5F0E4E394.jpg" width="150" height="120"/>
      </td>
      <td>
         <img src="http://col.stb.s-msn.com/i/76/3ADA01320CEC8B31D53FACC0C11E.jpg" width="150" height="120"/>
      </td>
   </tr>
   <tr>
      <td>
         <img src="http://col.stb.s-msn.com/i/92/51BF117987A3279571F06BEB4AE39D.jpg" width="150" height="120"/>
      </td>
      <td>
         <img src="http://col.stb.s-msn.com/i/9B/9A6E876BA2F7EAE82392C7E7F6C1C.jpg" width="150" height="120"/>
      </td>
      <td>
         <img src="http://col.stb.s-msn.com/i/50/8CC964E5503A7F61F8AD22A12024.jpg" width="150" height="120"/>
      </td>
   </tr>
   <tr>
      <td>
         <img src="http://col.stb.s-msn.com/i/C4/F7EF634B7084DA69AAB5AAD05C8922.jpg" width="150" height="120"/>
      </td>
      <td>
         <img src="http://col.stb.s-msn.com/i/FB/C8367425D67FA391A5E0F8A3E0276B.jpg" width="150" height="120"/>
      </td>
   </tr>
</table>

Do note:

  1. The use of the XPath mod operator to determine the items of each row.

  2. The use of modes to process the same type of element (<pic>) in two different ways.

  3. The use of AVT (attribute-value-templates) to make the code shorter and more understandable.

Dimitre Novatchev
It's seems very elegant... but I'm less than a beginner - I never really had time to understand the magic of the xslt.I tried to copy and paste your code change the pics urls and even add <?xml version="1.0" encoding="ISO-8859-1"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">... I got lost... but thank you anyway :)
Asaf
@Dimitre: +1 for the only answer wich doesn't change the input or uses a brick template. @Asaf: Of course this is an elegant solution, but there is no "magic" involve. Also, the preference for `xsl:for-each` instructions over `xsl:apply-templates` is not a XSLT beginner privilege, it is because there is some misundertanding of declarative paradigm.
Alejandro
@Alejandro: Thanks, your opinion is most valued. @Asaf: you risk to remain a beginner for a looooong time if you do not strive to learn.
Dimitre Novatchev