问题
I have almost the same issue for this one: Get value after each last colon. In my case, I need to iterate for every occurrence of :A: and :B:, :B: is a child of :A:. In my code, I am using a call-template which is I'm not very familiar with. But, I need to explore the other functionalities/elements in xslt.
Here is my sample test file:
<Record>
:A:This is sample only 1
:B:This is sample only 2
:B:This is sample only 3
:A:This is sample only 4
:B:This is sample only 5
</Record>
Expected output:
<Record>
<Detail>
<FieldA>This is sample only 1</FieldA>
<Trans>
<Group>
<FieldB>This is sample only 2</FieldB>
</Group>
<Group>
<FieldB>This is sample only 3</FieldB>
</Group>
</Trans>
</Detail>
<Detail>
<FieldA>This is sample only 4</FieldA>
<Trans>
<Group>
<FieldB>This is sample only 5</FieldB>
</Group>
</Trans>
</Detail>
<Record>
For every occurrence of :A:, I need to create a <Detail>
record, then for each :B: will create a <Group>
record. And here is my XSLT code,
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Record">
<xsl:call-template name="FormatXML">
<xsl:with-param name="Input">
<Record>
<xsl:for-each select="tokenize(.,':A:')">
<xsl:analyze-string select="." regex=":([0-9A-Za-z]+):(.*)\n">
<xsl:matching-substring>
<xsl:variable name="FieldA">
<xsl:if test="regex-group(1) = 'A'">
<FieldB>
<xsl:value-of select="regex-group(2)"/>
</FieldB>
</xsl:if>
</xsl:variable>
<xsl:for-each select="tokenize(.,':B:')">
<xsl:variable name="FieldB">
<xsl:if test="regex-group(1) = 'B'">
<FieldB>
<xsl:value-of select="regex-group(2)"/>
</FieldB>
</xsl:if>
</xsl:variable>
<Group>
<FieldB>
<xsl:value-of select="$FieldB"/>
</FieldB>
</Group>
</xsl:for-each>
<Detail>
<FieldA>
<xsl:value-of select="$FieldA"/>
</FieldA>
</Detail>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:for-each>
</Record>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="FormatXML">
<xsl:param name="Input"/>
<xsl:apply-templates select="$Input"/>
</xsl:template>
<xsl:template match="/Record">
<xsl:copy>
<xsl:apply-templates select="Detail"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Detail">
<xsl:copy>
<xsl:copy-of select="FieldA"/>
<Trans>
<xsl:apply-templates select="Group"/>
</Trans>
</xsl:copy>
</xsl:template>
<xsl:template match="Group">
<xsl:copy>
<xsl:apply-templates select="FieldB"/>
</xsl:copy>
</xsl:template>
My xslt code is not working. Can anyone help me if what I missed in my code. Your feedback is highly appreciated.
Thanks!
回答1:
I would suggest a radically different approach: first convert the string to individual elements, so that you have:
<FieldA>This is sample only 1</FieldA>
<FieldB>This is sample only 2</FieldB>
<FieldB>This is sample only 3</FieldB>
<FieldA>This is sample only 4</FieldA>
<FieldB>This is sample only 5</FieldB>
in a variable.
Then group the elements in the variable and output them the way you want them:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Record">
<!-- convert strings to elements -->
<xsl:variable name="temp" as="element()*">
<xsl:analyze-string select="." regex="^:([AB]):(.*)$" flags="m">
<xsl:matching-substring>
<xsl:element name="Field{regex-group(1)}">
<xsl:value-of select="regex-group(2)" />
</xsl:element>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<!-- output -->
<xsl:copy>
<!-- group elements -->
<xsl:for-each-group select="$temp" group-starting-with="FieldA">
<Detail>
<xsl:copy-of select="current-group()[1]" />
<Trans>
<xsl:for-each select="current-group()[position() ge 2]">
<Group>
<xsl:copy-of select="." />
</Group>
</xsl:for-each>
</Trans>
</Detail>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Demo: http://xsltransform.net/gVhD8Ro/1
回答2:
With XSLT 3.0 (as supported by Saxon 9.8 all editions) or Altova XMLSpy and Raptor you could directly group the lines:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math"
expand-text="true" version="3.0">
<xsl:output indent="yes"/>
<xsl:template match="Record">
<xsl:copy>
<xsl:for-each-group select="tokenize(., '\r?\n')[normalize-space()]"
group-starting-with=".[starts-with(., ':A:')]">
<Detail>
<FieldA>{replace(., ':A:', '')}</FieldA>
<Trans>
<xsl:apply-templates select="current-group()[position() gt 1]"/>
</Trans>
</Detail>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match=".[starts-with(., ':B:')]">
<Group>
<FieldB>{replace(., ':B:', '')}</FieldB>
</Group>
</xsl:template>
</xsl:stylesheet>
来源:https://stackoverflow.com/questions/45539458/get-the-value-after-the-colon-keys