XSLT Can't Read an Excel XML File? - excel

I'm using XSLT / XPath to browse some of the XML files you get when you unzip an Excel file. I found a "relationships" file workbook.xml.rels that I don't seem to be able to read, using code similar to that which successfully read the workbook.xml file.
Here's some of the workbook.xml file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
...
<sheets>
<sheet name="Sheet1"
sheetId="2"
r:id="rId1"/>
<sheet name="Test Sheet"
sheetId="1"
r:id="rId2"/>
</sheets>
...
</workbook>
Here's the workbook.xml.rels file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId3"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
Target="theme/theme1.xml"/>
<Relationship Id="rId2"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
Target="worksheets/sheet2.xml"/>
<Relationship Id="rId1"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
Target="worksheets/sheet1.xml"/>
<Relationship Id="rId5"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
Target="sharedStrings.xml"/>
<Relationship Id="rId4"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
Target="styles.xml"/>
</Relationships>
Here's some of the XSLT:
<?xml version="1.0"?>
<!-- greeting.xsl -->
<xsl:stylesheet
...
<xsl:output method="text"/>
<xsl:variable name="baseDir" select="replace(document-uri(.), '(.*[\\/]xl).*', '$1/')"/>
<xsl:variable name="workbook" select="concat($baseDir, 'workbook.xml')"/>
<xsl:variable name="theSheetId" select="doc($workbook)/workbook/sheets/sheet[matches(#name, 'Test Sheet')]/#r:id"/>
<xsl:variable name="workbook_rels" select="concat($baseDir, '_rels/workbook.xml.rels')"/>
<!-- code to read workbook.xml.rels -->
<xsl:variable name="theSheet" select="doc($workbook_rels)/Relationships/Relationship[matches(#Id, $theSheetId)]/#Target"/>
<xsl:template match="/">
<xsl:text>
baseDir = </xsl:text><xsl:value-of select="$baseDir"/>
<xsl:text>
workbook = </xsl:text><xsl:value-of select="$workbook"/>
<xsl:text>
workbook_rels = </xsl:text><xsl:value-of select="$workbook_rels"/>
<xsl:text>
theSheetId = </xsl:text><xsl:value-of select="$theSheetId"/>
<xsl:text>
theSheet = </xsl:text><xsl:value-of select="$theSheet"/>
<xsl:text>
end</xsl:text>
</xsl:template>
</xsl:stylesheet>
And the output:
baseDir = file:/C:/Training/sandbox/conv_/xl/
workbook = file:/C:/Training/sandbox/conv_/xl/workbook.xml
workbook_rels = file:/C:/Training/sandbox/conv_/xl/_rels/workbook.xml.rels
theSheetId = rId2
theSheet = **<I get nothing here>**
end
You can see that 'theSheetID' variable is correctly set when reading workbook.xml. But when I use that variable to get the corresponding Target value into 'theSheet' variable from workbook.xml.rels, I get nothing. I tried replacing the matches expression with just a number but I still get nothing. Is there a problem from reading this type of file?
Suggestions? Thanks!

The use of matches and replace suggests you are using an XSLT 2 or 3 processor and that way XSLT 2 or 3 where you can certainly declare xpath-default-namespace, you just have to understand you have to change that in the sections that deal with elements from a different namespace e.g. <xsl:variable name="theSheet" select="doc($workbook_rels)/Relationships/Relationship[matches(#Id, $theSheetId)]/#Target" xpath-default-namespace="http://schemas.openxmlformats.org/package/2006/relationships"/>.
Given the samples I would rather use a key <xsl:key name="rel" match="Relationships/Relationship" use="#Id" xpath-default-namespace="http://schemas.openxmlformats.org/package/2006/relationships"/> and then use <xsl:variable name="theSheet" select="key('rel,$theSheetId, doc($workbook_rels))/#Target"/> but the use of xpath-default-namespace to declare the relevant namespace when selecting elements from a particular document is probably what is missing in your XSLT.

Related

How to return the count of input that start with a string in XSL?

I'm trying to return the count of names which start with NB but im having trouble as it returns 0. I believe i got the syntax right so it's kind of frustrating to see it return 0. Any help would be appreciated!
XML Input:
<?xml version='1.1' encoding='UTF-8'?>
<breaches>
<breach>
<name>NB111</name>
<severity>MAJOR</severity>
</breach>
<breach>
<name>NB222</name>
<severity>MAJOR</severity>
</breach>
<breach>
<name>NB333</name>
<severity>MAJOR</severity>
</breach>
<breach>
<name>PO999</name>
<severity>MAJOR</severity>
</breach>
</breaches>
XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(/breaches/breach/name/*[contains(text(), 'NB')])" />
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>0
I'm trying to return the count of names which start with NB
If you want to count names that start with "NB" then use the starts-with() function, not the contains() function:
<xsl:value-of select="count(breaches/breach[starts-with(name, 'NB')])"/>
This actually counts breach elements that have a child name element that starts with "NB". IOW, it assumes each breach has only one name. If such assumption is incorrect, then use:
<xsl:value-of select="count(breaches/breach/name[starts-with(., 'NB')])"/>
to count the actual name elements.
You could do it like this :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(breaches/breach/name[contains(., 'NB')])" />
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/jxWZS86

XSL loop through faultblock and apppend values to a string without using template

How to loop through fault_block and append values to a string/variable without using template in XSLT. fault_block may occur once or twice or n number of times based on validation errors
Desired Output: 11-Invalid ID;22-Invalid Password;.....nn-Error;
<status>
<code>00</code>
<description>Success</description>
<faultblock>
<faultcode>11</faultcode>
<faultdesc>Invalid ID</faultdesc>
</faultblock>
<faultblock>
<faultcode>22</faultcode>
<faultdesc>Invalid Password</faultdesc>
</faultblock>
<faultblock>
<faultcode>nn</faultcode>
<faultdesc>Error</faultdesc>
</faultblock>
</status>
I'm guessing you want to get your output using only a for-each statement instead of using multiple templates.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="//faultblock">
<xsl:value-of select="faultcode"/>
<xsl:text>-</xsl:text>
<xsl:value-of select="faultdesc"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
See it working here: https://xsltfiddle.liberty-development.net/6pS2B71

move an element to another element or create a new one if it does not exist using xslt-3

using xslt 3, i need to take all content elements' values, and move them to the title elements (if the title elements already exist in a record, they need to be appended with a separator like -) i now have inputted my real data, since the below solution does not solve the problem when implemented to something like:
example input:
<data>
<RECORD ID="31365">
<no>25099</no>
<seq>0</seq>
<date>2/4/2012</date>
<ver>2/4/2012</ver>
<access>021999</access>
<col>GS</col>
<call>889</call>
<pr>0</pr>
<days>0</days>
<stat>0</stat>
<ch>0</ch>
<title>1 title</title>
<content>1 content</content>
<sj>1956</sj>
</RECORD>
<RECORD ID="31366">
<no>25100</no>
<seq>0</seq>
<date>2/4/2012</date>
<ver>2/4/2012</ver>
<access>022004</access>
<col>GS</col>
<call>8764</call>
<pr>0</pr>
<days>0</days>
<stat>0</stat>
<ch>0</ch>
<sj>1956</sj>
<content>1 title</content>
</RECORD>
</data>
expected output:
<data>
<RECORD ID="31365">
<no>25099</no>
<seq>0</seq>
<date>2/4/2012</date>
<ver>2/4/2012</ver>
<access>021999</access>
<col>GS</col>
<call>889</call>
<pr>0</pr>
<days>0</days>
<stat>0</stat>
<ch>0</ch>
<title>1 title - 1 content</title>
<sj>1956</sj>
</RECORD>
<RECORD ID="31366">
<no>25100</no>
<seq>0</seq>
<date>2/4/2012</date>
<ver>2/4/2012</ver>
<access>022004</access>
<col>ΓΣ</col>
<call>8764</call>
<pr>0</pr>
<days>0</days>
<stat>0</stat>
<ch>0</ch>
<sj>1956</sj>
<title>1 title</title>
</RECORD>
<data>
with my attempt, i did not manage to move the elements, i just got an empty line where the content element existed, so please add the removal of blank lines in the suggested solution.
i believe the removal of blank lines could be fixed with the use of
<xsl:template match="text()"/>
One way to achieve this is the following template. It uses XSLT-3.0 content value templates.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" expand-text="true">
<xsl:output method="xml" indent="yes" />
<xsl:mode on-no-match="shallow-copy" />
<xsl:strip-space elements="*" /> <!-- Remove space between elements -->
<xsl:template match="RECORD">
<xsl:copy>
<xsl:copy-of select="#*" />
<title>{title[1]}{if (title[1]) then ' - ' else ''}<xsl:value-of select="content" separator=" " /></title>
<xsl:apply-templates select="node() except (title,content)" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It's output is as desired.
If you want to separate the <content> elements with a -, too, you can simplify the core <title> expression to
<xsl:value-of select="title|content" separator=" - " />
EDIT:
All I changed was replacing chapter with RECORD, and it's working fine with Saxon-HE 9.9.1.4J. The only difference in the output is that the title element is always at the first position, but that shouldn't matter. I also added a directive to remove space between elements.

Modify Excel Sheet with data from XML using XSLT

I need to provide a report in Excel format from a Template Excel and an XML file that contains the data (created from SQL request: report_data.xml). But I can't use XPath expression to navigate through an Excel sheet, and I can't select a specific row in the template to duplicate with the data from the report_data.xml
In order to achieve this, I've first "unzipped" the Excel template in order to have access to the individual sheets in .xml format. At the same time I'm setting "source" files that will be used as the default file (eg: source-sheet1.xml, source-sharedstring.xml, ...) to create the new populated files.
I can't find a way to select a specific row in the template to duplicate with the data from the xml.
TemplateSource
Report
I've tested this using Xalan 2.7.2 and Saxon 9.7.0.15 //
XSLT 1.0
source-sheet1.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="x14ac"
xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">
<dimension ref="A1:G1"/>
<sheetViews>
<sheetView tabSelected="1" workbookViewId="0">
<selection activeCell="B5" sqref="B5"/>
</sheetView>
</sheetViews>
<sheetFormatPr baseColWidth="10" defaultColWidth="9.140625" defaultRowHeight="15" x14ac:dyDescent="0.25"/>
<sheetData>
<row r="1" spans="1:7" x14ac:dyDescent="0.25">
<c r="A1" t="s">
<v>0</v>
</c><c r="B1" t="s">
<v>1</v>
</c><c r="C1" t="s">
<v>2</v>
</c><c r="D1" t="s">
<v>3</v>
</c><c r="E1" t="s">
<v>4</v>
</c><c r="F1" t="s">
<v>5</v>
</c><c r="G1" t="s">
<v>6</v>
</c>
</row>
</sheetData>
<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
</worksheet>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:math="http://exslt.org/math"
xmlns:set="http://exslt.org/sets"
xmlns:exslt="http://exslt.org/common"
xmlns:redirect="org.apache.xalan.xslt.extensions.Redirect"
xmlns:office="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
extension-element-prefixes="redirect"
exclude-result-prefixes="fo fn math set redirect office r ss">
<xsl:variable name="SrcSheet1" select="document('XLSX/source-sheet1.xml')"/>
<xsl:template match="Report">
<xsl:call-template name="Test1"/>
</xsl:template>
<xsl:template name="Test1">
<xsl:for-each select="$SrcSheet1/Worksheet/sheetData/row">
<xsl:message>
row = <xsl:value-of select="position()"/>
colcount= <xsl:value-of select="count(./c)"/>
</xsl:message>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
report_data.xml:
<?xml version="1.0" encoding="utf-8"?>
<Report attrib1="foo" ...>
<Data attrib1="foo" ...>
<SubData attrib1="foo" ... />
...
</Data>
...
</Report>
I would like to be able to "read" the source-sheet1.xml and copy the rows in it, and the change the values in each columns (when needed; styles and polices depends if it's the 1st , 2nd, ... row for the same data) with the data from report_data.xml
If this is not the way to generate a report in excel from data in xml with different style depending on the "position" of the data in the excel file.

Populating nodes based on condition in Receiver file

I need to populate one segment based on input values.the requirement like below
in the input payload we are getting below segment like
<charac>
<charactername>
<charactervalue>
</charac>
so the above segment may come multiple times but based on the few values only need to populate the segment for example,
<charac>
<charactername>print</charactername>
<charactervalue>123</charactervalue>
</charac>
<charac>
<charactername>comp</charactername>
<charactervalue>1234</charactervalue>
</charac>
<charac>
<charactername>pal</charactername>
<charactervalue>1235</charactervalue>
</charac>
so here only I need populate segment when charactername is equal to only print or comp
the receiver structure the segment is
<e1edl1>
<at>
<rt>
</e1edl1>
so the output should be like
<e1edl1>
<at>print</at>
<rt>123</rt>
</e1edl1>
<e1edl1>
<at>comp</at>
<rt>1234</rt>
</e1edl1>
I tried with below code
<ns0:if test="count(./charac)!=0">
<ns0:for-each select="./charac">
<e1edl1 SEGMENT="1">
<at>
<ns0:value-of select="charactername" />
</at>
<rt>
<ns0:value-of select="charactervalue" />
</rt>
</e1edl1>
</ns0:for-each>
</ns0:if>
could you please help on this.
Regards,
Janardhan
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//charac[charactername='print' or charactername='comp']" />
</xsl:template>
<xsl:template match="charac">
<e1edl1>
<at><xsl:value-of select="charactername" /></at>
<rt><xsl:value-of select="charactervalue" /></rt>
</e1edl1>
</xsl:template>
</xsl:stylesheet>

Resources