XSLT style sheet to migrate from ZF1 comfort to ZF2 EN16931

This commit is contained in:
Jochen Stärk
2017-10-28 16:58:55 +02:00
parent 458290ea8a
commit 7ebb344401
8 changed files with 232 additions and 125 deletions

View File

@@ -123,6 +123,10 @@ public class ZUGFeRDExporter implements Closeable {
ZFVersion = ver;
}
public IXMLProvider getProvider() {
return xmlProvider;
}
private void init() {
setZUGFeRDVersion(2);
}

View File

@@ -1,136 +1,87 @@
package org.mustangproject.ZUGFeRD;
public class ZUGFeRDMigrator {
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
public String migrateFromV1ToV2(String xml) {
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class ZUGFeRDMigrator {
static final ClassLoader cl = ZUGFeRDMigrator.class.getClassLoader();
private static final TransformerFactory factory = getTransformerFactory();
private static final String resourcePath = ""; //$NON-NLS-1$
private static TransformerFactory getTransformerFactory() {
TransformerFactory fact = TransformerFactory.newInstance();
fact.setURIResolver(new ClasspathResourceURIResolver());
return fact;
}
public String migrateFromV1ToV2(String xmlFilename) {
/***
* http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
* http://www.ferd-net.de/upload/Dokumente/FACTUR-X_ZUGFeRD_2p0_Teil1_Profil_EN16931_1p03.pdf
http://countwordsfree.com/xmlviewer
*/
// todo: attributes may also be in single quotes, this one hardcodedly expects
// double ones
xml = xml.replace("\"urn:ferd:CrossIndustryDocument:invoice:1p0",
"\"urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100");
xml = xml.replace("urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12",
"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100");
xml = xml.replace("urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15",
"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100");
xml = xml.replace("rsm:CrossIndustryDocument", "rsm:CrossIndustryInvoice");
xml = xml.replace("rsm:SpecifiedExchangedDocumentContext", "rsm:ExchangedDocumentContext");
xml = xml.replace("rsm:HeaderExchangedDocument", "rsm:ExchangedDocument");
xml = xml.replace("rsm:SpecifiedSupplyChainTradeTransaction", "rsm:SupplyChainTradeTransaction");
xml = xml.replace("ram:ApplicableSupplyChainTradeAgreement", "ram:ApplicableHeaderTradeAgreement");
xml = xml.replace("ram:ApplicableSupplyChainTradeDelivery", "ram:ApplicableHeaderTradeDelivery");
xml = xml.replace("ram:ApplicableSupplyChainTradeSettlement", "ram:ApplicableHeaderTradeSettlement");
xml = xml.replace("ram:ApplicablePercent", "ram:RateApplicablePercent");
xml = xml.replace("ram:SpecifiedSupplyChainTradeDelivery", "ram:SpecifiedLineTradeDelivery");
xml = xml.replace("ram:SpecifiedSupplyChainTradeSettlement", "ram:SpecifiedLineTradeSettlement");
xml = xml.replace("ram:SpecifiedTradeSettlementMonetarySummation", "ram:SpecifiedTradeSettlementLineMonetarySummation");
//xml = xml.replace("ram:SpecifiedTradeSettlementLineMonetarySummation", "ram:SpecifiedTradeSettlementHeaderMonetarySummation");
//ram:SpecifiedTradeSettlementLineMonetarySummation bleibt unterhalb von ram:SpecifiedLineTradeSettlement
//xml = xml.replace("ram:IssueDateTime","ram:FormattedIssueDateTime");
xml = xml.replace("ram:SpecifiedTradeAccountingAccount","ram:SalesSpecifiedTradeAccountingAccount");
xml = xml.replace("ram:SpecifiedSupplyChainTradeAgreement","ram:SpecifiedLineTradeAgreement");
//xml = xml.replace("ram:ID","ram:GlobalID");
//ram:ID unterhalb von ram:ContractReferencedDocument zu ram:IssuerAssignedID
//http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
/*
xml = xml.replace("rsm:SpecifiedExchangedDocumentContext", "rsm:CIExchangedDocumentContext");
xml = xml.replace("rsm:HeaderExchangedDocument", "rsm:CIIHExchangedDocument");
xml = xml.replace("SpecifiedSupplyChainTradeTransaction", "CIIHSupplyChainTradeTransaction");
xml = xml.replace("ram:GuidelineSpecifiedDocumentContextParameter",
"ram:GuidelineSpecifiedCIDocumentContextParameter");
xml = xml.replace("ram:IncludedNote", "ram:IncludedCINote");
xml = xml.replace("ram:ApplicableSupplyChainTradeAgreement", "ram:ApplicableCIIHSupplyChainTradeAgreement");
xml = xml.replace("ram:SellerTradeParty", "ram:SellerCITradeParty");
xml = xml.replace("ram:BuyerTradeParty", "ram:BuyerCITradeParty");
xml = xml.replace("ram:ApplicableSupplyChainTradeDelivery", "ram:ApplicableCIIHSupplyChainTradeDelivery");
xml = xml.replace("ram:ApplicableSupplyChainTradeSettlement", "ram:ApplicableCIIHSupplyChainTradeSettlement");
xml = xml.replace("ram:IncludedSupplyChainTradeLineItem", "ram:IncludedCIILSupplyChainTradeLineItem");
xml = xml.replace("ram:AssociatedDocumentLineDocument", "ram:AssociatedCIILDocumentLineDocument");
xml = xml.replace("ram:SpecifiedSupplyChainTradeDelivery", "ram:SpecifiedCIILSupplyChainTradeDelivery");
xml = xml.replace("ram:SpecifiedSupplyChainTradeSettlement", "ram:SpecifiedCIILSupplyChainTradeSettlement");
xml = xml.replace("ram:SpecifiedTradeProduct", "ram:SpecifiedCITradeProduct");
xml = xml.replace("ram:ActualDeliverySupplyChainEvent", "ram:ActualDeliveryCISupplyChainEvent");
xml = xml.replace("ram:SpecifiedTradeSettlementPaymentMeans", "ram:SpecifiedCITradeSettlementPaymentMeans");
xml = xml.replace("ram:PayeePartyCreditorFinancialAccount", "ram:PayeePartyCICreditorFinancialAccount");
xml = xml.replace("ram:PayeeSpecifiedCreditorFinancialInstitution",
"ram:PayeeSpecifiedCICreditorFinancialInstitution");
xml = xml.replace("ram:ApplicableTradeTax", "ram:ApplicableCITradeTax");
xml = xml.replace("ram:ApplicablePercent", "ram:RateApplicablePercent");
xml = xml.replace("ram:PostalTradeAddress", "ram:PostalCITradeAddress");
xml = xml.replace("ram:ApplicableTradePaymentDiscountTerms", "ram:ApplicableCITradePaymentDiscountTerms");
xml = xml.replace("ram:ApplicableProductCharacteristic", "ram:ApplicableCIProductCharacteristic");
xml = xml.replace("ram:ShipToTradeParty", "ram:ShipToCITradeParty");
xml = xml.replace("ram:ShipFromTradeParty", "ram:ShipFromCITradeParty");
xml = xml.replace("ram:ReceivableSpecifiedTradeAccountingAccount",
"ram:ReceivableSpecifiedCITradeAccountingAccount");
xml = xml.replace("ram:ContractReferencedDocument", "ram:ContractReferencedCIReferencedDocument");
// "ram:SpecifiedTradeAccountingAccount ram:SalesSpecifiedTradeAccountingAccount
// oder ReceivablesSpecifiedTradeAccountingAccount oder
// PurchaseSpecifiedTradeAccountingAccount
xml = xml.replace("ram:AdditionalReferencedDocument", "ram:AdditionalReferencedCIReferencedDocument");
xml = xml.replace("ram:TelephoneUniversalCommunication", "ram:TelephoneCIUniversalCommunication");
xml = xml.replace("ram:EmailURIUniversalCommunication", "ram:EmailURICIUniversalCommunication");
xml = xml.replace("ram:AdditionalReferencedDocument", "ram:AdditionalReferencedCIReferencedDocument");
xml = xml.replace("ram:IncludedReferencedProduct", "ram:IncludedReferencedProduct");
xml = xml.replace("ram:DefinedTradeContact", "ram:DefinedCITradeContact");
xml = xml.replace("ram:BillingSpecifiedPeriod", "ram:BillingCISpecifiedPeriod");
xml = xml.replace("ram:BuyerOrderReferencedDocument", "ram:BuyerOrderReferencedCIReferencedDocument");
xml = xml.replace("ram:DeliveryNoteReferencedDocument", "ram:DeliveryNoteReferencedCIReferencedDocument");
xml = xml.replace("ram:SpecifiedTradeAllowanceCharge", "ram:SpecifiedCITradeAllowanceCharge");
xml = xml.replace("ram:SpecifiedLogisticsServiceCharge", "ram:SpecifiedCILogisticsServiceCharge");
xml = xml.replace("ram:AppliedTradeAllowanceCharge", "ram:AppliedCITradeAllowanceCharge");
xml = xml.replace("ram:InvoiceeTradeParty", "ram:InvoiceeCITradeParty");
xml = xml.replace("ram:CategoryTradeTax", "ram:CategoryCITradeTax");
xml = xml.replace("ram:SpecifiedTaxRegistration", "ram:SpecifiedCITaxRegistration");
xml = xml.replace("ram:PostalTradeAddress", "ram:PostalCITradeAddress");
xml = xml.replace("ram:SpecifiedTradePaymentTerms", "ram:SpecifiedCITradePaymentTerms");
xml = xml.replace("ram:SpecifiedSupplyChainTradeAgreement", "ram:SpecifiedCIILSupplyChainTradeAgreement");
xml = xml.replace("ram:GrossPriceProductTradePrice", "ram:GrossPriceProductCITradePrice");
xml = xml.replace("ram:NetPriceProductTradePrice", "ram:NetPriceProductCITradePrice");
xml = xml.replaceAll("(?s)\\<ram:TestIndicator.*\\/ram:TestIndicator>", "");
// remove manually for the time being:
// xml=xml.replaceAll("ram:TestIndicator>(.*?)/ram:TestIndicator>", "");
// one ram:SpecifiedCIILTradeSettlementMonetarySummation will have to be
// ram:SpecifiedCIIHTradeSettlementMonetarySummation afterwards
String summationClose = "</ram:SpecifiedTradeSettlementMonetarySummation>";
int posFirstSummation = xml.indexOf(summationClose) + summationClose.length();
// if ram:SpecifiedTradeSettlementMonetarySummation were not found indexOf would
// return -1, therefore,
// to check if it
if (posFirstSummation > summationClose.length()) {
String xmlAfterFirstSummation = xml.substring(posFirstSummation);
String xmlBeforeIncludingFirstSummation = xml.substring(0, posFirstSummation);
// replace only once the header
xmlBeforeIncludingFirstSummation = xmlBeforeIncludingFirstSummation.replace(
"ram:SpecifiedTradeSettlementMonetarySummation",
"ram:SpecifiedCIIHTradeSettlementMonetarySummation");
// reconstruct the document now with a replaced first
// ram:SpecifiedTradeSettlementMonetarySummation to SpecifiedCIIH...
xml = xmlBeforeIncludingFirstSummation + xmlAfterFirstSummation;
ByteArrayOutputStream baos=new ByteArrayOutputStream();
try {
applySchematronXsl(new FileInputStream(xmlFilename), baos);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TransformerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String res=null;
try {
res=baos.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return res;
}
// replace the rest of the ram:SpecifiedTradeSettlementMonetarySummation with
// the line value SpecifiedCIIL...
xml = xml.replace("ram:SpecifiedTradeSettlementMonetarySummation",
"ram:SpecifiedCIILTradeSettlementMonetarySummation");
// the rest of the ram:SpecifiedTradeSettlementMonetarySummation should be in
// ram:ApplicableSupplyChainTradeSettlement
// xml=xml.replaceAll(Pattern.quote("ram:SpecifiedTradeSettlementMonetarySummation"),
// "ram:SpecifiedCIILTradeSettlementMonetarySummation");
*
*/
return xml;
private static File createTempFileResult(final Transformer transformer, final StreamSource toTransform,
final String suffix) throws TransformerException, IOException {
File result = File.createTempFile("ZUV_", suffix); //$NON-NLS-1$
result.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(result)) {
transformer.transform(toTransform, new StreamResult(fos));
}
return result;
}
private static class ClasspathResourceURIResolver implements URIResolver {
ClasspathResourceURIResolver() {
// Do nothing, just prevents synthetic access warning.
}
@Override
public Source resolve(String href, String base) throws TransformerException {
return new StreamSource(cl.getResourceAsStream(resourcePath + href));
}
}
public static void applySchematronXsl(final InputStream xmlFile,
final OutputStream EN16931Outstream) throws TransformerException {
Transformer transformer = factory.newTransformer(new StreamSource(cl.getResourceAsStream(resourcePath+"COMFORTtoEN16931.xsl")));
transformer.transform(new StreamSource(xmlFile), new StreamResult(EN16931Outstream));
}
}

View File

@@ -281,9 +281,8 @@ public class Toecount {
xmlName=getFilenameFromUser("ZUGFeRD 1.0 XML source", "ZUGFeRD-invoice.xml", "xml", true, false);
outName=getFilenameFromUser("ZUGFeRD 2.0 XML target", "ZUGFeRD-2-invoice.xml", "xml", false, true);
String xml = new String(Files.readAllBytes(Paths.get(xmlName)), StandardCharsets.UTF_8);
ZUGFeRDMigrator zmi=new ZUGFeRDMigrator();
xml=zmi.migrateFromV1ToV2(xml);
String xml=zmi.migrateFromV1ToV2(xmlName);
Files.write(Paths.get(outName), xml.getBytes());
} catch (IOException e) {
e.printStackTrace();

View File

@@ -0,0 +1,153 @@
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15"
xmlns:rsm="urn:ferd:CrossIndustryDocument:invoice:1p0">
<!-- use saxon6 java -cp saxon.jar com.icl.saxon.StyleSheet -o target.xml
ZUGFeRD-invoice.xml v1to2.xsl see also http://www.lenzconsulting.com/namespaces-in-xslt/
extension-element-prefixes="exsl str datetime uw" -->
<xsl:output encoding="UTF-8" indent="yes" method="xml" />
<xsl:template match="//*[local-name() = 'CrossIndustryDocument']">
<!-- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" -->
<xsl:element name="rsm:CrossIndustryInvoice" >
<xsl:apply-templates select="*" />
</xsl:element>
</xsl:template>
<!-- xsl:template match="//*[local-name() = 'HeaderExchangedDocument']">
<xsl:copy> <xsl:apply-templates select="@*|node()" /> </xsl:copy> </xsl:template> -->
<!-- attribute rename -->
<xsl:template match="//*[local-name() = 'SpecifiedExchangedDocumentContext']">
<xsl:element name="rsm:ExchangedDocumentContext">
<xsl:apply-templates select="@*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="//*[local-name() = 'HeaderExchangedDocument']">
<rsm:ExchangedDocument>
<xsl:apply-templates select="@*|node()" />
</rsm:ExchangedDocument>
</xsl:template>
<xsl:template match="//*[local-name() = 'ApplicablePercent']">
<ram:RateApplicablePercent>
<xsl:apply-templates select="@*|node()" />
</ram:RateApplicablePercent>
</xsl:template>
<xsl:template match="//*[local-name() = 'SpecifiedSupplyChainTradeDelivery']">
<ram:SpecifiedLineTradeDelivery>
<xsl:apply-templates select="@*|node()" />
</ram:SpecifiedLineTradeDelivery>
</xsl:template>
<xsl:template match="//*[local-name() = 'SpecifiedLineTradeAgreement']">
<ram:AssociatedDocumentLineDocument>
<xsl:apply-templates select="@*|node()" />
</ram:AssociatedDocumentLineDocument>
</xsl:template>
<xsl:template
match="//*[local-name() = 'SpecifiedSupplyChainTradeAgreement']">
<ram:SpecifiedLineTradeAgreement>
<xsl:apply-templates select="@*|node()" />
</ram:SpecifiedLineTradeAgreement>
</xsl:template>
<xsl:template
match="//*[local-name() = 'SpecifiedSupplyChainTradeSettlement']">
<ram:SpecifiedLineTradeSettlement>
<xsl:apply-templates select="@*|node()" />
</ram:SpecifiedLineTradeSettlement>
</xsl:template>
<!-- SpecifiedTradeSettlementMonetarySummation -> SpecifiedTradeSettlementLineMonetarySummation
unterhalb SpecifiedLineTradeSettlement -> SpecifiedTradeSettlementHeaderMonetarySummation
unterhalb ApplicableHeaderTradeSettlement -->
<xsl:template
match="//*[local-name()='SpecifiedSupplyChainTradeSettlement']/*[local-name()='SpecifiedTradeSettlementMonetarySummation']">
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<xsl:apply-templates select="@*|node()" />
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</xsl:template>
<!-- ApplicableSupplyChainTradeSettlement -->
<xsl:template
match="//*[local-name() = 'ApplicableSupplyChainTradeSettlement']/*[local-name() = 'SpecifiedTradeSettlementMonetarySummation']">
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<xsl:apply-templates select="@*|node()" />
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</xsl:template>
<xsl:template
match="//*[local-name() = 'ApplicableSupplyChainTradeAgreement']">
<ram:ApplicableHeaderTradeAgreement>
<xsl:apply-templates select="@*|node()" />
</ram:ApplicableHeaderTradeAgreement>
</xsl:template>
<xsl:template
match="//*[local-name() = 'ApplicableSupplyChainTradeDelivery']">
<ram:ApplicableHeaderTradeDelivery>
<xsl:apply-templates select="@*|node()" />
</ram:ApplicableHeaderTradeDelivery>
</xsl:template>
<xsl:template
match="//*[local-name() = 'ApplicableSupplyChainTradeSettlement']">
<ram:ApplicableHeaderTradeSettlement>
<xsl:apply-templates select="@*|node()" />
</ram:ApplicableHeaderTradeSettlement>
</xsl:template>
<!-- rename and reorder -->
<xsl:template
match="//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']">
<rsm:SupplyChainTradeTransaction><!-- surplus namespaces -->
<xsl:apply-templates
select="//*[local-name() = 'IncludedSupplyChainTradeLineItem']" />
<!-- now copy any other children that we haven't explicitly reordered;
again, possibly this is not what you want -->
<xsl:apply-templates
select="./*[not(local-name() = 'IncludedSupplyChainTradeLineItem')]" />
</rsm:SupplyChainTradeTransaction>
</xsl:template>
<!-- innerhalb IncludedSupplyChainTradeLineItem zuerst AssociatedDocumentLineDocument
dann SpecifiedTradeProduct -->
<xsl:template match="//*[local-name() = 'IncludedSupplyChainTradeLineItem']">
<ram:IncludedSupplyChainTradeLineItem>
<xsl:apply-templates
select="./*[local-name() = 'AssociatedDocumentLineDocument']" />
<xsl:apply-templates select="./*[local-name() = 'SpecifiedTradeProduct']" />
<!-- now copy any other children that we haven't explicitly reordered;
again, possibly this is not what you want -->
<xsl:apply-templates
select="./*[not(local-name() = 'AssociatedDocumentLineDocument' or local-name() = 'SpecifiedTradeProduct')]" />
</ram:IncludedSupplyChainTradeLineItem>
</xsl:template>
<!-- unterhalb von SupplyChainTradeTransaction muss zuerst die IncludedSupplyChainTradeLineItem
kommen -->
<!-- rest -->
<!-- this is the identity transform: it copies everything that isn't matched
by a more specific template -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" /><!-- @*|node() -->
</xsl:copy>
</xsl:template>
</xsl:stylesheet>