问题
I have an output from our local accounting system, which consists of an envelope tag and just a repeated sequence of the fields, but not grouped. I have no prior experience in XSL but have been trying to find a solution for this and have found a solution, which works for XSLT 2.0. Unfortunately, it seems that Access 2007 only accepts XSLT 1.0 transformations.
Can you please help me find a XSLT 1.0 solution that works?
These are two records in the original flat XML:
<ENVELOPE>
<DBCFIXED> <DBCDATE>1-Apr-2011</DBCDATE>
<DBCPARTY></DBCPARTY>
</DBCFIXED>
<DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
<DBCVCHNO>1</DBCVCHNO>
<DBCVCHREF></DBCVCHREF>
<DBCSTNO></DBCSTNO>
<DBCSERVICETAXNO></DBCSERVICETAXNO>
<DBCPANNO></DBCPANNO>
<DBCCSTNO></DBCCSTNO>
<DBCNARR>Opening balance transfar</DBCNARR>
<DBCQTY>0.000 Kg</DBCQTY>
<DBCRATE></DBCRATE>
<DBCAMOUNT></DBCAMOUNT>
<DBCADDLCOST></DBCADDLCOST>
<DBCGROSSAMT></DBCGROSSAMT>
<DBCLEDAMT></DBCLEDAMT>
<DBCFIXED> <DBCDATE></DBCDATE>
<DBCPARTY>ME KN YARN BL 1</DBCPARTY>
</DBCFIXED>
<DBCVCHTYPE></DBCVCHTYPE>
<DBCVCHNO></DBCVCHNO>
<DBCVCHREF></DBCVCHREF>
<DBCSTNO></DBCSTNO>
<DBCSERVICETAXNO></DBCSERVICETAXNO>
<DBCPANNO></DBCPANNO>
<DBCCSTNO></DBCCSTNO>
<DBCNARR></DBCNARR>
<DBCQTY>0.150 Kg</DBCQTY>
<DBCRATE>566.00/Kg</DBCRATE>
<DBCAMOUNT>-84.90</DBCAMOUNT>
<DBCADDLCOST></DBCADDLCOST>
<DBCGROSSAMT></DBCGROSSAMT>
<DBCLEDAMT></DBCLEDAMT>
</ENVELOPE>
For the import into Access, I need the data to be grouped into records starting with DBCFIXED until the next DBCFIXED. Something like:
<InventoryDaybook>
<record>
<DBCFIXED>
<DBCDATE>1-Apr-2011</DBCDATE>
<DBCPARTY/>
</DBCFIXED>
<DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
<...></...>
</record>
<record>
<...></...>
</record>
/<InventoryDaybook>
The XSLT 2.0 code that works is
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<InventoryDaybook>
<xsl:for-each-group select="*" group-starting-with="DBCFIXED">
<record>
<xsl:copy-of select="current-group()"/>
</record>
</xsl:for-each-group>
</InventoryDaybook>
</xsl:template>
</xsl:stylesheet>
It is amazing what can be done with XSL but my knowledge seems just too little.. Thanks in advance for your help and your understanding.
回答1:
This could be achieved in XSLT1.0 by means of a xsl:key, that matches non-DBCFIXED elements, and uses the first preceding DBCFIXED element as the key
<xsl:key
name="records"
match="ENVELOPE/*[not(self::DBCFIXED)]"
use="generate-id(preceding-sibling::DBCFIXED[1])" />
Then, when you match the individual DBCFIXED elements, you can easily look-up the associated elements that make up the record
<xsl:apply-templates select="key('records', generate-id())" />
Here is the full XLST
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="records" match="ENVELOPE/*[not(self::DBCFIXED)]" use="generate-id(preceding-sibling::DBCFIXED[1])" />
<xsl:template match="/ENVELOPE">
<InventoryDaybook>
<xsl:apply-templates select="DBCFIXED" />
</InventoryDaybook>
</xsl:template>
<xsl:template match="DBCFIXED">
<record>
<xsl:copy-of select="." />
<xsl:apply-templates select="key('records', generate-id())" />
</record>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to the input XML, the following is output
<InventoryDaybook>
<record>
<DBCFIXED>
<DBCDATE>1-Apr-2011</DBCDATE>
<DBCPARTY/>
</DBCFIXED>
<DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
<DBCVCHNO>1</DBCVCHNO>
<DBCVCHREF/>
<DBCSTNO/>
<DBCSERVICETAXNO/>
<DBCPANNO/>
<DBCCSTNO/>
<DBCNARR>Opening balance transfar</DBCNARR>
<DBCQTY>0.000 Kg</DBCQTY>
<DBCRATE/>
<DBCAMOUNT/>
<DBCADDLCOST/>
<DBCGROSSAMT/>
<DBCLEDAMT/>
</record>
<record>
<DBCFIXED>
<DBCDATE/>
<DBCPARTY>ME KN YARN BL 1</DBCPARTY>
</DBCFIXED>
<DBCVCHTYPE/>
<DBCVCHNO/>
<DBCVCHREF/>
<DBCSTNO/>
<DBCSERVICETAXNO/>
<DBCPANNO/>
<DBCCSTNO/>
<DBCNARR/>
<DBCQTY>0.150 Kg</DBCQTY>
<DBCRATE>566.00/Kg</DBCRATE>
<DBCAMOUNT>-84.90</DBCAMOUNT>
<DBCADDLCOST/>
<DBCGROSSAMT/>
<DBCLEDAMT/>
</record>
</InventoryDaybook>
回答2:
Congratulations to Tim for first correct solution. Whilst Tim's is absolutely correct, I just want to bring to the OP's attention, that there are two general forms of solution to XSLT 1.0 group-starting-with - One where the head node is excluded from the key (as in Tim's case), and the other where the head node is included (shown below). I am not sure which is superior. Perhaps Dimitre can tell us.
So for interest sake, here is the alternate form, which includes the head node in the key.
Head-node-included-in-key form
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="records" match="ENVELOPE/*" use="generate-id(
(preceding-sibling::DBCFIXED|self::DBCFIXED)[last()])" />
<xsl:template match="/ENVELOPE">
<InventoryDaybook>
<xsl:apply-templates select="DBCFIXED" mode="group-head" />
</InventoryDaybook>
</xsl:template>
<xsl:template match="DBCFIXED" mode="group-head">
<record>
<xsl:apply-templates select="key('records', generate-id())" />
</record>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Very slight advantages
- key match condition simpler.
- One less line in the template for the head node.
- The head node goes through apply-templates rather than directly copy-of. This may be of benefit if you are doing more than just straight copy.
Very slight disadvantages
- Key use attribute more complicated
- Have to add modes to discriminate between grouping the head node and down-stream processing of it. Although in way, this is also an advantage, as the mode designators improve self-documentation.
来源:https://stackoverflow.com/questions/11625288/grouping-of-a-well-defined-flat-xml-for-access-import-xslt-1-0