XSLT Formatting to Remove Empty Columns (and Headers)

I have a database field. I am taking out xml from. This field contains content from several XML feeds, for which I need two. I display XML on the page and use XSLT to format it.

My problem is that there can be up to 40 elements per message in the database, of which I'm looking for about 30. However, not all messages will have all 30. I need help figuring out how to delete a column if there is no data to display.

If I can use XSLT, then fine, if it's JavaScript (jQuery), then that's fine too. There will never be more than one line and one heading at a time.

Here is an example XML

<MessageToUser> <Name>Bob</Name> <MessageType>Text Message</MessageType> <MessageID>121223</MessageID> <ResponseTo /> <Message>Call Me Please </Message> <OfficePhone>555-555-1212</OfficePhone> <CellPhone>555-555-5555</CellPhone> </MessageToUser> 

So XSL I use

 <xsl:template match="/"> <html> <head> <style type="text/css"> .Header{min-width: 100px;background-color:#9acddd;} </style> </head> <body> <table border="1"> <tr> <th class="Header">Name</th> <th class="Header">Message</th> <th class="Header">Cell Phone</th> <th class="Header">Office Phone</th> <th class="Header">Message Type</th> <th class="Header">Message Time</th> <th class="Header">Message ID</th> </tr> <xsl:for-each select="MessageFromUser"> <tr> <td><xsl:value-of select="Name" /></td> <td><xsl:value-of select="Message" /></td> <td><xsl:value-of select="CellPhone" /></td> <td><xsl:value-of select="OfficePhone" /></td> <td><xsl:value-of select="MessageType" /></td> <td><xsl:value-of select="MessageTime" /></td> <td><xsl:value-of select="MessageID" /></td> </tr> </xsl:for-each> <xsl:for-each select="MessageToUser"> <tr> <td><xsl:value-of select="Name" /></td> <td><xsl:value-of select="Message" /></td> <td><xsl:value-of select="CellPhone" /></td> <td><xsl:value-of select="OfficePhone" /></td> <td><xsl:value-of select="MessageType" /></td> <td><xsl:value-of select="MessageTime" /></td> <td><xsl:value-of select="MessageID" /></td> </tr> </xsl:for-each> </table> </body> 

Use of this XML MessageTime is empty and this column should be removed.

Thanks in advance for your help.

+4
source share
3 answers

This conversion is:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my" exclude-result-prefixes="my" > <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <my:Layout> <html> <head> <style type="text/css"> .Header{min-width: 100px;background-color:#9acddd;} </style> </head> <body> <table border="1"> <tr> <header name="Name">Name</header> <header name="Message">Message</header> <header name="CellPhone">Cell Phone</header> <header name="OfficePhone">Office Phone</header> <header name="MessageType">Message Type</header> <header name="MessageTime">Message Time</header> <header name="MessageID">Message ID</header> </tr> <every select="MessageFromUser"> <tr> <Name/> <Message/> <CellPhone/> <OfficePhone/> <MessageType/> <MessageTime/> <MessageID/> </tr> </every> <every select="MessageToUser"> <tr> <Name/> <Message/> <CellPhone/> <OfficePhone/> <MessageType/> <MessageTime/> <MessageID/> </tr> </every> </table> </body> </html> </my:Layout> <xsl:variable name="vLayout" select="document('')/*/my:Layout/*"/> <xsl:variable name="vDoc" select="/"/> <xsl:template match="node()|@*"> <xsl:param name="pContextNode"/> <xsl:copy> <xsl:apply-templates select="node()|@*"> <xsl:with-param name="pContextNode" select="$pContextNode"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <xsl:template match= "Name|Message|CellPhone|OfficePhone|MessageType|MessageTime|MessageID"> <xsl:param name="pContextNode"/> <xsl:if test="not($vDoc/*/*[not(*[name()=name(current())])])"> <td><xsl:value-of select="$pContextNode/*[name()=name(current())]"/></td> </xsl:if> </xsl:template> <xsl:template match="/*"> <xsl:apply-templates select="$vLayout"/> </xsl:template> <xsl:template match="header"> <xsl:if test="not($vDoc/*/*[not(*[name()=current()/@name])])"> <th class="Header"><xsl:value-of select="."/></th> </xsl:if> </xsl:template> <xsl:template match="every"> <xsl:variable name="vLayoutTop" select="."/> <xsl:for-each select="$vDoc/*/*[name()=current()/@select]"> <xsl:apply-templates select="$vLayoutTop/*"> <xsl:with-param name="pContextNode" select="."/> </xsl:apply-templates> </xsl:for-each> </xsl:template> </xsl:stylesheet> 

when applied to this XML document (provided and added by MessageFromUser ):

 <Messages> <MessageFromUser> <Name>Bob</Name> <MessageType>Text Message</MessageType> <MessageID>121223</MessageID> <ResponseTo /> <Message>Call Me Please </Message> <OfficePhone>555-555-1212</OfficePhone> <CellPhone>555-555-5555</CellPhone> </MessageFromUser> <MessageToUser> <Name>Bob</Name> <MessageType>Text Message</MessageType> <MessageID>121223</MessageID> <ResponseTo /> <Message>Call Me Please </Message> <OfficePhone>555-555-1212</OfficePhone> <CellPhone>555-555-5555</CellPhone> </MessageToUser> </Messages> 

creates the desired output in which an empty 1MessageTime` column is not displayed:

 <html xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <style type="text/css"> .Header{min-width: 100px;background-color:#9acddd;} </style></head> <body> <table border="1"> <tr> <th class="Header">Name</th> <th class="Header">Message</th> <th class="Header">Cell Phone</th> <th class="Header">Office Phone</th> <th class="Header">Message Type</th> <th class="Header">Message ID</th> </tr> <tr> <td>Bob</td> <td>Call Me Please </td> <td>555-555-5555</td> <td>555-555-1212</td> <td>Text Message</td> <td>121223</td> </tr> <tr> <td>Bob</td> <td>Call Me Please </td> <td>555-555-5555</td> <td>555-555-1212</td> <td>Text Message</td> <td>121223</td> </tr> </table> </body> </html> 

Please note :

  • The best design (filling in the gaps) is used , which separates the code and presentation. This is one of the most fundamental and powerful XSLT templates (filling in the blanks) .

  • This design allows you to use the same code with different layouts (passed as a parameter) to get completely different results.

  • The identification rule copies as-is all non-variable layout nodes .

  • We redefine the identity rule only for the "variable" parts of the layout .

  • The every auxiliary element indicates that iterative processing should be performed .

  • In a practical application, the <my:Layout> element will be located as a separate XML document in a separate file, in which case you need to replace document('')/* with document('theFileURI') . The most common is achieved when this file URI is passed as an XSLT conversion parameter.

  • A condition tested to process only fields / columns that appear at least once in at least one record (for headers):

not($vDoc/*/*[not(*[name()=current()/@name])])

A similar condition for cells:

not($vDoc/*/*[not(*[name()=name(current())])])

+3
source

Can you do it?

 <xsl:if test="MessageTime"> <td><xsl:value-of select="MessageTime" /></td> </xsl:if> 
0
source

This XSLT:

 <?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="yes"/> <xsl:key name="notEmptyAll" match="/root/MessageToUser/*" use="local-name()"/> <xsl:template match="root"> <html> <head> <style type="text/css"> th {min-width: 100px;background-color:#9acddd;} </style> </head> <body> <table border="1"> <tr> <xsl:apply-templates select="MessageToUser[1]/*" mode="header"/> </tr> <xsl:apply-templates select="MessageToUser"/> </table> </body> </html> </xsl:template> <xsl:template match="*" mode="header"> <xsl:if test="count( key('notEmptyAll', local-name())[text()] ) != 0"> <th> <xsl:value-of select="local-name()"/> </th> </xsl:if> </xsl:template> <xsl:template match="MessageToUser"> <tr> <xsl:apply-templates select="*" mode="tbody"/> </tr> </xsl:template> <xsl:template match="*" mode="tbody"> <xsl:if test="count( key('notEmptyAll', local-name())[text()] ) != 0"> <td> <xsl:value-of select="."/> </td> </xsl:if> </xsl:template> </xsl:stylesheet> 

With this XML input:

 <root> <MessageToUser> <Name>Bob</Name> <MessageType>Text Message</MessageType> <MessageID>121223</MessageID> <ResponseTo> </ResponseTo> <Message>Call Me Please </Message> <OfficePhone></OfficePhone> <CellPhone>555-555-5555</CellPhone> </MessageToUser> <MessageToUser> <Name></Name> <MessageType>Text Message</MessageType> <MessageID>121223</MessageID> <ResponseTo> </ResponseTo> <Message>Call Me Please </Message> <OfficePhone>555-555-1212</OfficePhone> <CellPhone>555-555-5555</CellPhone> </MessageToUser> </root> 

will provide this result (excerpt):

 <table border="1"> <tr> <th>Name</th> <th>MessageType</th> <th>MessageID</th> <th>Message</th> <th>OfficePhone</th> <th>CellPhone</th> </tr> <tr> <td>Bob</td> <td>Text Message</td> <td>121223</td> <td>Call Me Please </td> <td></td> <td>555-555-5555</td> </tr> <tr> <td></td> <td>Text Message</td> <td>121223</td> <td>Call Me Please </td> <td>555-555-1212</td> <td>555-555-5555</td> </tr> </table> 

Thus, all elements having at least one nonempty value are displayed.

local-name may not be enough for you because the headers will look like OfficePhone . This can be solved using substrings or vocabulary. Please remember to comment on what will be better, because it is not obvious from your XML excerpt.

I did not use MessageFromUser because you did not provide it with an XML sample.

0
source

Source: https://habr.com/ru/post/1332117/


All Articles