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.
Files changed (107) hide show
  1. {factur_x-3.16.dev0 → factur_x-4.1}/PKG-INFO +15 -2
  2. {factur_x-3.16.dev0 → factur_x-4.1}/README.rst +13 -1
  3. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/__init__.py +2 -5
  4. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/facturx.py +205 -57
  5. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/pdfextractxml.py +10 -6
  6. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/pdfgen.py +12 -6
  7. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/xmlcheck.py +11 -6
  8. factur_x-4.1/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC-compiled.xsl +9175 -0
  9. factur_x-4.1/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC_codedb.xml +4866 -0
  10. factur_x-4.1/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL-compiled.xsl +6581 -0
  11. factur_x-4.1/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL_codedb.xml +2260 -0
  12. factur_x-4.1/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931-compiled.xsl +11895 -0
  13. factur_x-4.1/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931_codedb.xml +5897 -0
  14. factur_x-4.1/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED-compiled.xsl +26245 -0
  15. factur_x-4.1/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED_codedb.xml +7113 -0
  16. factur_x-4.1/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM-compiled.xsl +1939 -0
  17. factur_x-4.1/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM_codedb.xml +1111 -0
  18. factur_x-4.1/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +2931 -0
  19. factur_x-4.1/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +4364 -0
  20. factur_x-4.1/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B-compiled.xsl +6855 -0
  21. {factur_x-3.16.dev0 → factur_x-4.1}/requirements.txt +1 -0
  22. {factur_x-3.16.dev0 → factur_x-4.1}/.flake8 +0 -0
  23. {factur_x-3.16.dev0 → factur_x-4.1}/.gitignore +0 -0
  24. {factur_x-3.16.dev0 → factur_x-4.1}/LICENSE +0 -0
  25. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/__init__.py +0 -0
  26. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/scripts/webservice.py +0 -0
  27. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xmp/Factur-X_extension_schema.xmp +0 -0
  28. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xmp/ZUGFeRD_extension_schema.xmp +0 -0
  29. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basic/Factur-X_1.08_BASIC.xsd +0 -0
  30. {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
  31. {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
  32. {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
  33. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-basicwl/Factur-X_1.08_BASICWL.xsd +0 -0
  34. {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
  35. {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
  36. {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
  37. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-en16931/Factur-X_1.08_EN16931.xsd +0 -0
  38. {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
  39. {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
  40. {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
  41. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-extended/Factur-X_1.08_EXTENDED.xsd +0 -0
  42. {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
  43. {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
  44. {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
  45. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/facturx-minimum/Factur-X_1.08_MINIMUM.xsd +0 -0
  46. {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
  47. {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
  48. {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
  49. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-basic/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
  50. {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
  51. {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
  52. {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
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {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
  61. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/orderx-extended/SCRDMCCBDACIOMessageStructure_100pD20B.xsd +0 -0
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {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
  91. {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
  92. {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
  93. {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
  94. {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
  95. {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
  96. {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
  97. {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
  98. {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
  99. {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
  100. {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
  101. {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
  102. {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
  103. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0.xsd +0 -0
  104. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_QualifiedDataType_12.xsd +0 -0
  105. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_12.xsd +0 -0
  106. {factur_x-3.16.dev0 → factur_x-4.1}/facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_15.xsd +0 -0
  107. {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.16.dev0
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 3.16 dated 2026-03-15
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 3.16 dated 2026-03-15
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__ = "3.16-dev"
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 get_facturx_xml_from_pdf(pdf_file, check_xsd=True):
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(pdf_file, check_xsd=check_xsd, filenames=filenames)
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(pdf_file, check_xsd=check_xsd, filenames=filenames)
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
- if check_xsd:
427
+ level = False
428
+ if check_xsd or check_schematron:
282
429
  try:
283
- xml_check_xsd(xml_root, flavor=flavor)
430
+ level = get_level(xml_root, flavor)
284
431
  except Exception:
285
- # Logs are already present in xml_check_xsd()
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, pdf_metadata=None, lang=None, attachments=None,
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, pdf_metadata=pdf_metadata, lang=lang,
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, pdf_metadata=None, lang=None, output_pdf_file=None,
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 seconds',
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__ = "July 2025"
13
- __version__ = "0.3"
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 = True
46
- if args.disable_xsd_check:
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 = True
47
- if args.disable_xsd_check:
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, check_xsd=check_xsd,
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 as e:
85
- logger.error('factur-x lib call failed. Error: %s', e)
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__ = "July 2025"
13
- __version__ = "0.4"
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
- xml_file = open(args.xml_file, 'rb')
38
- # The important line of code is below !
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
- xml_file, flavor=args.flavor, level=args.level)
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)