diff --git a/mustang/pom.xml b/mustang/pom.xml new file mode 100644 index 00000000..404aee03 --- /dev/null +++ b/mustang/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + org.mustangproject.ZUGFeRD + mustang + 1.0 + jar + + mustang + http://maven.apache.org + + + UTF-8 + + + + + + org.apache.pdfbox + pdfbox + 2.0.0-SNAPSHOT + compile + + + + junit + junit + 3.8.1 + test + + + diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/App.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/App.java new file mode 100644 index 00000000..2deff0ca --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/App.java @@ -0,0 +1,13 @@ +package org.mustangproject.ZUGFeRD; + +/** + * Hello world! + * + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableContact.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableContact.java new file mode 100644 index 00000000..8c49a4d6 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableContact.java @@ -0,0 +1,49 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * Neccessary interface for ZUGFeRD exporter + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ + + +public interface IZUGFeRDExportableContact { + + /** + * First and last name of the recipient + * @return + */ + String getName(); + + /** + * Postal code of the recipient + * @return + */ + String getZIP(); + + /** + * VAT ID (Umsatzsteueridentifikationsnummer) of the contact + * @return + */ + String getVATID(); + + /** + * two-letter country code of the contact + * @return + */ + String getCountry(); + + /*** + * Returns the city of the contact + * @return + */ + String getLocation(); + + /*** + * Returns the street address (street+number) of the contact + */ + String getStreet(); + +} diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java new file mode 100644 index 00000000..49622a54 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -0,0 +1,41 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * Neccessary interface for ZUGFeRD exporter + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ + +import java.math.BigDecimal; + +public interface IZUGFeRDExportableItem { + + IZUGFeRDExportableProduct getProduct(); + + /** + * The price of one item incl. taxes + * @return + */ + BigDecimal getPriceGross(); + + /** + * The price of one item excl. taxes + * @return + */ + BigDecimal getPrice(); + + /*** + * how many + * @return + */ + BigDecimal getQuantity(); + + /** + * The price of quantity items incl. taxes + * @return + */ + BigDecimal getTotalGross(); + +} diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java new file mode 100644 index 00000000..8f552eb2 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java @@ -0,0 +1,61 @@ +package org.mustangproject.ZUGFeRD; +import java.math.BigDecimal; +/** + * Mustangproject's ZUGFeRD implementation + * Necessary interface for ZUGFeRD exporter + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ + +public interface IZUGFeRDExportableProduct { + + /** + * Unit code of the product + * Most common ones are + C62 one (piece) + DAY day + HAR hectare + HUR hour + KGM kilogram + KTM kilometre + KWH kilowatt hour + LS lump sum + LTR litre + MIN minute + MMK square millimetre + MMT millimetre + MTK square metre + MTQ cubic metre + MTR metre + NAR number of articles + NPR number of pairs + P1 percent + SET set + TNE tonne (metric ton) + WEE week + + * @return + */ + String getUnit(); + + /** + * Short name of the product + * @return + */ + String getName(); + + /** + * long description of the product + * @return + */ + String getDescription(); + + /** + * VAT percent of the product (e.g. 19, or 5.1 if you like) + * @return + */ + BigDecimal getVATPercent(); + +} diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTransaction.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTransaction.java new file mode 100644 index 00000000..257c1cc2 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTransaction.java @@ -0,0 +1,102 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * Neccessary interface for ZUGFeRD exporter + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ + + + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; + +public interface IZUGFeRDExportableTransaction { + + /** + * Number, typically invoice number of the invoice + * @return + */ + String getNumber(); + + /** + * the date when the invoice was created + * @return + */ + Date getIssueDate(); + + + /** + * total amount incl. taxes + * @return + */ + BigDecimal getTotalGross(); + + /** + * total amount excl. taxes + * @return + */ + BigDecimal getTotal(); + + /** + * when the invoice is to be paid + * @return + */ + Date getDueDate(); + + IZUGFeRDExportableItem[] getZFItems(); + + /*** + * the recipient + * @return + */ + IZUGFeRDExportableContact getRecipient(); + + /*** + * BIC of the sender + * @return + */ + String getOwnBIC(); + + /*** + * Bank name of the sender + * @return + */ + String getOwnBankName(); + + /** + * IBAN of the sender + * @return + */ + String getOwnIBAN(); + + /** + * Tax ID (not VAT ID) of the sender + */ + String getOwnTaxID(); + + /** + * VAT ID (Umsatzsteueridentifikationsnummer) of the sender + * @return + */ + String getOwnVATID(); + + /** + * own name + * @return + */ + String getOwnOrganisationName(); + + /** + * which taxes have been used with which amounts in this transaction, + * empty for no taxes, or e.g. 19=>190 and 7=>14 if 1000 Eur were applicable + * to 19% VAT (=>190 EUR VAT) and 200 EUR were applicable to 7% (=>14 EUR VAT) + * 190 Eur + * @return + */ + HashMap getVATPercentAmountMap(); + +} diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/LICENSE b/mustang/src/main/java/org/mustangproject/ZUGFeRD/LICENSE new file mode 100644 index 00000000..d4ecc703 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/LICENSE @@ -0,0 +1,335 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +EXTERNAL COMPONENTS + +Apache PDFBox includes a number of components with separate copyright notices +and license terms. Your use of these components is subject to the terms and +conditions of the following licenses. + +Contributions made to the original PDFBox, JempBox and FontBox projects: + + Copyright (c) 2002-2007, www.pdfbox.org + Copyright (c) 2006-2007, www.jempbox.org + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of pdfbox; nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +Adobe Font Metrics (AFM) for PDF Core 14 Fonts + + This file and the 14 PostScript(R) AFM files it accompanies may be used, + copied, and distributed for any purpose and without charge, with or without + modification, provided that all copyright notices are retained; that the + AFM files are not distributed without this file; that all modifications + to this file or any of the AFM files are prominently noted in the modified + file(s); and that this paragraph is not modified. Adobe Systems has no + responsibility or obligation to support the use of the AFM files. + +CMaps for PDF Fonts (http://opensource.adobe.com/wiki/display/cmap/Downloads) + + Copyright 1990-2009 Adobe Systems Incorporated. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + +Glyphlist (http://www.adobe.com/devnet/opentype/archives/glyph.html) + + Copyright (c) 1997,1998,2002,2007 Adobe Systems Incorporated + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this documentation file to use, copy, publish, distribute, + sublicense, and/or sell copies of the documentation, and to permit + others to do the same, provided that: + - No modification, editing or other alteration of this document is + allowed; and + - The above copyright notice and this permission notice shall be + included in all copies of the documentation. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this documentation file, to create their own derivative works + from the content of this document to use, copy, publish, distribute, + sublicense, and/or sell the derivative works, and to permit others to do + the same, provided that the derived work is not represented as being a + copy or version of this document. + + Adobe shall not be liable to any party for any loss of revenue or profit + or for indirect, incidental, special, consequential, or other similar + damages, whether based on tort (including without limitation negligence + or strict liability), contract or other legal or equitable grounds even + if Adobe has been advised or had reason to know of the possibility of + such damages. The Adobe materials are provided on an "AS IS" basis. + Adobe specifically disclaims all express, statutory, or implied + warranties relating to the Adobe materials, including but not limited to + those concerning merchantability or fitness for a particular purpose or + non-infringement of any third party rights regarding the Adobe + materials. + + +PaDaF PDF/A preflight (http://sourceforge.net/projects/padaf) + + Copyright 2010 Atos Worldline SAS + + Licensed by Atos Worldline SAS under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + Atos Worldline SAS licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/NOTICE b/mustang/src/main/java/org/mustangproject/ZUGFeRD/NOTICE new file mode 100644 index 00000000..75b846a1 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/NOTICE @@ -0,0 +1,25 @@ +Mustangproject +Copyright 2014 Jochen Staerk + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Based on Apache PDFBox developed at +The Apache Software Foundation (http://www.apache.org/). + +Apache PDFBox was based on source code originally developed in the PDFBox, JempBox and FontBox projects. +Copyright (c) 2002-2007, www.pdfbox.org +Copyright (c) 2006-2007, www.jempbox.org + +Apache PDFBox was based on source code originally developed in the PaDaF project. +Copyright (c) 2010 Atos Worldline SAS \ No newline at end of file diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java new file mode 100644 index 00000000..76c2b318 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java @@ -0,0 +1,160 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * ZUGFeRD exporter helper class + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ +import org.apache.jempbox.impl.XMLUtil; +import org.apache.jempbox.xmp.XMPSchemaBasic; +import org.w3c.dom.Element; + + /** + * Additionally to adding a RDF namespace with a indication which file + * attachment if Zugferd, this namespace has to be described in a PDFA + * Extension Schema. + * + * I know there is a PDFAExtensionSchema in the context of PDFBox' XMPBOX + * but I have been using PDFBox' JempBOX so far because I could not find out + * how to write XMPBOX XMPMetadata to a PDF file. + * + * So this is my version of PDFAExtensionSchema + * for PDFBox' jempbox XMPMetadata + * + * @author jstaerk + * + */ + public class XMPSchemaPDFAExtensions extends XMPSchemaBasic { + + private void addProperty(Element listNode, String name, String type, + String category, String description) { + Element nameNode = schema.getOwnerDocument().createElement( + "pdfaProperty:name"); //$NON-NLS-1$ + XMLUtil.setStringValue(nameNode, name); + listNode.appendChild(nameNode); + + Element typeNode = schema.getOwnerDocument().createElement( + "pdfaProperty:valueType"); //$NON-NLS-1$ + XMLUtil.setStringValue(typeNode, type); + listNode.appendChild(typeNode); + + Element categoryNode = schema.getOwnerDocument().createElement( + "pdfaProperty:category"); //$NON-NLS-1$ + XMLUtil.setStringValue(categoryNode, category); + listNode.appendChild(categoryNode); + + Element descriptionNode = schema.getOwnerDocument().createElement( + "pdfaProperty:description"); //$NON-NLS-1$ + XMLUtil.setStringValue(descriptionNode, description); + listNode.appendChild(descriptionNode); + + } + + + + public XMPSchemaPDFAExtensions(org.apache.jempbox.xmp.XMPMetadata parent) { + super(parent); + + // add some namespaces + schema.setAttributeNS(NS_NAMESPACE, "xmlns:pdfaExtension", //$NON-NLS-1$ + "http://www.aiim.org/pdfa/ns/extension/"); //$NON-NLS-1$ + + schema.setAttributeNS(NS_NAMESPACE, "xmlns:pdfaSchema", //$NON-NLS-1$ + "http://www.aiim.org/pdfa/ns/schema#"); //$NON-NLS-1$ + + schema.setAttributeNS(NS_NAMESPACE, "xmlns:pdfaProperty", //$NON-NLS-1$ + "http://www.aiim.org/pdfa/ns/property#"); //$NON-NLS-1$ + + // the superclass includes this two namespaces we don't need + schema.removeAttributeNS(NS_NAMESPACE, "xapGImg"); //$NON-NLS-1$ + schema.removeAttributeNS(NS_NAMESPACE, "xmp"); //$NON-NLS-1$ + + /* + *What we attach is basically this: + *pdfaExtension:schemas-node + *+--bag + * +--rdf:li + * +--some text node (multiple) + * +--property node + * +--rdf:Seq + * +--rdf:li (multiple) attribute node + * +--some attribute property description text node (multiple) + */ + Element schemasNode = schema.getOwnerDocument().createElement( + "pdfaExtension:schemas"); //$NON-NLS-1$ + + Element bagNode = schema.getOwnerDocument() + .createElement("rdf:Bag"); //$NON-NLS-1$ + + Element bagListNode = schema.getOwnerDocument().createElement( + "rdf:li"); //$NON-NLS-1$ + + bagListNode.setAttribute("rdf:parseType", "Resource"); //$NON-NLS-1$ //$NON-NLS-2$ + + Element schemaInfoNode = schema.getOwnerDocument().createElement( + "pdfaSchema:schema"); //$NON-NLS-1$ + XMLUtil.setStringValue(schemaInfoNode, + "ZUGFeRD PDFA Extension Schema"); //$NON-NLS-1$ + bagListNode.appendChild(schemaInfoNode); + + Element nsInfoNode = schema.getOwnerDocument().createElement( + "pdfaSchema:namespaceURI"); //$NON-NLS-1$ + XMLUtil.setStringValue(nsInfoNode, "urn:ferd:pdfa:invoice:rc#"); //$NON-NLS-1$ + bagListNode.appendChild(nsInfoNode); + + Element prefixInfoNode = schema.getOwnerDocument().createElement( + "pdfaSchema:prefix"); //$NON-NLS-1$ + XMLUtil.setStringValue(prefixInfoNode, "zf"); //$NON-NLS-1$ + bagListNode.appendChild(prefixInfoNode); + + Element propertyNode = schema.getOwnerDocument().createElement( + "pdfaSchema:property"); //$NON-NLS-1$ + + Element sequenceNode = schema.getOwnerDocument().createElement( + "rdf:Seq"); //$NON-NLS-1$ + + Element seqList1Node = schema.getOwnerDocument().createElement( + "rdf:li"); //$NON-NLS-1$ + + seqList1Node.setAttribute("rdf:parseType", "Resource"); //$NON-NLS-1$ //$NON-NLS-2$ + + addProperty(seqList1Node, "DocumentFileName", "Text", "external", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "The name of the embedded Zugferd XML invoice file"); //$NON-NLS-1$ + + sequenceNode.appendChild(seqList1Node); + + Element seqList2Node = schema.getOwnerDocument().createElement( + "rdf:li"); //$NON-NLS-1$ + seqList2Node.setAttribute("rdf:parseType", "Resource"); //$NON-NLS-1$ //$NON-NLS-2$ + addProperty(seqList2Node, "DocumentType", "Text", "external", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "INVOICE"); //$NON-NLS-1$ + sequenceNode.appendChild(seqList2Node); + + Element seqList3Node = schema.getOwnerDocument().createElement( + "rdf:li"); //$NON-NLS-1$ + seqList3Node.setAttribute("rdf:parseType", "Resource"); //$NON-NLS-1$ //$NON-NLS-2$ + addProperty(seqList3Node, "Version", "Text", "external", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "The version of the ZUGFeRD data"); //$NON-NLS-1$ + sequenceNode.appendChild(seqList3Node); + + Element seqList4Node = schema.getOwnerDocument().createElement( + "rdf:li"); //$NON-NLS-1$ + seqList4Node.setAttribute("rdf:parseType", "Resource"); //$NON-NLS-1$ //$NON-NLS-2$ + addProperty(seqList4Node, "ConformanceLevel", "Text", "external", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "The conformance level of the ZUGFeRD data, i.e. BASIC or EXTENDED"); //$NON-NLS-1$ + sequenceNode.appendChild(seqList4Node); + + propertyNode.appendChild(sequenceNode); + + bagListNode.appendChild(propertyNode); + + bagNode.appendChild(bagListNode); + schemasNode.appendChild(bagNode); + + schema.appendChild(schemasNode); + + } + + } diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java new file mode 100644 index 00000000..c06fa286 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java @@ -0,0 +1,50 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * ZUGFeRD exporter helper class + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ +import org.apache.jempbox.impl.XMLUtil; +import org.apache.jempbox.xmp.XMPSchemaBasic; +import org.w3c.dom.Element; + + +public class XMPSchemaZugferd extends XMPSchemaBasic { + + /** + * This is what needs to be added to the RDF metadata - basically the name of the embedded + * Zugferd file + * */ + public XMPSchemaZugferd(org.apache.jempbox.xmp.XMPMetadata parent) { + super(parent); + + schema.setAttributeNS(NS_NAMESPACE, "xmlns:zf", //$NON-NLS-1$ + "urn:ferd:pdfa:invoice:rc#"); //$NON-NLS-1$ +// the superclass includes this two namespaces we don't need + schema.removeAttributeNS(NS_NAMESPACE, "xapGImg"); //$NON-NLS-1$ + schema.removeAttributeNS(NS_NAMESPACE, "xmp"); //$NON-NLS-1$ + Element textNode = schema.getOwnerDocument().createElement( + "zf:DocumentType"); //$NON-NLS-1$ + XMLUtil.setStringValue(textNode, "INVOICE"); //$NON-NLS-1$ + schema.appendChild(textNode); + + textNode = schema.getOwnerDocument().createElement( + "zf:DocumentFileName"); //$NON-NLS-1$ + XMLUtil.setStringValue(textNode, "ZUGFeRD-invoice.xml"); //$NON-NLS-1$ + schema.appendChild(textNode); + + textNode = schema.getOwnerDocument().createElement("zf:Version"); //$NON-NLS-1$ + XMLUtil.setStringValue(textNode, "RC"); //$NON-NLS-1$ + schema.appendChild(textNode); + + textNode = schema.getOwnerDocument().createElement( + "zf:ConformanceLevel"); //$NON-NLS-1$ + XMLUtil.setStringValue(textNode, "BASIC"); //$NON-NLS-1$ + schema.appendChild(textNode); + + } + + } \ No newline at end of file diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java new file mode 100644 index 00000000..0b6d2d63 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java @@ -0,0 +1,497 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * ZUGFeRD exporter + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; + +import javax.xml.transform.TransformerException; + +import org.apache.jempbox.xmp.XMPMetadata; +import org.apache.jempbox.xmp.XMPSchemaBasic; +import org.apache.jempbox.xmp.XMPSchemaDublinCore; +import org.apache.jempbox.xmp.XMPSchemaPDF; +import org.apache.jempbox.xmp.pdfa.XMPSchemaPDFAId; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; + + +public class ZUGFeRDExporter { + /*** + * You will need Apache PDFBox. To use the ZUGFeRD exporter, + implement IZUGFeRDExportableTransaction in yourTransaction + (which will require you to implement Product, Item and Contact) + then call + doc = PDDocument.load(PDFfilename); + // automatically add Zugferd to all outgoing invoices + ZUGFeRDExporter ze = new ZUGFeRDExporter(); + ze.PDFmakeA3compliant(doc, "Your application name", + System.getProperty("user.name"), true); + ze.PDFattachZugferdFile(doc, yourTransaction); + + doc.save(PDFfilename); + + * @author jstaerk + * + */ + + + + + + //// MAIN CLASS + + private String conformanceLevel="U"; + private String versionStr="1.0"; + + private String currencyFormat(BigDecimal value, char decimalDelimiter) { + /* + * I needed 123,45, locale independent.I tried + * NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is + * locale specific.I also tried DecimalFormat df = new DecimalFormat( + * "0,00" ); df.setDecimalSeparatorAlwaysShown(true); + * df.setGroupingUsed(false); DecimalFormatSymbols symbols = new + * DecimalFormatSymbols(); symbols.setDecimalSeparator(','); + * symbols.setGroupingSeparator(' '); + * df.setDecimalFormatSymbols(symbols); + * + * but that would not switch off grouping. Although I liked very much + * the (incomplete) "BNF diagram" in + * http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html + * in the end I decided to calculate myself and take eur+sparator+cents + * + * This function will cut off, i.e. floor() subcent values Tests: + * System.err.println(utils.currencyFormat(new BigDecimal(0), + * ".")+"\n"+utils.currencyFormat(new BigDecimal("-1.10"), + * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.1"), + * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.01"), + * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3489"), + * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3419"), + * ",")+"\n"+utils.currencyFormat(new BigDecimal("12"), ",")); + * + * results 0.00 -1,10 -1,10 -1,01 20000123,34 20000123,34 12,00 + */ + value=value.setScale( 2, BigDecimal.ROUND_HALF_UP ); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19 + long totalCent = value.multiply(new BigDecimal(100)).intValue(); //now get the cents + long eurOnly = value.longValue(); + long centOnly = Math.abs(totalCent % 100); + StringBuffer res = new StringBuffer(); + res.append(eurOnly); + res.append(decimalDelimiter); + if (centOnly < 10) { + res.append('0'); + } + res.append(centOnly); + return res.toString(); + } + + /** + All files are PDF/A-3, setConformance refers to the level conformance. + + PDF/A-3 has three coformance levels, called "A", "U" and "B". + + PDF/A-3-B where B means only visually + preservable, U -standard for Mustang- means visually and unicode + preservable and A means full compliance, i.e. visually, + unicode and structurally preservable and tagged PDF, i.e. useful metainformation for blind people. + + Feel free to pass "A" as new level if you know what you are doing :-) + + + */ + public void setConformanceLevel(String newLevel) { + conformanceLevel=newLevel; + } + + + /** + * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on + * the metadata level, this will not e.g. convert graphics to JPG-2000) + * */ + public PDDocumentCatalog PDFmakeA3compliant(PDDocument doc, String producer, String creator, + boolean attachZugferdHeaders) throws IOException, + TransformerException { + String fullProducer=producer+"(via mustangproject.org "+versionStr+")"; + PDDocumentCatalog cat = doc.getDocumentCatalog(); + PDMetadata metadata = new PDMetadata(doc); + cat.setMetadata(metadata); + // we're using the jempbox org.apache.jempbox.xmp.XMPMetadata version, + // not the xmpbox one + XMPMetadata xmp = new XMPMetadata(); + + XMPSchemaPDFAId pdfaid = new XMPSchemaPDFAId(xmp); + pdfaid.setAbout(""); //$NON-NLS-1$ + xmp.addSchema(pdfaid); + + XMPSchemaDublinCore dc = xmp.addDublinCoreSchema(); + dc.addCreator(creator); + dc.setAbout(""); //$NON-NLS-1$ + + XMPSchemaBasic xsb = xmp.addBasicSchema(); + xsb.setAbout(""); //$NON-NLS-1$ + + xsb.setCreatorTool(creator); + xsb.setCreateDate(GregorianCalendar.getInstance()); + // PDDocumentInformation pdi=doc.getDocumentInformation(); + PDDocumentInformation pdi = new PDDocumentInformation(); + pdi.setProducer(fullProducer); + pdi.setAuthor(creator); + doc.setDocumentInformation(pdi); + + XMPSchemaPDF pdf = xmp.addPDFSchema(); + pdf.setProducer(fullProducer); + pdf.setAbout(""); //$NON-NLS-1$ + + /* + // Mandatory: PDF/A3-a is tagged PDF which has to be expressed using a + // MarkInfo dictionary (PDF A/3 Standard sec. 6.7.2.2) + PDMarkInfo markinfo = new PDMarkInfo(); + markinfo.setMarked(true); + doc.getDocumentCatalog().setMarkInfo(markinfo); +*/ +/* + * + To be on the safe side, we use level B without Markinfo because we can not + guarantee that the user correctly tagged the templates for the PDF. + + * */ + pdfaid.setConformance(conformanceLevel);//$NON-NLS-1$ //$NON-NLS-1$ + + pdfaid.setPart(3); + + if (attachZugferdHeaders) { + addZugferdXMP(xmp); /* + * this is the only line where we do something + * Zugferd-specific, i.e. add PDF metadata + * specifically for Zugferd, not generically for + * a embedded file + */ + } + + metadata.importXMPMetadata(xmp); + return cat; + } + + private String getZugferdXMLForTransaction(IZUGFeRDExportableTransaction trans) { + SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$ + String xml= "\n" //$NON-NLS-1$ + + "\n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " false\n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " urn:ferd:invoice:rc:comfort\n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+trans.getNumber()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " RECHNUNG\n" //$NON-NLS-1$ + + " 380\n" //$NON-NLS-1$ + + " "+zugferdDateFormat.format(trans.getIssueDate())+"\n" //date format was 20130605 //$NON-NLS-1$ //$NON-NLS-2$ +// + " \n" +// + " \n" +// + "Rechnung gemäß Bestellung Nr. 2013-471331 vom 01.03.2013.\n" +// + "\n" +// + " \n" +// + " \n" +// + " \n" +// + " \n" +// + "Es bestehen Rabatt- und Bonusvereinbarungen.\n" +// + " \n" +// + " AAK\n" +// + " \n" + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ +// + " AB-312\n" + + " \n" //$NON-NLS-1$ +// + " 4000001123452\n" + + " "+trans.getOwnOrganisationName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ +/* + " \n" + + " 80333\n" + + " Lieferantenstraße 20\n" + + " München\n" + + " DE\n" + + " \n"*/ + + " \n" //$NON-NLS-1$ + + " "+trans.getOwnTaxID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+trans.getOwnVATID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ +// + " GE2020211\n" +// + " 4000001987658\n" + + " "+trans.getRecipient().getName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ +// + " \n" +// + " xxx\n" +// + " \n" + + " \n" //$NON-NLS-1$ + + " "+trans.getRecipient().getZIP()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+trans.getRecipient().getStreet()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+trans.getRecipient().getLocation()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+trans.getRecipient().getCountry()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+trans.getRecipient().getVATID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ +// + " \n" +// + " 20130301\n" +// + " 2013-471331\n" +// + " \n" + + " \n" //$NON-NLS-1$ +/* + " \n" + + " \n" + + " 20130603\n" + + " \n" + + " \n" + + " 20130603\n" + + " 2013-51112\n" + + " \n" + + " \n"*/ + + " \n" //$NON-NLS-1$ + // it's unclear if this is supposed to refer to the SEPA mandate or the transaction purpose +// + " "+trans.getNumber()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " EUR\n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " 42\n" //$NON-NLS-1$ + + " Überweisung\n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+trans.getOwnIBAN()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+trans.getOwnBIC()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+trans.getOwnBankName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n"; //$NON-NLS-1$ + + + + HashMap VATPercentAmountMap=trans.getVATPercentAmountMap(); + for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { + BigDecimal amount = VATPercentAmountMap.get(currentTaxPercent); + if (amount != null) { + xml += " \n" //$NON-NLS-1$ + + " "+currencyFormat(amount, '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " VAT\n" //$NON-NLS-1$ +// + " 129.37\n" + + " S\n" //$NON-NLS-1$ + + " "+currentTaxPercent+"\n" //$NON-NLS-1$ + + " \n"; //$NON-NLS-1$ + + + + } + } +/* xml+= " + + " \n" + + " false\n" + + " 10\n" + + " 1.00\n" + + " Sondernachlass\n" + + " \n" + + " VAT\n" + + " S\n" + + " 19\n" + + " \n" + + " \n" + + " \n" + + " false\n" + + " 137.30\n" + + " 13.73\n" + + " Sondernachlass\n" + + " \n" + + " VAT\n" + + " S\n" + + " 7\n" + + " \n" + + " \n" + + " \n" + + " Versandkosten\n" + + " 5.80\n" + + " \n" + + " VAT\n" + + " S\n" + + " 7\n" + + " \n" + + " \n"*/ + + xml=xml+ " \n" //$NON-NLS-1$ +// + " Zahlbar innerhalb 30 Tagen netto bis 04.07.2013, 3% Skonto innerhalb 10 Tagen bis 15.06.2013\n" + + " "+zugferdDateFormat.format(trans.getDueDate())+"\n"//20130704 //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+currencyFormat(trans.getTotal(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ +// + " 5.80\n" +// + " 14.73\n" + + " "+currencyFormat(trans.getTotal(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+currencyFormat(trans.getTotalGross().subtract(trans.getTotal()), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+currencyFormat(trans.getTotalGross(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ +// + " 0.00\n" + + " "+currencyFormat(trans.getTotalGross(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n"; //$NON-NLS-1$ +// + " \n" +// + " \n" +// + " \n" +// + " Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2013-51112 in Rechnung zu stellen:\n" +// + " \n" +// + " \n" +// + " \n"; + + + int lineID=0; + for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { + lineID++; + xml=xml+ " \n"+ //$NON-NLS-1$ + " \n" //$NON-NLS-1$ + + " "+lineID+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+currencyFormat(currentItem.getPriceGross(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " 1\n" //$NON-NLS-1$ //$NON-NLS-2$ +// + " \n" +// + " false\n" +// + " 0.6667\n" +// + " Rabatt\n" +// + " \n" + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+currencyFormat(currentItem.getPrice(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " 1\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + + " \n" //$NON-NLS-1$ + + " "+currentItem.getQuantity()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " VAT\n" //$NON-NLS-1$ + + " S\n" //$NON-NLS-1$ + + " "+currentItem.getProduct().getVATPercent()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+currencyFormat(currentItem.getTotalGross(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ +// + " 4012345001235\n" +// + " KR3M\n" +// + " 55T01\n" + + " "+currentItem.getProduct().getName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+currentItem.getProduct().getDescription()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n"; //$NON-NLS-1$ + + + + } + + + xml=xml + " \n" //$NON-NLS-1$ + + ""; //$NON-NLS-1$ + return xml; + } + + /** + * embed the Zugferd XML structure in a file named ZUGFeRD-invoice.xml + * */ + public void PDFattachZugferdFile(PDDocument doc, IZUGFeRDExportableTransaction trans) throws IOException { + + // embedded files are stored in a named tree + PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode(); + + String filename="ZUGFeRD-invoice.xml"; //$NON-NLS-1$ + // first create the file specification, which holds the embedded file + PDComplexFileSpecification fs = new PDComplexFileSpecification(); + fs.setFile(filename); + + COSDictionary dict = fs.getCOSDictionary(); + // Relation "Source" for linking with eg. catalog + dict.setName("AFRelationship", "Alternative"); // as defined in Zugferd standard //$NON-NLS-1$ //$NON-NLS-2$ + + dict.setString("UF", filename); //$NON-NLS-1$ + + // create a dummy file stream, this would probably normally be a + // FileInputStream + + byte[] zugferdRaw = getZugferdXMLForTransaction(trans).getBytes("UTF-8"); //$NON-NLS-1$ + + byte[] zugferdData; + + + + if ((zugferdRaw[0]==(byte)0xEF)&&(zugferdRaw[1]==(byte)0xBB)&&(zugferdRaw[2]==(byte)0xBF)) { + // I don't like BOMs, lets remove it + zugferdData=new byte[zugferdRaw.length-3]; + System.arraycopy(zugferdRaw,3,zugferdData,0,zugferdRaw.length-3); + } else { + zugferdData=zugferdRaw; + } + + ByteArrayInputStream fakeFile = new ByteArrayInputStream(zugferdData); + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); + // now lets some of the optional parameters + ef.setSubtype("text/xml");// as defined in Zugferd standard //$NON-NLS-1$ + ef.setSize(zugferdData.length); + ef.setCreationDate(new GregorianCalendar()); + + ef.setModDate(GregorianCalendar.getInstance()); + + fs.setEmbeddedFile(ef); + + // now add the entry to the embedded file tree and set in the document. + efTree.setNames(Collections.singletonMap(filename, fs)); + PDDocumentNameDictionary names = new PDDocumentNameDictionary( + doc.getDocumentCatalog()); + names.setEmbeddedFiles(efTree); + doc.getDocumentCatalog().setNames(names); + // AF entry (Array) in catalog with the FileSpec + COSArray cosArray = new COSArray(); + cosArray.add(fs); + doc.getDocumentCatalog().getCOSDictionary().setItem("AF", cosArray); //$NON-NLS-1$ + + } + +/*** + * This will add both the RDF-indication which embedded file is Zugferd and the + * neccessary PDF/A schema extension description to be able to add this information to RDF + * @param metadata + */ + private void addZugferdXMP(XMPMetadata metadata) { + + XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata); + zf.setAbout(""); //$NON-NLS-1$ + metadata.addSchema(zf); + + XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(metadata); + pdfaex.setAbout(""); //$NON-NLS-1$ + metadata.addSchema(pdfaex); + + } + +} diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java new file mode 100644 index 00000000..7f1ea430 --- /dev/null +++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -0,0 +1,330 @@ +package org.mustangproject.ZUGFeRD; +/** + * Mustangproject's ZUGFeRD implementation + * ZUGFeRD importer + * Licensed under the APLv2 + * @date 2014-05-10 + * @version 1.0 + * @author jstaerk + * */ + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.common.COSObjectable; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +public class ZUGFeRDImporter { + /* + call extract(importFilename). + containsMeta() will return if ZUGFeRD data has been found, + afterwards you can call getBIC(), getIBAN() etc. + +*/ + + /**@var if metadata has been found */ + private boolean containsMeta=false; + /**@var the reference (i.e. invoice number) of the sender */ + private String foreignReference; + private String BIC; + private String IBAN; + private String holder; + private String amount; + private String meta; + private String bankName; + private boolean amountFound; + + + + + public void extract(String pdfFilename) { + PDDocument doc; + try { + doc = PDDocument.load(pdfFilename); +// PDDocumentInformation info = doc.getDocumentInformation(); + PDEmbeddedFilesNameTreeNode etn; + PDDocumentNameDictionary names = new PDDocumentNameDictionary( + doc.getDocumentCatalog()); + etn = names.getEmbeddedFiles(); + Map efMap = etn.getNames(); + // String filePath = "/tmp/"; + + for (String filename : efMap.keySet()) { + /** + * currently (in the release candidate of version 1) only one + * attached file with the name ZUGFeRD-invoice.xml is allowed + * */ + if (filename.equals("ZUGFeRD-invoice.xml")) { //$NON-NLS-1$ + containsMeta = true; + + PDComplexFileSpecification fileSpec = (PDComplexFileSpecification) efMap + .get(filename); + PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile(); + // String embeddedFilename = filePath + filename; + // File file = new File(filePath + filename); + // System.out.println("Writing " + embeddedFilename); + // ByteArrayOutputStream fileBytes=new + // ByteArrayOutputStream(); + // FileOutputStream fos = new FileOutputStream(file); + + setMeta(new String(embeddedFile.getByteArray())); + // fos.write(embeddedFile.getByteArray()); + // fos.close(); + } + } + + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + } + + public void parse() { + DocumentBuilderFactory factory = null; + DocumentBuilder builder = null; + Document document = null; + + factory = DocumentBuilderFactory.newInstance(); + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException ex3) { + // TODO Auto-generated catch block + ex3.printStackTrace(); + } + + try { + InputStream bais = new ByteArrayInputStream(meta.getBytes()); + document = builder.parse(bais); + } catch (SAXException ex1) { + ex1.printStackTrace(); + } catch (IOException ex2) { + ex2.printStackTrace(); + } + // rootNode = document.getDocumentElement(); + // ApplicableSupplyChainTradeSettlement + NodeList ndList = document + .getElementsByTagName("PaymentReference"); //$NON-NLS-1$ + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // if there is a attribute in the tag number:value + + setForeignReference(booking.getTextContent()); + + } +/* + ndList = document + .getElementsByTagName("GermanBankleitzahlID"); //$NON-NLS-1$ + + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // if there is a attribute in the tag number:value + setBIC(booking.getTextContent()); + + } + + ndList = document.getElementsByTagName("ProprietaryID"); //$NON-NLS-1$ + + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // if there is a attribute in the tag number:value + setIBAN(booking.getTextContent()); + + } +*/ + ndList = document.getElementsByTagName("PayeePartyCreditorFinancialAccount"); //$NON-NLS-1$ + + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // there are many "name" elements, so get the one below + // SellerTradeParty + NodeList bookingDetails = booking.getChildNodes(); + for (int detailIndex = 0; detailIndex < bookingDetails + .getLength(); detailIndex++) { + Node detail = bookingDetails.item(detailIndex); + if (detail.getNodeName().equals("IBANID")) { //$NON-NLS-1$ + setIBAN(detail.getTextContent()); + } + } + + } + + ndList = document.getElementsByTagName("PayeeSpecifiedCreditorFinancialInstitution"); //$NON-NLS-1$ + + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // there are many "name" elements, so get the one below + // SellerTradeParty + NodeList bookingDetails = booking.getChildNodes(); + for (int detailIndex = 0; detailIndex < bookingDetails + .getLength(); detailIndex++) { + Node detail = bookingDetails.item(detailIndex); + if (detail.getNodeName().equals("BICID")) { //$NON-NLS-1$ + setBIC(detail.getTextContent()); + } + if (detail.getNodeName().equals("Name")) { //$NON-NLS-1$ + setBankName(detail.getTextContent()); + } + } + + } + + // + ndList = document.getElementsByTagName("SellerTradeParty"); //$NON-NLS-1$ + + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // there are many "name" elements, so get the one below + // SellerTradeParty + NodeList bookingDetails = booking.getChildNodes(); + for (int detailIndex = 0; detailIndex < bookingDetails + .getLength(); detailIndex++) { + Node detail = bookingDetails.item(detailIndex); + if (detail.getNodeName().equals("Name")) { //$NON-NLS-1$ + setHolder(detail.getTextContent()); + } + } + + } + + ndList = document.getElementsByTagName("DuePayableAmount"); //$NON-NLS-1$ + + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // if there is a attribute in the tag number:value + amountFound=true; + setAmount(booking.getTextContent()); + + } + + + if (!amountFound) { + /* there is apparently no requirement to mention DuePayableAmount,, + * if it's not there, check for GrandTotalAmount + */ + ndList = document.getElementsByTagName("GrandTotalAmount"); //$NON-NLS-1$ + for (int bookingIndex = 0; bookingIndex < ndList + .getLength(); bookingIndex++) { + Node booking = ndList.item(bookingIndex); + // if there is a attribute in the tag number:value + amountFound=true; + setAmount(booking.getTextContent()); + + } + + } + + + } + + + public boolean containsMeta() { + return containsMeta; + } + + public String getForeignReference() { + return foreignReference; + } + + + + public void setForeignReference(String foreignReference) { + this.foreignReference = foreignReference; + } + + + + public String getBIC() { + return BIC; + } + + + + private void setBIC(String bic) { + this.BIC = bic; + } + + + private void setBankName(String bankname) { + this.bankName = bankname; + } + + + + public String getIBAN() { + return IBAN; + } + + + public String getBankName() { + return bankName; + } + + + + public void setIBAN(String IBAN) { + this.IBAN = IBAN; + } + + + + public String getHolder() { + return holder; + } + + + + private void setHolder(String holder) { + this.holder = holder; + } + + + + public String getAmount() { + return amount; + } + + + private void setAmount(String amount) { + this.amount = amount; + } + + public void setMeta(String meta) { + this.meta=meta; + } + + public String getMeta() { + return meta; + } + + /** + * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML + * */ + public boolean canParse() { + + + //SpecifiedExchangedDocumentContext is in the schema, so a relatively good indication if zugferd is present - better than just invoice + return (meta!=null)&&( meta.length()>0)&&( meta.contains("SpecifiedExchangedDocumentContext")); //$NON-NLS-1$ + } +} diff --git a/mustang/src/test/java/org/mustangproject/ZUGFeRD/AppTest.java b/mustang/src/test/java/org/mustangproject/ZUGFeRD/AppTest.java new file mode 100644 index 00000000..a8cece5d --- /dev/null +++ b/mustang/src/test/java/org/mustangproject/ZUGFeRD/AppTest.java @@ -0,0 +1,38 @@ +package org.mustangproject.ZUGFeRD; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +}