factur-x 3.16.dev0__tar.gz → 4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {factur_x-3.16.dev0 → factur_x-4.1}/PKG-INFO +15 -2
- {factur_x-3.16.dev0 → factur_x-4.1}/README.rst +13 -1
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/__init__.py +2 -5
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/facturx.py +205 -57
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/pdfextractxml.py +10 -6
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/pdfgen.py +12 -6
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/xmlcheck.py +11 -6
- factur_x-4.1/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC-compiled.xsl +9175 -0
- factur_x-4.1/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC_codedb.xml +4866 -0
- factur_x-4.1/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL-compiled.xsl +6581 -0
- factur_x-4.1/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL_codedb.xml +2260 -0
- factur_x-4.1/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931-compiled.xsl +11895 -0
- factur_x-4.1/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931_codedb.xml +5897 -0
- factur_x-4.1/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED-compiled.xsl +26245 -0
- factur_x-4.1/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED_codedb.xml +7113 -0
- factur_x-4.1/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM-compiled.xsl +1939 -0
- factur_x-4.1/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM_codedb.xml +1111 -0
- factur_x-4.1/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +2931 -0
- factur_x-4.1/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +4364 -0
- factur_x-4.1/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +6855 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/requirements.txt +1 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/.flake8 +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/.gitignore +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/LICENSE +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/__init__.py +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/webservice.py +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xmp/Factur-X_extension_schema.xmp +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xmp/ZUGFeRD_extension_schema.xmp +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_ISO_ISO3AlphaCurrencyCode_2012-08-31.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ActionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DeliveryTermsCode_2010.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DeliveryTermsFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DocumentNameCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DocumentStatusCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_MessageFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_QualifiedDataType_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_identifierlist_standard_ISO_ISOTwo-letterCountryCode_SecondEdition2006.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_EDIFICAS-EU_AccountingAccountType_D11A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_EN16931_AllowanceChargeReasonCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_ISO_ISO3AlphaCurrencyCode_2012-08-31.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ActionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ContactFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DeliveryTermsCode_2010.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DeliveryTermsFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DocumentNameCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DocumentStatusCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DutyTaxFeeTypeCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DutyorTaxorFeeCategoryCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_MessageFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_PackageTypeCode_2006.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_PaymentMeansCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ReferenceTypeCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_QualifiedDataType_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_identifierlist_standard_ISO_ISOTwo-letterCountryCode_SecondEdition2006.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_EDIFICAS-EU_AccountingAccountType_D11A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_EN16931_AllowanceChargeReasonCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_ISO_ISO3AlphaCurrencyCode_2012-08-31.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ActionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ContactFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DeliveryTermsCode_2010.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DeliveryTermsFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DocumentNameCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DocumentStatusCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DutyTaxFeeTypeCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_DutyorTaxorFeeCategoryCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_EventTimeReferenceCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_MessageFunctionCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_PackageTypeCode_2006.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_PaymentMeansCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_ReferenceTypeCode_D20A.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_codelist_standard_UNECE_TimePointFormatCode_D19B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_QualifiedDataType_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_128.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B_urn_un_unece_uncefact_identifierlist_standard_ISO_ISOTwo-letterCountryCode_SecondEdition2006.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_QualifiedDataType_12.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_12.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_15.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.1}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: factur-x
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.1
|
|
4
4
|
Summary: Factur-X and Order-X: electronic invoicing and ordering standards
|
|
5
5
|
Project-URL: Homepage, https://github.com/akretion/factur-x
|
|
6
6
|
Project-URL: Source, https://github.com/akretion/factur-x
|
|
@@ -41,6 +41,7 @@ Requires-Python: >=3.7
|
|
|
41
41
|
Requires-Dist: importlib-resources; python_version < '3.9'
|
|
42
42
|
Requires-Dist: lxml
|
|
43
43
|
Requires-Dist: pypdf>=5.3.0
|
|
44
|
+
Requires-Dist: saxonche
|
|
44
45
|
Description-Content-Type: text/x-rst
|
|
45
46
|
|
|
46
47
|
Factur-X and Order-X Python library
|
|
@@ -164,7 +165,19 @@ Contributors
|
|
|
164
165
|
Changelog
|
|
165
166
|
=========
|
|
166
167
|
|
|
167
|
-
* Version
|
|
168
|
+
* Version 4.1 dated 2026-03-19
|
|
169
|
+
|
|
170
|
+
* Fix compatibility with python < 3.12
|
|
171
|
+
|
|
172
|
+
* Version 4.0 dated 2026-03-18
|
|
173
|
+
|
|
174
|
+
* Add method xml_check_schematron(). It adds a dependency in saxonche.
|
|
175
|
+
* Add named argument check_shematron=True on generate_from_binary(), generate_from_file() and get_xml_from_pdf(). When this new arg is True, it calls the new method xml_check_schematron(). When check_shematron=True, the performance impact is signifiant (it adds about 0.3 seconds on my laptop).
|
|
176
|
+
* Remove deprecated methods check_facturx_xsd(), get_facturx_flavor(), generate_facturx_from_binary() and generate_facturx_from_file(). These methods have been marked as deprecated for 5 years now (since release 2.0 dated April 4th 2021).
|
|
177
|
+
* Update scripts facturx-pdfextractxml and facturx-pdfgen: add option --disable-schematron-check
|
|
178
|
+
* Update script facturx-xmlcheck: add check against schematron after the check against XSD
|
|
179
|
+
|
|
180
|
+
* Version 3.16 dated 2026-03-13
|
|
168
181
|
|
|
169
182
|
* /CheckSum is now a Bytes string object instead of a string object (patch by Adrian Devries)
|
|
170
183
|
|
|
@@ -119,7 +119,19 @@ Contributors
|
|
|
119
119
|
Changelog
|
|
120
120
|
=========
|
|
121
121
|
|
|
122
|
-
* Version
|
|
122
|
+
* Version 4.1 dated 2026-03-19
|
|
123
|
+
|
|
124
|
+
* Fix compatibility with python < 3.12
|
|
125
|
+
|
|
126
|
+
* Version 4.0 dated 2026-03-18
|
|
127
|
+
|
|
128
|
+
* Add method xml_check_schematron(). It adds a dependency in saxonche.
|
|
129
|
+
* Add named argument check_shematron=True on generate_from_binary(), generate_from_file() and get_xml_from_pdf(). When this new arg is True, it calls the new method xml_check_schematron(). When check_shematron=True, the performance impact is signifiant (it adds about 0.3 seconds on my laptop).
|
|
130
|
+
* Remove deprecated methods check_facturx_xsd(), get_facturx_flavor(), generate_facturx_from_binary() and generate_facturx_from_file(). These methods have been marked as deprecated for 5 years now (since release 2.0 dated April 4th 2021).
|
|
131
|
+
* Update scripts facturx-pdfextractxml and facturx-pdfgen: add option --disable-schematron-check
|
|
132
|
+
* Update script facturx-xmlcheck: add check against schematron after the check against XSD
|
|
133
|
+
|
|
134
|
+
* Version 3.16 dated 2026-03-13
|
|
123
135
|
|
|
124
136
|
* /CheckSum is now a Bytes string object instead of a string object (patch by Adrian Devries)
|
|
125
137
|
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "4.1"
|
|
2
2
|
from .facturx import generate_from_file, \
|
|
3
|
-
generate_facturx_from_file, \
|
|
4
3
|
generate_from_binary, \
|
|
5
|
-
generate_facturx_from_binary, \
|
|
6
4
|
get_xml_namespaces, \
|
|
7
|
-
get_facturx_flavor, \
|
|
8
5
|
get_flavor, \
|
|
9
6
|
get_facturx_level, \
|
|
10
7
|
get_level, \
|
|
11
|
-
check_facturx_xsd, \
|
|
12
8
|
xml_check_xsd, \
|
|
9
|
+
xml_check_schematron, \
|
|
13
10
|
get_facturx_xml_from_pdf, \
|
|
14
11
|
get_orderx_xml_from_pdf, \
|
|
15
12
|
get_xml_from_pdf, \
|
|
@@ -34,6 +34,7 @@ from datetime import datetime
|
|
|
34
34
|
from pypdf import PdfWriter, PdfReader
|
|
35
35
|
from pypdf.generic import DictionaryObject, DecodedStreamObject, \
|
|
36
36
|
NameObject, NumberObject, ArrayObject, create_string_object, ByteStringObject
|
|
37
|
+
import saxonche
|
|
37
38
|
import importlib.resources as importlib_resources
|
|
38
39
|
try:
|
|
39
40
|
importlib_resources.files # added in py3.9
|
|
@@ -108,13 +109,6 @@ XML_NAMESPACES = {
|
|
|
108
109
|
CREATOR = f'factur-x Python lib v{VERSION} by Alexis de Lattre'
|
|
109
110
|
|
|
110
111
|
|
|
111
|
-
def check_facturx_xsd(
|
|
112
|
-
facturx_xml, flavor='autodetect', facturx_level='autodetect'):
|
|
113
|
-
logger.warning(
|
|
114
|
-
'check_facturx_xsd() is deprecated. Use xml_check_xsd() instead.')
|
|
115
|
-
return xml_check_xsd(facturx_xml, flavor=flavor, level=facturx_level)
|
|
116
|
-
|
|
117
|
-
|
|
118
112
|
def xml_check_xsd(xml, flavor='autodetect', level='autodetect'):
|
|
119
113
|
"""
|
|
120
114
|
Validate the XML file against the XSD
|
|
@@ -137,6 +131,7 @@ def xml_check_xsd(xml, flavor='autodetect', level='autodetect'):
|
|
|
137
131
|
raise ValueError('Wrong type for flavor argument')
|
|
138
132
|
if not isinstance(level, (type(None), str)):
|
|
139
133
|
raise ValueError('Wrong type for level argument')
|
|
134
|
+
start_chrono = datetime.now()
|
|
140
135
|
xml_etree = None
|
|
141
136
|
if isinstance(xml, bytes):
|
|
142
137
|
xml_bytes = xml
|
|
@@ -202,7 +197,6 @@ def xml_check_xsd(xml, flavor='autodetect', level='autodetect'):
|
|
|
202
197
|
try:
|
|
203
198
|
t = etree.parse(BytesIO(xml_bytes))
|
|
204
199
|
official_schema.assertValid(t)
|
|
205
|
-
logger.info('%s XML file successfully validated against XSD', flavor)
|
|
206
200
|
except Exception as e:
|
|
207
201
|
# if the validation of the XSD fails, we arrive here
|
|
208
202
|
logger.error(
|
|
@@ -213,26 +207,178 @@ def xml_check_xsd(xml, flavor='autodetect', level='autodetect'):
|
|
|
213
207
|
"XML Schema Definition. "
|
|
214
208
|
"Here is the error, which may give you an idea on the "
|
|
215
209
|
"cause of the problem: %s." % (flavor.capitalize(), str(e)))
|
|
210
|
+
end_chrono = datetime.now()
|
|
211
|
+
logger.info(
|
|
212
|
+
'%s XML file successfully validated against XSD in %s sec',
|
|
213
|
+
flavor, (end_chrono - start_chrono).total_seconds())
|
|
216
214
|
return True
|
|
217
215
|
|
|
218
216
|
|
|
219
|
-
def
|
|
217
|
+
def xml_check_schematron(xml, flavor='autodetect', level='autodetect'):
|
|
218
|
+
"""
|
|
219
|
+
Validate the XML file against the schematron
|
|
220
|
+
:param xml: the Factur-X or Order-X XML
|
|
221
|
+
:type xml: string, file or etree object
|
|
222
|
+
:param flavor: possible values: 'factur-x', 'zugferd', 'order-x' or 'autodetect'.
|
|
223
|
+
Value 'zugferd' means ZUGFeRD 1.0.
|
|
224
|
+
:type flavor: string
|
|
225
|
+
:param level: the level of the Factur-X or Order-X XML file. Default value
|
|
226
|
+
is 'autodetect'. The only advantage to specifiy a particular value instead
|
|
227
|
+
of using the autodetection is for a small perf improvement.
|
|
228
|
+
Possible values for Factur-X: minimum, basicwl, basic, en16931, extended.
|
|
229
|
+
Possible values for Order-X: basic, comfort, extended.
|
|
230
|
+
:return: True if the XML is valid against the schematron
|
|
231
|
+
raise an error if it is not valid against the schematron
|
|
232
|
+
"""
|
|
233
|
+
logger.debug(
|
|
234
|
+
'xml_check_schematron with factur-x lib %s', VERSION)
|
|
235
|
+
if not isinstance(flavor, str):
|
|
236
|
+
raise ValueError('Wrong type for flavor argument')
|
|
237
|
+
if not isinstance(level, (type(None), str)):
|
|
238
|
+
raise ValueError('Wrong type for level argument')
|
|
239
|
+
start_chrono = datetime.now()
|
|
240
|
+
xml_etree = None
|
|
241
|
+
if isinstance(xml, bytes):
|
|
242
|
+
xml_bytes = xml
|
|
243
|
+
xml_str = xml_bytes.decode('utf-8')
|
|
244
|
+
elif isinstance(xml, str):
|
|
245
|
+
xml_str = xml
|
|
246
|
+
xml_bytes = xml_str.encode('utf-8')
|
|
247
|
+
elif isinstance(xml, type(etree.Element('pouet'))):
|
|
248
|
+
xml_etree = xml
|
|
249
|
+
xml_bytes = etree.tostring(
|
|
250
|
+
xml, pretty_print=True, encoding='UTF-8',
|
|
251
|
+
xml_declaration=True)
|
|
252
|
+
xml_str = xml_bytes.decode('utf-8')
|
|
253
|
+
elif isinstance(xml, IOBase):
|
|
254
|
+
xml.seek(0)
|
|
255
|
+
xml_bytes = xml.read()
|
|
256
|
+
xml.close()
|
|
257
|
+
xml_str = xml_bytes.decode('utf-8')
|
|
258
|
+
else:
|
|
259
|
+
raise ValueError('Wrong type for xml argument')
|
|
260
|
+
|
|
261
|
+
if not xml_str or not xml_bytes:
|
|
262
|
+
raise ValueError('xml argument is empty')
|
|
263
|
+
|
|
264
|
+
# autodetect
|
|
265
|
+
if flavor not in ('factur-x', 'facturx', 'zugferd', 'order-x', 'orderx'):
|
|
266
|
+
if xml_etree is None:
|
|
267
|
+
try:
|
|
268
|
+
xml_etree = etree.fromstring(xml_bytes)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
raise Exception(
|
|
271
|
+
f"The XML syntax is invalid: {str(e)}.")
|
|
272
|
+
flavor = get_flavor(xml_etree)
|
|
273
|
+
if flavor in ('factur-x', 'facturx'):
|
|
274
|
+
if level not in FACTURX_LEVEL2xsd:
|
|
275
|
+
if xml_etree is None:
|
|
276
|
+
try:
|
|
277
|
+
xml_etree = etree.fromstring(xml_bytes)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
raise Exception(
|
|
280
|
+
f"The XML syntax is invalid: {str(e)}.")
|
|
281
|
+
level = get_level(xml_etree, flavor)
|
|
282
|
+
if level not in FACTURX_LEVEL2xsd:
|
|
283
|
+
raise ValueError(
|
|
284
|
+
f"Wrong level '{level}' for Factur-X invoice.")
|
|
285
|
+
xsd_filename = FACTURX_LEVEL2xsd[level]
|
|
286
|
+
elif flavor in ('order-x', 'orderx'):
|
|
287
|
+
if level not in ORDERX_LEVEL2xsd:
|
|
288
|
+
if xml_etree is None:
|
|
289
|
+
try:
|
|
290
|
+
xml_etree = etree.fromstring(xml_bytes)
|
|
291
|
+
except Exception as e:
|
|
292
|
+
raise Exception(
|
|
293
|
+
f"The XML syntax is invalid: {str(e)}.")
|
|
294
|
+
level = get_level(xml_etree, flavor)
|
|
295
|
+
if level not in ORDERX_LEVEL2xsd:
|
|
296
|
+
raise ValueError(
|
|
297
|
+
f"xsd/{ORDERX_LEVEL2xsd[level][:-4]}-compiled-saxonc.xsl")
|
|
298
|
+
xsd_filename = ORDERX_LEVEL2xsd[level]
|
|
299
|
+
else:
|
|
300
|
+
logger.warning('There is no schematron check for flavor %s', flavor)
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
relative_xsl_file = f"xsd/{xsd_filename[:-4]}-compiled.xsl"
|
|
304
|
+
xsl_file = str(importlib_resources.files(__package__).joinpath(relative_xsl_file))
|
|
305
|
+
logger.debug('Using schematron XSL file %s', xsl_file)
|
|
306
|
+
xml_str_no_bom = xml_str.lstrip('\ufeff')
|
|
307
|
+
errors = []
|
|
308
|
+
with saxonche.PySaxonProcessor() as saxproc:
|
|
309
|
+
xslt_proc = saxproc.new_xslt30_processor()
|
|
310
|
+
xdm_node = saxproc.parse_xml(xml_text=xml_str_no_bom)
|
|
311
|
+
# compile_stylesheet() is the slow/heavy part
|
|
312
|
+
# It can be optimized by generating stylesheet export files using saxon EE
|
|
313
|
+
# stylesheet export files can then be used by saxon HE
|
|
314
|
+
executable = xslt_proc.compile_stylesheet(stylesheet_file=xsl_file)
|
|
315
|
+
result_str = executable.transform_to_string(xdm_node=xdm_node)
|
|
316
|
+
logger.debug('schematron result_str=%s', result_str)
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
svrl_root = etree.fromstring(result_str.encode('utf-8'))
|
|
320
|
+
except Exception as e:
|
|
321
|
+
logger.error(f"Schematron check generated an invalid XML output. Error: {str(e)}")
|
|
322
|
+
logger.info('Unable to validate %s XML file against schematron', flavor)
|
|
323
|
+
return False
|
|
324
|
+
xpath_errors = svrl_root.xpath(
|
|
325
|
+
".//svrl:successful-report | .//svrl:failed-assert", namespaces=svrl_root.nsmap)
|
|
326
|
+
error_nr = 1
|
|
327
|
+
for xpath_error in xpath_errors:
|
|
328
|
+
detail_xpath = xpath_error.xpath("*[local-name() = 'text']", namespaces=svrl_root.nsmap)
|
|
329
|
+
if detail_xpath:
|
|
330
|
+
error_msg = detail_xpath[0].text and detail_xpath[0].text.strip()
|
|
331
|
+
error_msg = f'{error_nr}. {error_msg}'
|
|
332
|
+
location = xpath_error.attrib and xpath_error.attrib.get('location')
|
|
333
|
+
if location:
|
|
334
|
+
error_msg = f'{error_msg}\nError location: {location}'
|
|
335
|
+
errors.append(error_msg)
|
|
336
|
+
error_nr += 1
|
|
337
|
+
|
|
338
|
+
if errors:
|
|
339
|
+
logger.error(
|
|
340
|
+
"The XML file is invalid against the schematron: %d errors found.", len(errors))
|
|
341
|
+
for error_msg in errors:
|
|
342
|
+
logger.error(error_msg)
|
|
343
|
+
error_list_str = '\n'.join(errors)
|
|
344
|
+
full_error = (
|
|
345
|
+
f"The Factur-X XML file is not valid against the official "
|
|
346
|
+
f"schematron. {len(errors)} errors found:\n{error_list_str}")
|
|
347
|
+
raise Exception(full_error)
|
|
348
|
+
end_chrono = datetime.now()
|
|
349
|
+
logger.info(
|
|
350
|
+
'%s XML file successfully validated against schematron in %s sec',
|
|
351
|
+
flavor, (end_chrono - start_chrono).total_seconds())
|
|
352
|
+
return True
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def get_facturx_xml_from_pdf(pdf_file, check_xsd=True, check_schematron=True):
|
|
220
356
|
filenames = [FACTURX_FILENAME] + ZUGFERD_FILENAMES
|
|
221
|
-
return get_xml_from_pdf(
|
|
357
|
+
return get_xml_from_pdf(
|
|
358
|
+
pdf_file,
|
|
359
|
+
check_xsd=check_xsd,
|
|
360
|
+
check_schematron=check_schematron,
|
|
361
|
+
filenames=filenames)
|
|
222
362
|
|
|
223
363
|
|
|
224
|
-
def get_orderx_xml_from_pdf(pdf_file, check_xsd=True):
|
|
364
|
+
def get_orderx_xml_from_pdf(pdf_file, check_xsd=True, check_schematron=True):
|
|
225
365
|
filenames = [ORDERX_FILENAME]
|
|
226
|
-
return get_xml_from_pdf(
|
|
366
|
+
return get_xml_from_pdf(
|
|
367
|
+
pdf_file,
|
|
368
|
+
check_xsd=check_xsd,
|
|
369
|
+
check_schematron=check_schematron,
|
|
370
|
+
filenames=filenames)
|
|
227
371
|
|
|
228
372
|
|
|
229
|
-
def get_xml_from_pdf(pdf_file, check_xsd=True, filenames=[]):
|
|
373
|
+
def get_xml_from_pdf(pdf_file, check_xsd=True, check_schematron=True, filenames=[]):
|
|
230
374
|
logger.debug(
|
|
231
375
|
'get_xml_from_pdf with factur-x lib %s', VERSION)
|
|
232
376
|
if not pdf_file:
|
|
233
377
|
raise ValueError('Missing pdf_invoice argument')
|
|
234
378
|
if not isinstance(check_xsd, bool):
|
|
235
379
|
raise ValueError('Bad type for check_xsd argument')
|
|
380
|
+
if not isinstance(check_schematron, bool):
|
|
381
|
+
raise ValueError('Bad type for check_schematron argument')
|
|
236
382
|
if not isinstance(filenames, list):
|
|
237
383
|
raise ValueError('Bad type for filenames argument')
|
|
238
384
|
if isinstance(pdf_file, (str, bytes)):
|
|
@@ -278,11 +424,30 @@ def get_xml_from_pdf(pdf_file, check_xsd=True, filenames=[]):
|
|
|
278
424
|
"Filename is %s but detected flavor is %s. "
|
|
279
425
|
"This is very weird: skipping file.", filename, flavor)
|
|
280
426
|
continue
|
|
281
|
-
|
|
427
|
+
level = False
|
|
428
|
+
if check_xsd or check_schematron:
|
|
282
429
|
try:
|
|
283
|
-
|
|
430
|
+
level = get_level(xml_root, flavor)
|
|
284
431
|
except Exception:
|
|
285
|
-
|
|
432
|
+
logger.warning(
|
|
433
|
+
'Skipping file %s because the level could not be identified',
|
|
434
|
+
filename)
|
|
435
|
+
continue
|
|
436
|
+
if check_xsd and level:
|
|
437
|
+
try:
|
|
438
|
+
xml_check_xsd(xml_root, flavor=flavor, level=level)
|
|
439
|
+
except Exception:
|
|
440
|
+
logger.warning(
|
|
441
|
+
'Skipping file %s because it is not valid against the XSD',
|
|
442
|
+
filename)
|
|
443
|
+
continue
|
|
444
|
+
if check_schematron and level:
|
|
445
|
+
try:
|
|
446
|
+
xml_check_schematron(xml_root, flavor=flavor, level=level)
|
|
447
|
+
except Exception:
|
|
448
|
+
logger.warning(
|
|
449
|
+
'Skipping file %s because it is not valid against the schematron',
|
|
450
|
+
filename)
|
|
286
451
|
continue
|
|
287
452
|
xml_bytes = attach_obj.content
|
|
288
453
|
xml_filename = filename
|
|
@@ -789,12 +954,6 @@ def get_level(xml_etree, flavor='autodetect'):
|
|
|
789
954
|
return level
|
|
790
955
|
|
|
791
956
|
|
|
792
|
-
def get_facturx_flavor(facturx_xml_etree):
|
|
793
|
-
logger.warning(
|
|
794
|
-
'get_facturx_flavor() is deprecated. Use get_flavor() instead.')
|
|
795
|
-
return get_flavor(facturx_xml_etree)
|
|
796
|
-
|
|
797
|
-
|
|
798
957
|
def get_flavor(xml_etree):
|
|
799
958
|
if not isinstance(xml_etree, type(etree.Element('pouet'))):
|
|
800
959
|
raise ValueError('xml_etree must be an etree.Element() object')
|
|
@@ -829,23 +988,11 @@ def get_orderx_type(xml_etree):
|
|
|
829
988
|
return ORDERX_code2type[code]
|
|
830
989
|
|
|
831
990
|
|
|
832
|
-
def generate_facturx_from_binary(
|
|
833
|
-
pdf_file, xml, facturx_level='autodetect',
|
|
834
|
-
check_xsd=True, pdf_metadata=None, lang=None, attachments=None):
|
|
835
|
-
logger.warning(
|
|
836
|
-
'generate_facturx_from_binary() is deprecated. '
|
|
837
|
-
'Use generate_from_binary() instead.')
|
|
838
|
-
return generate_from_binary(
|
|
839
|
-
pdf_file, xml, flavor='factur-x', level=facturx_level,
|
|
840
|
-
check_xsd=check_xsd, pdf_metadata=pdf_metadata, lang=lang,
|
|
841
|
-
attachments=attachments)
|
|
842
|
-
|
|
843
|
-
|
|
844
991
|
def generate_from_binary(
|
|
845
992
|
pdf_file, xml, flavor='autodetect', level='autodetect',
|
|
846
993
|
orderx_type='autodetect',
|
|
847
|
-
check_xsd=True,
|
|
848
|
-
afrelationship='data', xmp_compression=True):
|
|
994
|
+
check_xsd=True, check_schematron=True, pdf_metadata=None, lang=None,
|
|
995
|
+
attachments=None, afrelationship='data', xmp_compression=True):
|
|
849
996
|
"""
|
|
850
997
|
Generate a Factur-X or Order-X PDF from a regular PDF and a factur-X
|
|
851
998
|
or Order-X XML file. The method uses a binary as input (the regular PDF)
|
|
@@ -873,6 +1020,11 @@ def generate_from_binary(
|
|
|
873
1020
|
beforehand, you should disable this feature to avoid a double check
|
|
874
1021
|
and get a small performance improvement.
|
|
875
1022
|
:type check_xsd: boolean
|
|
1023
|
+
:param check_schematron: if enable, checks the Factur-X XML file against
|
|
1024
|
+
the schematron. If this step has already been performed
|
|
1025
|
+
beforehand, you should disable this feature to avoid a double check
|
|
1026
|
+
and get a small performance improvement.
|
|
1027
|
+
:type check_schematron: boolean
|
|
876
1028
|
:param pdf_metadata: Specify the metadata of the generated PDF.
|
|
877
1029
|
If pdf_metadata is None (default value), this lib will generate some
|
|
878
1030
|
metadata in English by extracting relevant info from the Factur-X/Order-X XML.
|
|
@@ -923,7 +1075,8 @@ def generate_from_binary(
|
|
|
923
1075
|
f.write(pdf_file)
|
|
924
1076
|
generate_from_file(
|
|
925
1077
|
f, xml, flavor=flavor, level=level, orderx_type=orderx_type,
|
|
926
|
-
check_xsd=check_xsd,
|
|
1078
|
+
check_xsd=check_xsd, check_schematron=check_schematron,
|
|
1079
|
+
pdf_metadata=pdf_metadata, lang=lang,
|
|
927
1080
|
attachments=attachments, afrelationship=afrelationship,
|
|
928
1081
|
xmp_compression=xmp_compression)
|
|
929
1082
|
f.seek(0)
|
|
@@ -932,28 +1085,11 @@ def generate_from_binary(
|
|
|
932
1085
|
return result_pdf
|
|
933
1086
|
|
|
934
1087
|
|
|
935
|
-
def generate_facturx_from_file(
|
|
936
|
-
pdf_file, facturx_xml, facturx_level='autodetect',
|
|
937
|
-
check_xsd=True, pdf_metadata=None, output_pdf_file=None,
|
|
938
|
-
additional_attachments=None, attachments=None, lang=None):
|
|
939
|
-
logger.warning(
|
|
940
|
-
'generate_facturx_from_file() is deprecated. '
|
|
941
|
-
'Use generate_from_file() instead.')
|
|
942
|
-
if additional_attachments:
|
|
943
|
-
logger.warning(
|
|
944
|
-
"The argument additional_attachments is not supported "
|
|
945
|
-
"any more. Use the attachments arg instead.")
|
|
946
|
-
return generate_from_file(
|
|
947
|
-
pdf_file, facturx_xml, flavor='factur-x', level=facturx_level,
|
|
948
|
-
check_xsd=check_xsd, pdf_metadata=pdf_metadata,
|
|
949
|
-
output_pdf_file=output_pdf_file,
|
|
950
|
-
attachments=attachments, lang=lang)
|
|
951
|
-
|
|
952
|
-
|
|
953
1088
|
def generate_from_file(
|
|
954
1089
|
pdf_file, xml, flavor='autodetect', level='autodetect',
|
|
955
1090
|
orderx_type='autodetect',
|
|
956
|
-
check_xsd=True,
|
|
1091
|
+
check_xsd=True, check_schematron=True, pdf_metadata=None, lang=None,
|
|
1092
|
+
output_pdf_file=None,
|
|
957
1093
|
attachments=None, afrelationship='data', xmp_compression=True):
|
|
958
1094
|
"""
|
|
959
1095
|
Generate a Factur-X or Order-X PDF file from a regular PDF and a Factur-X
|
|
@@ -983,6 +1119,11 @@ def generate_from_file(
|
|
|
983
1119
|
beforehand, you should disable this feature to avoid a double check
|
|
984
1120
|
and get a small performance improvement.
|
|
985
1121
|
:type check_xsd: boolean
|
|
1122
|
+
:param check_schematron: if enable, checks the Factur-X XML file against
|
|
1123
|
+
the schematron. If this step has already been performed
|
|
1124
|
+
beforehand, you should disable this feature to avoid a double check
|
|
1125
|
+
and get a small performance improvement.
|
|
1126
|
+
:type check_schematron: boolean
|
|
986
1127
|
:param pdf_metadata: Specify the metadata of the generated PDF.
|
|
987
1128
|
If pdf_metadata is None (default value), this lib will generate some
|
|
988
1129
|
metadata in English by extracting relevant info from the Factur-X/Order-X XML.
|
|
@@ -1036,6 +1177,7 @@ def generate_from_file(
|
|
|
1036
1177
|
logger.debug('optional arg level=%s', level)
|
|
1037
1178
|
logger.debug('optional arg orderx_type=%s', orderx_type)
|
|
1038
1179
|
logger.debug('optional arg check_xsd=%s', check_xsd)
|
|
1180
|
+
logger.debug(f"optional arg check_schematron={check_schematron}")
|
|
1039
1181
|
logger.debug('optional arg pdf_metadata=%s', pdf_metadata)
|
|
1040
1182
|
logger.debug('optional arg lang=%s', lang)
|
|
1041
1183
|
logger.debug('optional arg output_pdf_file=%s', output_pdf_file)
|
|
@@ -1056,6 +1198,9 @@ def generate_from_file(
|
|
|
1056
1198
|
if not isinstance(check_xsd, bool):
|
|
1057
1199
|
raise ValueError(
|
|
1058
1200
|
'check_xsd argument is a %s, must be a boolean' % type(check_xsd))
|
|
1201
|
+
if not isinstance(check_schematron, bool):
|
|
1202
|
+
raise ValueError(
|
|
1203
|
+
"check_schematron argument is a {type(check_schematron)}, must be a boolean")
|
|
1059
1204
|
if not isinstance(pdf_metadata, (dict, type(None))):
|
|
1060
1205
|
raise ValueError(
|
|
1061
1206
|
'pdf_metadata argument is a %s, must be a dict or None'
|
|
@@ -1186,6 +1331,9 @@ def generate_from_file(
|
|
|
1186
1331
|
if check_xsd:
|
|
1187
1332
|
xml_check_xsd(
|
|
1188
1333
|
xml_bytes, flavor=flavor, level=level)
|
|
1334
|
+
if flavor in ('factur-x', 'order-x') and check_schematron:
|
|
1335
|
+
xml_check_schematron(
|
|
1336
|
+
xml_bytes, flavor=flavor, level=level)
|
|
1189
1337
|
if pdf_metadata is None:
|
|
1190
1338
|
if xml_root is None:
|
|
1191
1339
|
xml_root = etree.fromstring(xml_bytes)
|
|
@@ -1220,6 +1368,6 @@ def generate_from_file(
|
|
|
1220
1368
|
pdf_writer.write(pdf_file)
|
|
1221
1369
|
end_chrono = datetime.now()
|
|
1222
1370
|
logger.info(
|
|
1223
|
-
'%s PDF generated in %s
|
|
1371
|
+
'%s PDF generated in %s sec',
|
|
1224
1372
|
flavor, (end_chrono - start_chrono).total_seconds())
|
|
1225
1373
|
return True
|
|
@@ -9,8 +9,8 @@ import logging
|
|
|
9
9
|
from os.path import isfile, isdir
|
|
10
10
|
|
|
11
11
|
__author__ = "Alexis de Lattre <alexis.delattre@akretion.com>"
|
|
12
|
-
__date__ = "
|
|
13
|
-
__version__ = "0.
|
|
12
|
+
__date__ = "March 2026"
|
|
13
|
+
__version__ = "0.4"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def pdfextractxml(args):
|
|
@@ -42,13 +42,12 @@ def pdfextractxml(args):
|
|
|
42
42
|
'output XML filename)', out_xml_filename)
|
|
43
43
|
sys.exit(1)
|
|
44
44
|
pdf_file = open(pdf_filename, 'rb')
|
|
45
|
-
check_xsd =
|
|
46
|
-
|
|
47
|
-
check_xsd = False
|
|
45
|
+
check_xsd = not args.disable_xsd_check
|
|
46
|
+
check_schematron = not args.disable_schematron_check
|
|
48
47
|
# The important line of code is below !
|
|
49
48
|
try:
|
|
50
49
|
(xml_filename, xml_string) = get_xml_from_pdf(
|
|
51
|
-
pdf_file, check_xsd=check_xsd)
|
|
50
|
+
pdf_file, check_xsd=check_xsd, check_schematron=check_schematron)
|
|
52
51
|
except Exception as e:
|
|
53
52
|
logger.error(e)
|
|
54
53
|
sys.exit(1)
|
|
@@ -82,6 +81,11 @@ def main(args=None):
|
|
|
82
81
|
action='store_true',
|
|
83
82
|
help="De-activate XML Schema Definition check on Factur-X/Order-X XML file "
|
|
84
83
|
"(the check is enabled by default)")
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
'-ds', '--disable-schematron-check', dest='disable_schematron_check',
|
|
86
|
+
action='store_true',
|
|
87
|
+
help="De-activate Schematron check on Factur-X/Order-X XML file "
|
|
88
|
+
"(the check is enabled by default)")
|
|
85
89
|
parser.add_argument(
|
|
86
90
|
"facturx_orderx_file", help="PDF Factur-X or Order-X file")
|
|
87
91
|
parser.add_argument(
|
|
@@ -43,9 +43,8 @@ def pdfgen(args):
|
|
|
43
43
|
'3rd argument %s is a directory name (should be a the '
|
|
44
44
|
'Factur-X or Order-X PDF filename)', output_pdf_filename)
|
|
45
45
|
sys.exit(1)
|
|
46
|
-
check_xsd =
|
|
47
|
-
|
|
48
|
-
check_xsd = False
|
|
46
|
+
check_xsd = not args.disable_xsd_check
|
|
47
|
+
check_schematron = not args.disable_schematron_check
|
|
49
48
|
pdf_metadata = None
|
|
50
49
|
if (
|
|
51
50
|
args.meta_author or
|
|
@@ -76,13 +75,15 @@ def pdfgen(args):
|
|
|
76
75
|
try:
|
|
77
76
|
# The important line of code is below !
|
|
78
77
|
generate_from_file(
|
|
79
|
-
pdf_filename, args.xml_file,
|
|
78
|
+
pdf_filename, args.xml_file,
|
|
79
|
+
check_xsd=check_xsd, check_schematron=check_schematron,
|
|
80
80
|
flavor=args.flavor, level=args.level, orderx_type=args.orderx_type,
|
|
81
81
|
pdf_metadata=pdf_metadata, lang=lang, output_pdf_file=output_pdf_filename,
|
|
82
82
|
attachments=attachments, afrelationship=args.afrelationship,
|
|
83
83
|
xmp_compression=xmp_compression)
|
|
84
|
-
except Exception
|
|
85
|
-
|
|
84
|
+
except Exception:
|
|
85
|
+
# no need to re-print the error log, it is already present in the logs
|
|
86
|
+
logger.error('factur-x lib call failed, exiting.')
|
|
86
87
|
sys.exit(1)
|
|
87
88
|
|
|
88
89
|
|
|
@@ -112,6 +113,11 @@ def main(args=None):
|
|
|
112
113
|
action='store_true',
|
|
113
114
|
help="De-activate XML Schema Definition check on XML file "
|
|
114
115
|
"(the check is enabled by default)")
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
'-ds', '--disable-schematron-check', dest='disable_schematron_check',
|
|
118
|
+
action='store_true',
|
|
119
|
+
help="De-activate Schematron check on XML file "
|
|
120
|
+
"(the check is enabled by default)")
|
|
115
121
|
parser.add_argument(
|
|
116
122
|
'-f', '--flavor', dest='flavor', default='autodetect',
|
|
117
123
|
help="Specify if you want to generate a Factur-X or Order-X PDF file. "
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
6
|
-
from facturx import xml_check_xsd, __version__ as fxversion
|
|
6
|
+
from facturx import xml_check_xsd, xml_check_schematron, __version__ as fxversion
|
|
7
7
|
from facturx.facturx import logger
|
|
8
8
|
import logging
|
|
9
9
|
from os.path import isfile
|
|
10
10
|
|
|
11
11
|
__author__ = "Alexis de Lattre <alexis.delattre@akretion.com>"
|
|
12
|
-
__date__ = "
|
|
13
|
-
__version__ = "0.
|
|
12
|
+
__date__ = "March 2026"
|
|
13
|
+
__version__ = "0.5"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def xmlcheck(args):
|
|
@@ -34,11 +34,16 @@ def xmlcheck(args):
|
|
|
34
34
|
if not isfile(args.xml_file):
|
|
35
35
|
logger.error('%s is not a filename', args.xml_file)
|
|
36
36
|
sys.exit(1)
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
with open(args.xml_file, 'rb') as xml_file:
|
|
38
|
+
xml_bytes = xml_file.read()
|
|
39
39
|
try:
|
|
40
40
|
xml_check_xsd(
|
|
41
|
-
|
|
41
|
+
xml_bytes, flavor=args.flavor, level=args.level)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(e)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
try:
|
|
46
|
+
xml_check_schematron(xml_bytes, flavor=args.flavor, level=args.level)
|
|
42
47
|
except Exception as e:
|
|
43
48
|
logger.error(e)
|
|
44
49
|
sys.exit(1)
|