Solved

Right Justifying XML Data With a XSL Stylesheet

Posted on 2008-10-28
13
1,531 Views
Last Modified: 2013-11-18
I have posted this problem before but have not received a workable answer.

I have a simple XML record structure that involves price data. Each refers to a stock with a Beginning Prices and Ending Prices. When I go to display these records within IExplorer, the data is left justified - which makes the data appear total disorganized.

So how should we right-justify the output? The following code has been suggested for one field - EndPrice. It says, if test="local-name() = 'EndPrice'">    then...

<xsl:attribute name="align"><xsl:text>right</xsl:text></xsl:attribute>

See the entire XSL file which is 20 lines at most.

Unfortunately, the code doesn't work. The data under EndPrice remains justified to the left, not the right.

Any idea what's wrong here?








<!-- Loop thru all the Record nodes, sort by Symbol -->                  
<xsl:for-each select="Record[Exchange='NYSE']">
      <xsl:sort select="EquityName"/>
      <tr>
      <xsl:for-each select="*">
            <td>
            <xsl:if test="local-name() = 'EndPrice'">
                    <xsl:attribute name="align"><xsl:text>right</xsl:text></xsl:attribute>
            </xsl:if>
            <xsl:value-of select="."/>
            </td>
      </xsl:for-each>
      </tr>
</xsl:for-each>
Data from the XML data file:
 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<Records>

	<Record>

		<Symbol>MMM</Symbol>

		<Exchange>NYSE</Exchange>

		<EquityName>3M Company</EquityName>

		<BeginPrice>62.78</BeginPrice>

		<EndPrice>59.64</EndPrice>

		<Difference>-3.14</Difference>

		<Change>-5.00%</Change>

	</Record>

	<Record>

		<Symbol>AA</Symbol>

		<Exchange>NYSE</Exchange>

		<EquityName>Alcoa Inc.</EquityName>

		<BeginPrice>15.22</BeginPrice>

		<EndPrice>9.41</EndPrice>

		<Difference>-5.81</Difference>

		<Change>-38.17%</Change>

	</Record>
 

The here is the entire XSL file:
 

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
 

 	<xsl:template match="Records">

		<table border="1" width="700" cellpadding="3" cellspacing="3">

			<tr>

				<xsl:for-each select="Record[1]/*">

					<th><xsl:value-of select="name()"/></th>

				</xsl:for-each>

			</tr>

 

			<!-- Loop thru all the Record nodes, sort by Symbol -->			

			<xsl:for-each select="Record[Exchange='NYSE']">

				<xsl:sort select="EquityName"/>

 				<tr>

					<xsl:for-each select="*">

						<td>

							<xsl:if test="local-name() = 'EndPrice'">

								<xsl:attribute name="align"><xsl:text>right</xsl:text></xsl:attribute>

							</xsl:if>

							<xsl:value-of select="."/>

						</td>

					</xsl:for-each>

				</tr>

 			</xsl:for-each>

 		</table>

	</xsl:template>

</xsl:stylesheet>

Open in new window

0
Comment
Question by:gbmcneil
  • 7
  • 6
13 Comments
 

Author Comment

by:gbmcneil
ID: 22828252
Hello:

I have some input to share.

I've been working with the code and have confirmed that the "if test-local name" line works. The problem seems to be in the request to change the attribute to right justify the data in a column. That is, the <xsl:attribute name="align">right</xsl:attribute> line seems to fail.

Therefore, the following works.  I can select data in the column "EndPrice" and make all the data elements below it turn red.

<td>
      <xsl:if test="local-name() = 'EndPrice'">
     <xsl:attribute name="bgcolor">#FF1117</xsl:attribute>
     </xsl:if>
<xsl:value-of select="."/>
</td>

But, if I change the code to that immediately below, which attempts to right justify the data, it doesn't work.

<td>
      <xsl:if test="local-name() = 'EndPrice'">
     <xsl:attribute name="align">right</xsl:attribute>
     </xsl:if>
<xsl:value-of select="."/>
</td>

So, the question is: Is there a bug that prevents changing the alignment of text in a column on the fly or is the process of changing the alignment a more difficult process?   I see where there is a variant of setting align which involves defining it as "Char" and that involves setting several other attributes which together right justifies the numeric data in the column by the decimal. I doubt this, however, because I've tried to right justfy a column that contained only text info and it failed here also.

Could it be that trying to display a simple listing of a flat 30 record file with columnar data requires more involved programming just to display some simple numbers.

Gordon
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 22829069
Hi Gordon,

This is all very strange. The XSLT is obviously generating the <td align="right"> correctly,
it is just that your browser doesn't get it for one reason or another (so this is a browser problem rather than an XSLT problem)
I must say that I was also surprised that you asked for bolding the titles, that is what <th> should do for you
(so obviously something not going well at that end too)

So it just doesn't seem to show up right in your copy of Internet Explorer.
I pulled this in in the Javascript and XHTML sample I sent you before and for me it works fine.
(I seemed to have sent you a version with XHTML strict doctype... maybe you should set you output serialisation to XML,
in order to have well-formed XHTML.)

I also would not use align="right" in a cell anymore.
What about CSS and text-align?

So patched your code a little bit... it is not garantueed to change anything. But at least you can try
I don't know who changed my apply-templates into a for-each. I changed it back again, it simply is a better practice
I added a number formatting to add a zero in the column. At my end it now right aligns properly and all the '.' are align

Have a go and let me know

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output indent="yes" method="xml"/>

<xsl:template match="Records">

    <table border="1">

        <tr>

            <xsl:for-each select="Record[1]/*">

                <th><xsl:value-of select="name()"/></th>

            </xsl:for-each>

            <xsl:apply-templates select="Record[Exchange='NYSE']">

                <xsl:sort select="EquityName"/>

            </xsl:apply-templates>

        </tr>

    </table>

</xsl:template>

    <xsl:template match="Record">

             <tr>

                <xsl:for-each select="*">

                    <td>

                        <xsl:if test="local-name() = 'EndPrice'">

                            <xsl:attribute name="style"><xsl:text>text-align: right;</xsl:text></xsl:attribute>

                        </xsl:if>

                        <xsl:value-of select="format-number(. , '#.00')"/>

                    </td>

                </xsl:for-each>

            </tr>

    </xsl:template>

</xsl:stylesheet>

Open in new window

0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 22829178
Hi Gordon,

I just found the question where willyRD advised you to only use one template.
BAD ADVICE.
Don't trust so called XSLT experts that advice you to bring stuff together in a single template.
It is commonly understood that breaking templates up in smaller templates is a best practice.
Nested for-each statements are a nightmare to maintain

Geert
0
 
LVL 60

Accepted Solution

by:
Geert Bormans earned 250 total points
ID: 22829307
Here is a closer to final XSLT (I found a little inconsistency in the previous XSLT)
<?xml version="1.0" encoding="UTF-8"?>
 

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output indent="yes" method="xml"/>
 

    <xsl:template match="Records">

        <table border="1">

                <xsl:apply-templates select="Record[1]" mode="header"/>

                <xsl:apply-templates select="Record[Exchange='NYSE']">

                    <xsl:sort select="EquityName"/>

                </xsl:apply-templates>

        </table>

    </xsl:template>

 

    <!-- processing all rows -->

    <xsl:template match="Record">

             <tr>

                 <xsl:apply-templates select="*"/>

            </tr>

    </xsl:template>

    <xsl:template match="Record/*[not(self::EndPrice)][not(self::BeginPrice)]">

        <td><xsl:value-of select="."/></td>

    </xsl:template>

    <!-- special processing for EndPrice and BeginPrice, overrules the more generic Record/* -->

    <xsl:template match="Record/EndPrice | Record/BeginPrice">

        <td style="text-align: right;"><xsl:value-of select="format-number(. , '#.00')"/></td>

    </xsl:template>
 

    <!-- header row templates -->

    <xsl:template match="Record" mode="header">

        <tr>

            <xsl:apply-templates select="*" mode="header"/>

        </tr>

    </xsl:template>

    <xsl:template match="Record/*" mode="header">

        <th><xsl:value-of select="name()"/></th>

    </xsl:template>

</xsl:stylesheet>

Open in new window

0
 

Author Comment

by:gbmcneil
ID: 22834669
Gertone to the Rescue (once again)

You did a great job reorganizing the code in the XSL file. It is 1000% more readable and understandable. It gives me hope that I can learn how to code in this weird scripting language.

I am enclosing my most recent version (which is really your version with the Header section moved up. I've got all of the right-justified field properly aligned, a couple more than you dealt with. Thanks very much.

The only thing left under the topic of justification is to see if I could right-justify the headings themselves.  I tried to extend the spirit of right justifying the data to the columns. I made some progress - at least the code didn't stop in its tracks. See my modified code section called "<!-- Header row templates -->"

At present when I run the XSL against the XML the header "Begin Price" and "End Price" disappear altogether. But, since the code runs, I think that I am close.

Any thoughts on right justifying the headings a la the structure of the code that now exists?

Gordo



   
0
 

Author Comment

by:gbmcneil
ID: 22834702
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml"/>
 
    <!-- Record select and sort -->
    <xsl:template match="Records">
        <table border="1" width="600" cellpadding="3" cellspacing="3">
                <xsl:apply-templates select="Record[1]" mode="header"/>
                <xsl:apply-templates select="Record[Exchange !='AMEX']">
                    <xsl:sort select="EquityName"/>
                </xsl:apply-templates>
        </table>
    </xsl:template>
   
   
    <!-- Header row templates -->
    <xsl:template match="Record" mode="header">
        <tr>
            <xsl:apply-templates select="*" mode="header"/>
        </tr>
    </xsl:template>
    <xsl:template match="Record/*[not(self::BeginPrice)][not(self::EndPrice)]" mode="header">
        <th><xsl:value-of select="name()"/></th>
    </xsl:template>
    <!-- special processing for BeginPrice header overrules the more generic Record/* mode="header" -->
    <xsl:template match="Header/BeginPrice | Header/EndPrice">
        <th style="text-align: right;"></th>
    </xsl:template>
             
 
    <!-- Processing all rows -->
    <xsl:template match="Record">
             <tr>
                 <xsl:apply-templates select="*"/>
            </tr>
    </xsl:template>
    <xsl:template match="Record/*[not(self::BeginPrice)][not(self::EndPrice)][not(self::Difference)][not self::Change)]">
        <td><xsl:value-of select="."/></td>
    </xsl:template>
    <!-- special processing for BeginPrice, EndPrice, Difference and Change overrules the more generic Record/* -->
    <xsl:template match="Record/BeginPrice | Record/EndPrice | Record/Difference | Record/Change">
        <td style="text-align: right;"><xsl:value-of select="format-number(. , '#.00')"/></td>
    </xsl:template>
     
</xsl:stylesheet>
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:gbmcneil
ID: 22837687
Hello Gertone:

I am starting to get the hang of it now.

Below is my attempt to right-justify the BeginPrice, EndPrice, Difference and Change column headings. At the sametime, I had to process the Symbol, Exchange, and EquityName column headings - which logically should have remained left-justified.

I don't know if the code is correct per your techniques and standards, but it seems to work. In the process I have boldened the column headings and changed their fonts. What I am showing below is just the code block to define the templates to deal with the header rows.

Again, reorganizing the code made the whole thing more understandable to me.

I am now going to try to change the color of  each line depending on whether the each equity increased or decreased in value (the fact that everything is going down ought to be helpful - if you can call it that. I will post this functionality as a separate "issue in need of a solution" if I can't figure it out myself after a couple of hours. Please stay with me Gertone as this becomes a new Experts Exchange question.

Thanks for all your help. You're not only a "genius" but someone willing to take the time to share your knowledge. Kudos.

Gordo






0
 

Author Comment

by:gbmcneil
ID: 22837695
<!-- Header row templates -->
    <xsl:template match="Record" mode="header">
        <tr>
               <xsl:apply-templates select="*" mode="header"/>
        </tr>
    </xsl:template>
    <xsl:template match="Record/Symbol | Exchange | EquityName" mode="header">
        <th style="color: #000000; text-align: left; font-family: Arial, Helvetica ; font-weight: 900; font-size: 11px;">
        <xsl:value-of select="name()"/>
        </th>
    </xsl:template>
    <xsl:template match="Record/BeginPrice | Record/EndPrice | Record/Difference | Record/Change" mode="header">
        <th style="color: #000000; text-align: right; font-family: Arial, Helvetica ; font-weight: 900; font-size: 11px;">
        <xsl:value-of select="name()"/>
        </th>
    </xsl:template>
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 22844920
Hi Gordon,

I was in my car yesterday when you wrote your subsequent messages.
So I only saw them today.

You are very close to not needing my help anymore...
It is good that you took away the generality from the fallback template for record children.
It would have become too complex to express this without ambiguity.

You have done a good job by yourself
I just think that with
   <xsl:template match="Record/Symbol | Exchange | EquityName" mode="header">
you mean
   <xsl:template match="Record/Symbol | Record/Exchange | Record/EquityName" mode="header">
to be consistent, but with this XML
   <xsl:template match="Symbol | Exchange | EquityName" mode="header">
would work as well.
(since Exchange only exists as a child of Record anyway)

0
 

Author Comment

by:gbmcneil
ID: 22852327
Hello Gerton -

Thank you for your kind comments, however, while I can modify code that you've provided me - I really don't understand what I'm doing. This causes the seemingly simplest thing to be daunting.

I understand that there are two distinct areas of my code: the headings and the data. And, within each of these areas there are two, maybe three sections. The first seems like boilerplate: a match Records statement followed by an "apply templates select..."  Then (in the second section) there is a match Records statement followed by a "value of select" statement. And, in the middle of these two sections there maybe some code to define templates.

Now, beyond that, I am mystified as to how to go about thing. For example, momentarily I am going to post another question about displaying my idiot-level Dow Stock file where one of the fields does not appear in the output. I hope that you will follow me there.

Gordo
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 22853178
What you have to realise is that every single xsl:template element,
is a piece of code that operates on the node that is specified in the match attribute.

inside such a piece of code you can have an action apply-templates
this action pushes all the childnodes for evaluation to the templates
apply templates can be selctive (that is when you have a "select" attribute with the apply-templates)

match attributes in an xsl:template can be as general or as selective as you want yourself.
The more specific the XPath in the attribute, the higher the precedence

and you can switch off certain xsl:template elements from being evaluated by having a "mode"

This is the mechanism in a nutshell.
In order to understand this mechanism, you can study the generalised example I gave
in the question where I protested the "bringing it all together in one template"
Multiple templates are oh so much easier to grasp.

basically, you do something with a Record element
and then you pass on all the children to the next template, where you process them.
This is how you can create a tr at one level and a td at the next level

I hope this helps

Geert
0
 

Author Comment

by:gbmcneil
ID: 22853270
Many thanks for the explanation, Gertone.

You are still going to have to give me more time to take this all in. I am a little overwhelmed. The XSLT Tidwell book arrived today. I'm going to have to study it.

Gordo
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 22853290
Take all the time you need,
I am not going to leave the planet soon :-)
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

Article by: Matthew
I am a very big proponent of technology compliance standards and strive to meet such criteria in all of my work. That includes my site, which is 100% XHTML 1.0 compliant as determined by the World Wide Web Consortium. https://www.matthewstevenkel…
What is Node.js? Node.js is a server side scripting language much like PHP or ASP but is used to implement the complete package of HTTP webserver and application framework. The difference is that Node.js’s execution engine is asynchronous and event…
The viewer will learn how to count occurrences of each item in an array.
The viewer will learn the basics of jQuery including how to code hide show and toggles. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now