factur-x 3.16.dev0__tar.gz → 4.0__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.0}/PKG-INFO +11 -2
- {factur_x-3.16.dev0 → factur_x-4.0}/README.rst +9 -1
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/__init__.py +2 -5
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/facturx.py +203 -57
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/scripts/pdfextractxml.py +10 -6
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/scripts/pdfgen.py +12 -6
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/scripts/xmlcheck.py +11 -6
- factur_x-4.0/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC-compiled.xsl +9175 -0
- factur_x-4.0/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC_codedb.xml +4866 -0
- factur_x-4.0/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL-compiled.xsl +6581 -0
- factur_x-4.0/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL_codedb.xml +2260 -0
- factur_x-4.0/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931-compiled.xsl +11895 -0
- factur_x-4.0/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931_codedb.xml +5897 -0
- factur_x-4.0/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED-compiled.xsl +26245 -0
- factur_x-4.0/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED_codedb.xml +7113 -0
- factur_x-4.0/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM-compiled.xsl +1939 -0
- factur_x-4.0/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM_codedb.xml +1111 -0
- factur_x-4.0/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +2931 -0
- factur_x-4.0/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +4364 -0
- factur_x-4.0/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +6855 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/requirements.txt +1 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/.flake8 +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/.gitignore +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/LICENSE +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/scripts/__init__.py +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/scripts/webservice.py +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/xmp/Factur-X_extension_schema.xmp +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/xmp/ZUGFeRD_extension_schema.xmp +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/facturx/xsd/zugferd/ZUGFeRD1p0.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_QualifiedDataType_12.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_12.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_15.xsd +0 -0
- {factur_x-3.16.dev0 → factur_x-4.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: factur-x
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0
|
|
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,15 @@ Contributors
|
|
|
164
165
|
Changelog
|
|
165
166
|
=========
|
|
166
167
|
|
|
167
|
-
* Version
|
|
168
|
+
* Version 4.0 dated 2026-03-18
|
|
169
|
+
|
|
170
|
+
* Add method xml_check_schematron(). It adds a dependency in saxonche.
|
|
171
|
+
* 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).
|
|
172
|
+
* 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).
|
|
173
|
+
* Update scripts facturx-pdfextractxml and facturx-pdfgen: add option --disable-schematron-check
|
|
174
|
+
* Update script facturx-xmlcheck: add check against schematron after the check against XSD
|
|
175
|
+
|
|
176
|
+
* Version 3.16 dated 2026-03-13
|
|
168
177
|
|
|
169
178
|
* /CheckSum is now a Bytes string object instead of a string object (patch by Adrian Devries)
|
|
170
179
|
|
|
@@ -119,7 +119,15 @@ Contributors
|
|
|
119
119
|
Changelog
|
|
120
120
|
=========
|
|
121
121
|
|
|
122
|
-
* Version
|
|
122
|
+
* Version 4.0 dated 2026-03-18
|
|
123
|
+
|
|
124
|
+
* Add method xml_check_schematron(). It adds a dependency in saxonche.
|
|
125
|
+
* 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).
|
|
126
|
+
* 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).
|
|
127
|
+
* Update scripts facturx-pdfextractxml and facturx-pdfgen: add option --disable-schematron-check
|
|
128
|
+
* Update script facturx-xmlcheck: add check against schematron after the check against XSD
|
|
129
|
+
|
|
130
|
+
* Version 3.16 dated 2026-03-13
|
|
123
131
|
|
|
124
132
|
* /CheckSum is now a Bytes string object instead of a string object (patch by Adrian Devries)
|
|
125
133
|
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "4.0"
|
|
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,176 @@ 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
|
+
full_error = "The Factur-X XML file is not valid against the official "\
|
|
344
|
+
f"schematron. {len(errors)} errors found:\n{'\n'.join(errors)}"
|
|
345
|
+
raise Exception(full_error)
|
|
346
|
+
end_chrono = datetime.now()
|
|
347
|
+
logger.info(
|
|
348
|
+
'%s XML file successfully validated against schematron in %s sec',
|
|
349
|
+
flavor, (end_chrono - start_chrono).total_seconds())
|
|
350
|
+
return True
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def get_facturx_xml_from_pdf(pdf_file, check_xsd=True, check_schematron=True):
|
|
220
354
|
filenames = [FACTURX_FILENAME] + ZUGFERD_FILENAMES
|
|
221
|
-
return get_xml_from_pdf(
|
|
355
|
+
return get_xml_from_pdf(
|
|
356
|
+
pdf_file,
|
|
357
|
+
check_xsd=check_xsd,
|
|
358
|
+
check_schematron=check_schematron,
|
|
359
|
+
filenames=filenames)
|
|
222
360
|
|
|
223
361
|
|
|
224
|
-
def get_orderx_xml_from_pdf(pdf_file, check_xsd=True):
|
|
362
|
+
def get_orderx_xml_from_pdf(pdf_file, check_xsd=True, check_schematron=True):
|
|
225
363
|
filenames = [ORDERX_FILENAME]
|
|
226
|
-
return get_xml_from_pdf(
|
|
364
|
+
return get_xml_from_pdf(
|
|
365
|
+
pdf_file,
|
|
366
|
+
check_xsd=check_xsd,
|
|
367
|
+
check_schematron=check_schematron,
|
|
368
|
+
filenames=filenames)
|
|
227
369
|
|
|
228
370
|
|
|
229
|
-
def get_xml_from_pdf(pdf_file, check_xsd=True, filenames=[]):
|
|
371
|
+
def get_xml_from_pdf(pdf_file, check_xsd=True, check_schematron=True, filenames=[]):
|
|
230
372
|
logger.debug(
|
|
231
373
|
'get_xml_from_pdf with factur-x lib %s', VERSION)
|
|
232
374
|
if not pdf_file:
|
|
233
375
|
raise ValueError('Missing pdf_invoice argument')
|
|
234
376
|
if not isinstance(check_xsd, bool):
|
|
235
377
|
raise ValueError('Bad type for check_xsd argument')
|
|
378
|
+
if not isinstance(check_schematron, bool):
|
|
379
|
+
raise ValueError('Bad type for check_schematron argument')
|
|
236
380
|
if not isinstance(filenames, list):
|
|
237
381
|
raise ValueError('Bad type for filenames argument')
|
|
238
382
|
if isinstance(pdf_file, (str, bytes)):
|
|
@@ -278,11 +422,30 @@ def get_xml_from_pdf(pdf_file, check_xsd=True, filenames=[]):
|
|
|
278
422
|
"Filename is %s but detected flavor is %s. "
|
|
279
423
|
"This is very weird: skipping file.", filename, flavor)
|
|
280
424
|
continue
|
|
281
|
-
|
|
425
|
+
level = False
|
|
426
|
+
if check_xsd or check_schematron:
|
|
282
427
|
try:
|
|
283
|
-
|
|
428
|
+
level = get_level(xml_root, flavor)
|
|
284
429
|
except Exception:
|
|
285
|
-
|
|
430
|
+
logger.warning(
|
|
431
|
+
'Skipping file %s because the level could not be identified',
|
|
432
|
+
filename)
|
|
433
|
+
continue
|
|
434
|
+
if check_xsd and level:
|
|
435
|
+
try:
|
|
436
|
+
xml_check_xsd(xml_root, flavor=flavor, level=level)
|
|
437
|
+
except Exception:
|
|
438
|
+
logger.warning(
|
|
439
|
+
'Skipping file %s because it is not valid against the XSD',
|
|
440
|
+
filename)
|
|
441
|
+
continue
|
|
442
|
+
if check_schematron and level:
|
|
443
|
+
try:
|
|
444
|
+
xml_check_schematron(xml_root, flavor=flavor, level=level)
|
|
445
|
+
except Exception:
|
|
446
|
+
logger.warning(
|
|
447
|
+
'Skipping file %s because it is not valid against the schematron',
|
|
448
|
+
filename)
|
|
286
449
|
continue
|
|
287
450
|
xml_bytes = attach_obj.content
|
|
288
451
|
xml_filename = filename
|
|
@@ -789,12 +952,6 @@ def get_level(xml_etree, flavor='autodetect'):
|
|
|
789
952
|
return level
|
|
790
953
|
|
|
791
954
|
|
|
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
955
|
def get_flavor(xml_etree):
|
|
799
956
|
if not isinstance(xml_etree, type(etree.Element('pouet'))):
|
|
800
957
|
raise ValueError('xml_etree must be an etree.Element() object')
|
|
@@ -829,23 +986,11 @@ def get_orderx_type(xml_etree):
|
|
|
829
986
|
return ORDERX_code2type[code]
|
|
830
987
|
|
|
831
988
|
|
|
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
989
|
def generate_from_binary(
|
|
845
990
|
pdf_file, xml, flavor='autodetect', level='autodetect',
|
|
846
991
|
orderx_type='autodetect',
|
|
847
|
-
check_xsd=True,
|
|
848
|
-
afrelationship='data', xmp_compression=True):
|
|
992
|
+
check_xsd=True, check_schematron=True, pdf_metadata=None, lang=None,
|
|
993
|
+
attachments=None, afrelationship='data', xmp_compression=True):
|
|
849
994
|
"""
|
|
850
995
|
Generate a Factur-X or Order-X PDF from a regular PDF and a factur-X
|
|
851
996
|
or Order-X XML file. The method uses a binary as input (the regular PDF)
|
|
@@ -873,6 +1018,11 @@ def generate_from_binary(
|
|
|
873
1018
|
beforehand, you should disable this feature to avoid a double check
|
|
874
1019
|
and get a small performance improvement.
|
|
875
1020
|
:type check_xsd: boolean
|
|
1021
|
+
:param check_schematron: if enable, checks the Factur-X XML file against
|
|
1022
|
+
the schematron. If this step has already been performed
|
|
1023
|
+
beforehand, you should disable this feature to avoid a double check
|
|
1024
|
+
and get a small performance improvement.
|
|
1025
|
+
:type check_schematron: boolean
|
|
876
1026
|
:param pdf_metadata: Specify the metadata of the generated PDF.
|
|
877
1027
|
If pdf_metadata is None (default value), this lib will generate some
|
|
878
1028
|
metadata in English by extracting relevant info from the Factur-X/Order-X XML.
|
|
@@ -923,7 +1073,8 @@ def generate_from_binary(
|
|
|
923
1073
|
f.write(pdf_file)
|
|
924
1074
|
generate_from_file(
|
|
925
1075
|
f, xml, flavor=flavor, level=level, orderx_type=orderx_type,
|
|
926
|
-
check_xsd=check_xsd,
|
|
1076
|
+
check_xsd=check_xsd, check_schematron=check_schematron,
|
|
1077
|
+
pdf_metadata=pdf_metadata, lang=lang,
|
|
927
1078
|
attachments=attachments, afrelationship=afrelationship,
|
|
928
1079
|
xmp_compression=xmp_compression)
|
|
929
1080
|
f.seek(0)
|
|
@@ -932,28 +1083,11 @@ def generate_from_binary(
|
|
|
932
1083
|
return result_pdf
|
|
933
1084
|
|
|
934
1085
|
|
|
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
1086
|
def generate_from_file(
|
|
954
1087
|
pdf_file, xml, flavor='autodetect', level='autodetect',
|
|
955
1088
|
orderx_type='autodetect',
|
|
956
|
-
check_xsd=True,
|
|
1089
|
+
check_xsd=True, check_schematron=True, pdf_metadata=None, lang=None,
|
|
1090
|
+
output_pdf_file=None,
|
|
957
1091
|
attachments=None, afrelationship='data', xmp_compression=True):
|
|
958
1092
|
"""
|
|
959
1093
|
Generate a Factur-X or Order-X PDF file from a regular PDF and a Factur-X
|
|
@@ -983,6 +1117,11 @@ def generate_from_file(
|
|
|
983
1117
|
beforehand, you should disable this feature to avoid a double check
|
|
984
1118
|
and get a small performance improvement.
|
|
985
1119
|
:type check_xsd: boolean
|
|
1120
|
+
:param check_schematron: if enable, checks the Factur-X XML file against
|
|
1121
|
+
the schematron. If this step has already been performed
|
|
1122
|
+
beforehand, you should disable this feature to avoid a double check
|
|
1123
|
+
and get a small performance improvement.
|
|
1124
|
+
:type check_schematron: boolean
|
|
986
1125
|
:param pdf_metadata: Specify the metadata of the generated PDF.
|
|
987
1126
|
If pdf_metadata is None (default value), this lib will generate some
|
|
988
1127
|
metadata in English by extracting relevant info from the Factur-X/Order-X XML.
|
|
@@ -1036,6 +1175,7 @@ def generate_from_file(
|
|
|
1036
1175
|
logger.debug('optional arg level=%s', level)
|
|
1037
1176
|
logger.debug('optional arg orderx_type=%s', orderx_type)
|
|
1038
1177
|
logger.debug('optional arg check_xsd=%s', check_xsd)
|
|
1178
|
+
logger.debug(f"optional arg check_schematron={check_schematron}")
|
|
1039
1179
|
logger.debug('optional arg pdf_metadata=%s', pdf_metadata)
|
|
1040
1180
|
logger.debug('optional arg lang=%s', lang)
|
|
1041
1181
|
logger.debug('optional arg output_pdf_file=%s', output_pdf_file)
|
|
@@ -1056,6 +1196,9 @@ def generate_from_file(
|
|
|
1056
1196
|
if not isinstance(check_xsd, bool):
|
|
1057
1197
|
raise ValueError(
|
|
1058
1198
|
'check_xsd argument is a %s, must be a boolean' % type(check_xsd))
|
|
1199
|
+
if not isinstance(check_schematron, bool):
|
|
1200
|
+
raise ValueError(
|
|
1201
|
+
"check_schematron argument is a {type(check_schematron)}, must be a boolean")
|
|
1059
1202
|
if not isinstance(pdf_metadata, (dict, type(None))):
|
|
1060
1203
|
raise ValueError(
|
|
1061
1204
|
'pdf_metadata argument is a %s, must be a dict or None'
|
|
@@ -1186,6 +1329,9 @@ def generate_from_file(
|
|
|
1186
1329
|
if check_xsd:
|
|
1187
1330
|
xml_check_xsd(
|
|
1188
1331
|
xml_bytes, flavor=flavor, level=level)
|
|
1332
|
+
if flavor in ('factur-x', 'order-x') and check_schematron:
|
|
1333
|
+
xml_check_schematron(
|
|
1334
|
+
xml_bytes, flavor=flavor, level=level)
|
|
1189
1335
|
if pdf_metadata is None:
|
|
1190
1336
|
if xml_root is None:
|
|
1191
1337
|
xml_root = etree.fromstring(xml_bytes)
|
|
@@ -1220,6 +1366,6 @@ def generate_from_file(
|
|
|
1220
1366
|
pdf_writer.write(pdf_file)
|
|
1221
1367
|
end_chrono = datetime.now()
|
|
1222
1368
|
logger.info(
|
|
1223
|
-
'%s PDF generated in %s
|
|
1369
|
+
'%s PDF generated in %s sec',
|
|
1224
1370
|
flavor, (end_chrono - start_chrono).total_seconds())
|
|
1225
1371
|
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)
|