GithubHelp home page GithubHelp logo

factur-x's Introduction

Factur-X and Order-X Python library

Factur-X is a Franco-German e-invoicing standard which complies with the European e-invoicing standard EN 16931. The Factur-X specifications are available on the FNFE-MPE website in English and French. The Factur-X standard is also called ZUGFeRD 2.2 in Germany.

Order-X is the equivalent of Factur-X for purchase orders. The Order-X specifications are available in English on the FNFE-MPE website and on the FeRD website.

The main feature of this Python library is to generate Factur-X invoices and Order-X orders from a regular PDF document and a Factur-X or Order-X compliant XML file.

This lib provides additionnal features such as:

  • extract the XML file from a Factur-X or Order-X PDF file,
  • check a Factur-X or Order-X XML file against the official XML Schema Definition.

Some of the features provided by this lib also work for ZUGFeRD 1.0 (the ancestor of the Factur-X standard).

Installation

To install it on Linux, run:

sudo pip3 install --upgrade factur-x

Usage

from facturx import generate_from_file

generate_from_file(regular_pdf_file, xml_file)

The PDF file regular_pdf_file will be updated to Factur-X/Order-X. If you want to write the resulting Factur-X/Order-X PDF to another file, use the argument output_pdf_file.

To have more examples, look at the docstrings in the source code or look at the source code of the command line tools located in the bin subdirectory.

Command line tools

Several command line tools are provided with this lib:

  • facturx-pdfgen: generate a Factur-X or Order-X PDF file from a regular PDF file and an XML file
  • facturx-pdfextractxml: extract the XML file from a Factur-X or Order-X PDF file
  • facturx-xmlcheck: check a Factur-X or Order-X XML file against the official XML Schema Definition

All these commande line tools have a --help option that explains how to use them and shows all the available options.

Tutorial: generate a Factur-X invoice under Windows

Download the last version of Python for Windows from python.org/downloads.

Launch the installer. On the first screen of the installer, enable the option Add python.exe to PATH. At the end of the installation process, the installer displays a screen with the message Setup was successful ; at that step, it may propose you Disable path length limit with a help message that says Changes your machine configuration to allow programs, including Python, to bypass the 260 character "MAX_PATH" limitation. You must accept this proposal (otherwise the installation of the factur-x library will fail): click on the label Disable path length limit and follow the instructions.

Open a Windows command prompt as Administrator and enter the following command to download and install the factur-x library:

pip3 install --upgrade factur-x

Look at the installation logs and make sure there are no error messages. Close the Windows command prompt.

Open a new Windows command prompt (not as Administrator) and enter the following command (adapt the path to your filesystem):

python C:\Users\Alexis\AppData\Local\Programs\Python\Python311\Scripts\facturx-pdfgen --help

It should display the help of the command facturx-pdfgen.

Enter the following command to generate a Factur-X invoice:

python C:\Users\Alexis\AppData\Local\Programs\Python\Python311\Scripts\facturx-pdfgen C:\Users\Alexis\Documents\invoice.pdf C:\Users\Alexis\Documents\fx.xml C:\Users\Alexis\Documents\invoice-facturx.pdf

where:

  • C:\Users\Alexis\Documents\invoice.pdf is the original PDF invoice,
  • C:\Users\Alexis\Documents\fx.xml is the Factur-X XML file,
  • C:\Users\Alexis\Documents\invoice-facturx.pdf is the Factur-X PDF invoice that will be generated.

Webservice

This project also provides a webservice to generate a Factur-X or Order-X PDF file from a regular PDF file, the XML file and additional attachments (if any). This webservice uses Flask. To run the webservice, run facturx-webservice available in the bin subdirectory of the project. To query the webservice, you must send an HTTP POST request in multipart/form-data using the following keys:

  • pdf -> PDF file (required)
  • xml -> Factur-X or Order-X file (any profile, required)
  • attachment1 -> First attachment (optional)
  • attachment2 -> Second attachment (optional)
  • ...

To deploy this webservice in production, follow the guidelines of the official Flask documentation: you should use a WSGI server (such as Gunicorn) and a reverse proxy (such as Nginx or Apache). You will certainly have to increase the default maximum upload size (default value is only 1MB under Nginx!): use the parameter client_max_body_size for Nginx and LimitRequestBody for Apache.

I recommend this tutorial (in French) which explains how to deploy a Flask application with Gunicorn and Nginx on Ubuntu.

You can use curl, a command line tool to send HTTP requests (on Linux Ubuntu/Debian, just install the curl package) to generate the request:

curl -X POST -F 'pdf=@/home/me/regular_invoice.pdf' -F 'xml=@/home/me/factur-x.xml' -F 'attachment1=@/home/me/delivery_note.pdf' -o /home/me/facturx_invoice.pdf https://ws.fnfe-mpe.org/generate_facturx

A public instance of this webservice is available on a server of FNFE-MPE at the URL https://ws.fnfe-mpe.org/generate_facturx.

Licence

This library is published under the BSD licence (same licence as pypdf on which this lib depends).

Contributors

Changelog

  • Version 3.1 dated 2023-08-13
    • Keep bookmarks, annotations, etc. from input PDF file. For that, we use the method clone_document_from_reader() of pypdf instead of append_pages_from_reader()
    • Fix bug on xml type parsing (bug introduced in version 3.0)
    • raise explicit error when trying to generate a ZUGFeRD 1.x PDF invoice
  • Version 3.0 dated 2023-08-13
    • Replace dependency on PyPDF4 by pypdf. The development focus is back on pypdf and the forks PyPDF2, PyPDF3 and PyPDF4 are not maintained any more, cf this article.
    • Remove support for Python 2.7
    • In the scripts, replace /usr/bin/python3 by /usr/bin/env python
  • Version 2.5 dated 2023-03-24
    • Add support for ZUGFeRD 1.0 in get_level()
    • xml_check_xsd(): avoid warning Use specific 'len(elem)' or 'elem is not None' test instead.
  • Version 2.4 dated 2023-03-13
    • Update Factur-X XSD of all profiles to version 1.0.6
    • Update Order-X XSD of all profiles to version 1.0.0
  • Version 2.3 dated 2021-04-12
    • Fix wrong flavor argument passed by generate_facturx_from_file() to generate_from_file()
  • Version 2.2 dated 2021-04-08
    • Make method generate_from_binary() accessible via the lib
  • Version 2.1 dated 2021-04-07
    • Update Order-X XSD to the latest version provided to me by FNFE-MPE
  • Version 2.0 dated 2021-04-04
    • Add support for Order-X. This implies several changes:
      • method check_facturx_xsd() deprecated in favor of the new method xml_check_xsd() but still operates with a warning
      • method get_facturx_flavor() deprecated in favor of the new method get_flavor() but still operates with a warning
      • method generate_facturx_from_binary() deprecated in favor of the new method generate_from_binary() but still operates with a warning
      • method generate_facturx_from_file() deprecated in favor of the new method generate_from_file() but still operates with a warning
      • new optional argument orderx_type for methods generate_from_file() and generate_from_binary() with default value autodetect
      • new method get_orderx_type() to get the Order-X type (order, order change or order response)
      • new method get_xml_from_pdf() that work both on Factur-X and Order-X (the method get_facturx_xml_from_pdf() still exists and only operates on Factur-X)
      • scripts updated
    • Add lang argument to methods generate_from_file() and generate_from_binary() to set the lang of the PDF. This is one of the requirements for PDF accessibility, which is important for people with disabilities: it allows PDF speech synthesizers for blind people to choose the right language.
    • Add ability to choose the AFRelationship PDF property for the Factur-X/Order-X XML file and also for the additionnal attachments:
      • new argument afrelationship for methods generate_from_file() and generate_from_binary()
      • new key afrelationship for the attachments dict as argument of generate_from_file() and generate_from_binary()
    • Argument additional_attachments was deprecated in method generate_facturx_from_file() in version 1.8: it doesn't operate any more and only displays a warning.
    • Replace the optparse lib by the argparse lib in scripts.
  • Version 1.12 dated 2020-07-16
    • Compress attachments and XMP metadata using Flate compression
  • Version 1.11 dated 2020-05-11
    • Fix crash UnicodeEncodeError on Python 2.7
  • Version 1.10 dated 2020-04-14
    • Update XSD of all profiles to Factur-X version 1.0.5
  • Version 1.9 dated 2020-02-11
    • Improve Python3 support in get_facturx_xml_from_pdf()
  • Version 1.8 dated 2020-01-16
    • New tool facturx-webservice which implements a REST webservice using Flask to generate a Factur-X PDF invoice via a simple POST request.
    • New argument 'attachments' for generate_facturx_from_file() which replaces argument additional_attachments:
      • Possibility to set a filename for the attachment different from filename of the filepath
      • Possibility to set creation dates for attachments
      • Update script facturx-pdfgen to use the new attachments argument
  • Version 1.7 dated 2020-01-13
    • Fix bug in release 1.6 in XMP: variables were not replaced by their real value
  • Version 1.6 dated 2020-01-09
    • Generate XMP (XML-based PDF metadata) via string replacement instead of using XML lib
  • Version 1.5 dated 2019-11-13
    • Fix bug in generate_facturx_from_file() when using argument additional_attachments
  • Version 1.4 dated 2019-07-24
    • Update Factur-X XSD to the final version of Factur-X v1.0.04
    • Support XML extraction with ZUGFeRD invoices using 'zugferd-invoice.xml' filename (instead of the filename 'ZUGFeRD-invoice.xml' specified by the standard)
  • Version 1.3 dated 2019-06-12
    • Add XSD files for Extended profile in the Python package
  • Version 1.2 dated 2019-06-12
    • add support for the Extended profile
    • validate XML for Minimum and Basic WL profiles with the XSD of profile EN 16931, as asked by Cyrille Sautereau
    • minor improvements in the code for /Kids
  • Version 1.1 dated 2019-04-22
    • Improve support for embedded files extraction by adding support for /Kids
  • Version 1.0 dated 2019-01-26
  • Version 0.9 dated 2019-01-25
  • Version 0.8 dated 2018-06-10
    • Make pretty_print work for XMP file, for better readability of that file
  • Version 0.7 dated 2018-05-24
    • Fix XMP structure under /x:xmpmeta/rdf:RDF/rdf:Description (use XML tags instead of XML attributes)
    • declare PDF-1.6 instead of PDF-1.3 (still declared by default by pyPDF2)
  • Version 0.6 dated 2018-05-01
    • Now fully PDF/A-3 compliant with additionnal attachments (tested with veraPDF)
    • facturx-pdfgen: don't overwrite by default and add --overwrite option
    • Add factur-x library version number in metadata creator entry
  • Version 0.5 dated 2018-03-29
    • Fix XMP metadata structure
    • Now fully PDF/A-3 compliant when the input PDF file is PDF/A compliant (tested with veraPDF). This implied copying /OutputIntents and /ID datas from source PDF to Factur-X PDF.
    • Fix support for additionnal attachments: they can now all be saved with Acrobat Reader
    • Improve XML extraction from PDF Factur-x file
  • Version 0.4 dated 2018-03-27
    • Factur-x specs say /AFRelationship must be /Data (and not /Alternative)
    • Update Factur-X XSD to v1.0 final
    • Add support for additionnal attachments
    • Add factur-x lib version in Creator metadata table
    • Add /PageMode = /UseAttachments, so that the attachments are displayed by default when opening Factur-X PDF invoice with Acrobat Reader
    • Improve and enrich PDF objects (ModDate, CheckSum, Size)

factur-x's People

Contributors

alexis-via avatar attrib avatar joshuajan avatar lasalesi avatar tansadio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

factur-x's Issues

Add support for python 3.5 / ubuntu 16-04 LTS

Bonjour,

J'utilise factur-x sur un ubuntu 16-04 qui utilise python 3.5.
La librairie fonctionne très bien en 3.5 mais déclare nécessiter python >= 3.6

Pourriez vous activer le support py3.5 ?

Dans setup.py, ajouter
'Programming Language :: Python :: 3.5',

Cordialement,

lxml issue, cannot import etree

Hello 👋
So first of all thanks a lot for this lib, it is super usefull !
I got it working at some point and now i am trying to use it in a AWS env, but every time i have an error saying

cannot import name 'etree' from 'lxml' (/var/task/lxml/__init__.py)

Which is quite weird, i inspected the zip that is deployed for my lambda, lxml is in it.
I am not a python expert, and i am struggling a lot with it, if someone had this issue before or knows about it i would love some help or some insights !
Thanks you !

Env

Serverless v3
runtime: python 3.8 (also tried with 3.9)

The actuel code (removed irrelevant line)

import json

import os
import io
import tempfile

from facturx import generate_from_file

def generate_factur_x(pdf_data, xml_data):
    pdf_file = io.BytesIO(pdf_data)
    xml_file = io.BytesIO(xml_data)

    # Create a temporary file to store the generated PDF content
    with tempfile.NamedTemporaryFile(delete=False) as temp_pdf_file:
        generate_from_file(pdf_file, xml_file, 'factur-x',
                           output_pdf_file=temp_pdf_file.name)

        # Read the generated PDF content from the temporary file
        temp_pdf_file.seek(0)
        generated_pdf_content = temp_pdf_file.read()

    # Clean up the temporary file
    temp_pdf_file.close()
    temp_pdf_path = temp_pdf_file.name
    return generated_pdf_content, temp_pdf_path

xml path file read as xml content

Hi,

I'm running into a bug when passing xml path file as an argument like :
> python3.9 facturxmerge.py ./pdfA_invoice.pdf ./xml.xml

from my test file :

from datetime import datetime
import sys
import os
from facturx import generate_from_file

if __name__ == "__main__":
    pdfFileName = os.getcwd()+"/"+sys.argv[1]
    xmlFileName = os.getcwd()+"/"+sys.argv[2]

    now = datetime.now()
    outputFile = now.strftime("%Y_%m_%d_%H_%m_%S")+"_factur-x.pdf"
    outputFile = os.getcwd()+"/"+outputFile

    generate_from_file(pdfFileName, xmlFileName, output_pdf_file=outputFile)

Bug is :
lxml.etree.XMLSyntaxError: Start tag expected, '<' not found

In the facturx.py file at row 1106 the code test if the xml arg is a string : if isinstance(xml, (str, bytes)):
and yes, /path/to/my/xml.xml is a string... but not xml, then any further process fail.
Is that intended ?

pdfextractxml on reference sample does not return a file

Greetings,

when I try to extract the xml content from a ZUGFeRD 2.0 sample file (https://www.ferd-net.de/downloads/zugferd-2.0/zugferd-2.0.html), it returns no xml file

$ facturx-pdfextractxml zugferd_2p0_MINIMUM.pdf zugferd_2p0_MINIMUM.xml -l debug
2019-05-27 14:12:39,036 [DEBUG] get_facturx_xml_from_pdf with factur-x lib 1.1
2019-05-27 14:12:39,038 [DEBUG] pdf_root={'/AF': IndirectObject(3, 0), '/Type': '/Catalog', '/Pages': IndirectObject(6, 0), '/Names': {'/EmbeddedFiles': {'/Names': [u'zugferd-invoice.xml', IndirectObject(5, 0)]}}, '/Metadata': IndirectObject(4, 0)}
2019-05-27 14:12:39,038 [DEBUG] embeddedfiles=[u'zugferd-invoice.xml', IndirectObject(5, 0)]
2019-05-27 14:12:39,038 [DEBUG] embeddedfiles_by_two=[(u'zugferd-invoice.xml', IndirectObject(5, 0))]
2019-05-27 14:12:39,039 [DEBUG] found filename=zugferd-invoice.xml
2019-05-27 14:12:39,039 [INFO] Returning an XML file False
2019-05-27 14:12:39,039 [DEBUG] Content of the XML file: False
2019-05-27 14:12:39,039 [WARNING] File zugferd_2p0_MINIMUM.xml has not been created

Why is it not returning an XML file? Any ideas are welcome! Thanks!

This was observed in factur-x v1.1 running in Python 2.7.9.

flateEncode() conflict with PDF integrated signature (eIDAS )

Bonjour,

Depuis la mise à jour 1.12 et notamment ce commit c182e1f (et plus particulièrement les 3 insertions dans facturx/facturx.py), quand j'appose une signature certifiée eIDAS (intégrée au PDF) sur une factur-x, je perds la conformité PDF/A-3.

En empêchant flateEncode(), ma factur-x signée électroniquement est de nouveau valide PDF/A-3.

Serait t'il possible d'avoir une option dans generate_from_file() pour désactiver cette compression ?

Merci pour votre travail.

facturx-pdfgen [ERROR] list index out of range

facturx-pdfgen errors out with a weird list index error.

wget -N "https://raw.githubusercontent.com/konik-io/konik/master/src/test/resources/ZUGFeRD-invoice.sample.xml"

$ facturx-xmlcheck ZUGFeRD-invoice.sample.xml 
2019-12-18 18:32:24,326 [INFO] Factur-X flavor is zugferd (autodetected)
2019-12-18 18:32:24,353 [INFO] Factur-X XML file successfully validated against XSD

$ facturx-pdfgen -l debug testinvoice.pdf ZUGFeRD-invoice.sample.xml invoice.pdf
2019-12-18 18:42:22,074 [DEBUG] generate_facturx_from_file with factur-x lib 1.5
2019-12-18 18:42:22,074 [DEBUG] 1st arg pdf_invoice type=<class 'str'>
2019-12-18 18:42:22,074 [DEBUG] 2nd arg facturx_xml type=<class '_io.BufferedReader'>
2019-12-18 18:42:22,074 [DEBUG] optional arg facturx_level=autodetect
2019-12-18 18:42:22,074 [DEBUG] optional arg check_xsd=True
2019-12-18 18:42:22,074 [DEBUG] optional arg pdf_metadata=None
2019-12-18 18:42:22,074 [DEBUG] optional arg additional_attachments={}
2019-12-18 18:42:22,075 [ERROR] list index out of range

$ head -n 1 /home/testlab/.local/bin/facturx-pdfgen
#!/usr/bin/python3

Also it would be great if the project included some sample files, if not actually test cases.

Bad type for check_xsd argument

Hi,

I'm trying to use the extract xml function in this script :

from facturx import get_xml_from_pdf

pdf_filename = r"C:\Users\user\...\tmp\TEST_006251.pdf"
xml_filename = r"C:\Users\user\...\tmp\factur-x.xml"

check_xsd = False

get_xml_from_pdf(pdf_filename, xml_filename, check_xsd)

but it returns the following error :

Traceback (most recent call last):
  File "c:\Users\user\...\script\script.py", line 8, in <module>
    get_xml_from_pdf(pdf_filename, xml_filename, check_xsd)
  File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\facturx\facturx.py", line 292, in get_xml_from_pdf
    raise ValueError('Bad type for check_xsd argument')
ValueError: Bad type for check_xsd argument

I gather it's expecting a boolean value for check_xsd and somehow doesn't get it.

Maybe there is an error in the README.rst

Hello,

It's just for information, in the Usage section of the README.rst file it's written to use
from facturx import generate_facturx
facturx_pdf_invoice = generate_facturx(regular_pdf_invoice, facturx_xml_file)

But, after testing, it seems we have to use
from facturx import generate_facturx_from_file
facturx_pdf_invoice = generate_facturx_from_file("MacroPDF.pdf", xml_root)

Regression on 1.6 for XMP

Example : see {title} {author} {producer}
xmp:CreatorTool{creator_tool}</xmp:CreatorTool>
xmp:CreateDate{timestamp}</xmp:CreateDate>
xmp:ModifyDate{timestamp}</xmp:ModifyDate>

<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" rdf:about="">
pdfaid:part3</pdfaid:part>
pdfaid:conformanceB</pdfaid:conformance>
</rdf:Description>
<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about="">
dc:title
rdf:Alt
<rdf:li xml:lang="x-default">{title}</rdf:li>
</rdf:Alt>
</dc:title>
dc:creator
rdf:Seq
rdf:li{author}</rdf:li>
</rdf:Seq>
</dc:creator>
dc:description
rdf:Alt
<rdf:li xml:lang="x-default">{subject}</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
<rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about="">
pdf:Producer{producer}</pdf:Producer>
</rdf:Description>
<rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="">
xmp:CreatorTool{creator_tool}</xmp:CreatorTool>
xmp:CreateDate{timestamp}</xmp:CreateDate>
xmp:ModifyDate{timestamp}</xmp:ModifyDate>
</rdf:Description>
<rdf:Description xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#" rdf:about="">
pdfaExtension:schemas
rdf:Bag
<rdf:li rdf:parseType="Resource">
pdfaSchema:schemaFactur-X PDFA Extension Schema</pdfaSchema:schema>
pdfaSchema:namespaceURI
urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#
</pdfaSchema:namespaceURI>
pdfaSchema:prefixfx</pdfaSchema:prefix>
pdfaSchema:property
rdf:Seq
<rdf:li rdf:parseType="Resource">
pdfaProperty:nameDocumentFileName</pdfaProperty:name>
pdfaProperty:valueTypeText</pdfaProperty:valueType>
pdfaProperty:categoryexternal</pdfaProperty:category>
pdfaProperty:descriptionname of the embedded XML invoice file</pdfaProperty:description>
</rdf:li>
<rdf:li rdf:parseType="Resource">
pdfaProperty:nameDocumentType</pdfaProperty:name>
pdfaProperty:valueTypeText</pdfaProperty:valueType>
pdfaProperty:categoryexternal</pdfaProperty:category>
pdfaProperty:descriptionINVOICE</pdfaProperty:description>
</rdf:li>
<rdf:li rdf:parseType="Resource">
pdfaProperty:nameVersion</pdfaProperty:name>
pdfaProperty:valueTypeText</pdfaProperty:valueType>
pdfaProperty:categoryexternal</pdfaProperty:category>
pdfaProperty:descriptionThe actual version of the Factur-X XML schema</pdfaProperty:description>
</rdf:li>
<rdf:li rdf:parseType="Resource">
pdfaProperty:nameConformanceLevel</pdfaProperty:name>
pdfaProperty:valueTypeText</pdfaProperty:valueType>
pdfaProperty:categoryexternal</pdfaProperty:category>
pdfaProperty:description
The conformance level of the embedded Factur-X data
</pdfaProperty:description>
</rdf:li>
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
<rdf:Description xmlns:fx="urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#" rdf:about="">
fx:DocumentType{facturx_documenttype}</fx:DocumentType>
fx:DocumentFileName{facturx_filename}</fx:DocumentFileName>
fx:Version{facturx_version}</fx:Version>
fx:ConformanceLevel{facturx_level}</fx:ConformanceLevel>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>

Unicode strings with encoding declaration are not supported. (Py3?)

Hi all,

I have an XML which is rendered from a code generator. In Python 2.7, everything was running fine. Now, after an upgrade to Python 3.5, running

check_facturx_xsd(facturx_xml=xml)

will fail with Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration. I can resolve this by using

check_facturx_xsd(facturx_xml=xml.encode('utf-8'))

Then, the validation passes fine. However,

generate_facturx_from_binary(pdf, xml)

will again fail with the same Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration Passing an encoded byte array will fail by saying it required a string, file or etree object.

Is this a known Python 3.5 issue?

facturx-xmlcheck

Hello everyone

I'm new to python and would like to know how to run the file facturx-xmlcheck under python from A to Z.

Thank you in advance for taking the time to answer me.

Cannot process ZUGFeRD 2.0.1 documents, because the autodetection thinks they use version 1.0

Steps to reproduce

  1. Download the ZUGFeRD 2.0.1 documentation and examples: http://www.awv-net.de/updates/zugferd20/zugferd201.zip
  2. Extract the file ZUGFeRD201/Beispiele/EXTENDED/zugferd_2p0_EXTENDED_Warenrechnung.pdf
  3. Execute the following code and watch it fail:
from facturx import get_facturx_xml_from_pdf

with open("zugferd_2p0_EXTENDED_Warenrechnung.pdf", "rb") as f:
    get_facturx_xml_from_pdf(f)

This will produce the following error:

2024-07-11 13:32:42,803 [INFO] A valid XML file zugferd-invoice.xml has been found in the PDF file
2024-07-11 13:32:42,805 [ERROR] The XML file is invalid against the XML Schema Definition
2024-07-11 13:32:42,805 [ERROR] XSD Error: Element '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice': No matching global declaration available for the validation root., line 2
2024-07-11 13:32:42,806 [ERROR] No valid XML file found in the PDF: The Zugferd XML file is not valid against the official XML Schema Definition. Here is the error, which may give you an idea on the cause of the problem: Element '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice': No matching global declaration available for the validation root., line 2.

"Mitigation"

Downgrade to factur-x in version 1.12. This is the last version that processes this file completely fine.

Cause

The cause for this bug is the following snippet in get_xml_from_pdf:

flavor = 'autodetect'
if filename == ORDERX_FILENAME:
    flavor = 'order-x'
elif filename == FACTURX_FILENAME:
    flavor = 'factur-x'
elif filename in ZUGFERD_FILENAMES:
    flavor = 'zugferd'
xml_check_xsd(xml_root, flavor=flavor)

The file name of the XML embedded in ZUGFeRD 2.0.1 files is zugferd-invoice.xml. This leads the script to detect the flavor "zugferd" and pass this on to xml_check_xsd, which then automatically loads an incompatible XSD for ZUGFeRD 1.0; causing the validation to fail.

Funny enough, when just leaving the flavor at "autodetect", xml_check_xsd will do its own detection based on the XML inside using get_flavor. This would detect "factur-x" as flavor for the ZUGFeRD 2.0.1 file, which it can then correctly parse.

Proposed fix

The fastest fix I could imagine is to remove the snippet above from the get_xml_from_pdf method. As xml_check_xsd does its own check anyway, this should be fine?

Ability to detach any xml file

First of all your lib definitely helped me in my work.

But I faced with issue with names of inner xml.

You have specified the next names of inner xml to detach as default

FACTURX_FILENAME = 'factur-x.xml'
ZUGFERD_FILENAMES = ['zugferd-invoice.xml', 'ZUGFeRD-invoice.xml']
ORDERX_FILENAME = 'order-x.xml'
ALL_FILENAMES = [FACTURX_FILENAME] + ZUGFERD_FILENAMES + [ORDERX_FILENAME]

and also you have an ability to setup the custom names.
But there is no ability just detach any xml that would be found in pdf.
What if the name will be changed

For example I faced pdf-bill with xml inside without extention: ZUGFeRD-invoice

Error in generation: lxml.etree.XMLSyntaxError: Start tag expected, '<' not found

Hi,

I've been trying to get the 'generate_facturx_from_file' to work. Beforehand I validated my XML file and it's fine. When running the below unfortuntately, no succcess:

from facturx import generate_facturx_from_file
import os
thisdir = os.getcwd()
regular_pdf_invoice = str(thisdir + '\TEST

facturx_xml_file = str(thisdir + '\ZUGFeRD-invoice.xml')
facturx_pdf_invoice = generate_facturx_from_file(regular_pdf_invoice, facturx_xml_file)

The error I get is the following:

Traceback (most recent call last):
File "C:\Users\Mark\Desktop\factur-x.py", line 15, in <module>
facturx_pdf_invoice = generate_facturx_from_file(regular_pdf_invoice, facturx_xml_file)
File "C:\Users\Mark\AppData\Local\Programs\Python\Python37-32\lib\site-packages\facturx        \facturx.py", line 886, in generate_facturx_from_file
xml_root = etree.fromstring(xml_string)
File "src\lxml\etree.pyx", line 3234, in lxml.etree.fromstring
File "src\lxml\parser.pxi", line 1876, in lxml.etree._parseMemoryDocument
File "src\lxml\parser.pxi", line 1757, in lxml.etree._parseDoc
File "src\lxml\parser.pxi", line 1068, in lxml.etree._BaseParser._parseUnicodeDoc
File "src\lxml\parser.pxi", line 601, in lxml.etree._ParserContext._handleParseResultDoc
File "src\lxml\parser.pxi", line 711, in lxml.etree._handleParseResult
File "src\lxml\parser.pxi", line 640, in lxml.etree._raiseParseError
File "<string>", line 1
lxml.etree.XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1

The files I use are attached (had to attach the xml as txt).
TEST.pdf

ZUGFeRD-invoice.txt

The comment, classifying the file as containing 8-bit binary data, is missing

Hi,

After generate a factur-x with the perl command-line "facturx-pdfgen", I got a error validation on https://www.pdf-online.com/osa/validate.aspx :

Validating file "FA-2250-195153.xml.pdf" for conformance level pdfa-3b
The file header format does not conform to the standard.
The comment, classifying the file as containing 8-bit binary data, is missing.
The document does not conform to the requested standard.
The file format (header, trailer, objects, xref, streams) is corrupted.
The document's meta data is either missing or inconsistent or corrupt.
The document does not conform to the PDF/A-3b standard.
Done.

It seem missing "%âãÏÓ" in the 2nd line of the pdf.

More info here : https://forums.adobe.com/thread/2419888

PDF file read mode

Tried to read one of the test files and it failed.

manu@nyx2: ~/Downloads $ facturx-pdfextractxml factur-x/zugferd10/Beispiele/zugferd_example_invoice_en.pdf test.xml
PdfReadWarning: PdfFileReader stream/file object is not in binary mode. It may not be read correctly. [pdf.py:1079]
2018-05-08 15:10:21,938 [INFO] Returning an XML file False
2018-05-08 15:10:21,938 [WARNING] File test.xml has not been created

It did work when doing it step-by-step in iPython. So I guess the readmode isn't set somewhere. Will prepare a PR.

Not valid against the official XML-Schema

Greetings,

so I'm having a problem for over a week now.

This is the error-message I'm getting:

Exception: The Factur-x XML file is not valid against the official XML Schema Definition. Here is the error, which may give you an idea on the cause of the problem: Element '{urn:ferd:CrossIndustryDocument:invoice:1p0}CrossIndustryDocument': No matching global declaration available for the validation root., line 2.

Thanks in advance.

Error in programming logic

I had a look through the source code to learn more about PyPDF4 usage, and found line 206 in facturx.py to be suspicious. It looks like a logical error, and hence a bug.

Apparently the intent of the level parameter is to limit recursion depth, but as it stands the line in question does no such thing.

def _parse_embeddedfiles_kids_node(kids_node, level, res):
    ... # lines omitted
       _parse_embeddedfiles_kids_node(kids_node_l2, 2, res)

This is the line in the fuction _parse_embeddedfiles_kids_node that does the recursion, but instead of dynamically increasing the 2nd argument, level is hardcoded as being 2.

The following line would fix the bug.

_parse_embeddedfiles_kids_node(kids_node_l2, level + 1, res)

Unless I have misunderstood the code, I think this problem exists and can be fixed in the manner I suggested.

install error on debian 12

Installed /usr/local/lib/python3.11/dist-packages/factur_x-2.5-py3.11.egg
Processing dependencies for factur-x==2.5
Searching for PyPDF4
Reading https://pypi.org/simple/PyPDF4/
Downloading https://files.pythonhosted.org/packages/4f/1f/509b44850c475c101aa5b5c9b81755cedd363389d6fbb5c53be6fa915a61/PyPDF4-1.27.0.tar.gz#sha256=7c932441146d205572f96254d53c79ea2c30c9e11df55a5cf87e056c7b3d7f89
Best match: PyPDF4 1.27.0
Processing PyPDF4-1.27.0.tar.gz
Writing /tmp/easy_install-50_cvtbr/PyPDF4-1.27.0/setup.cfg
Running PyPDF4-1.27.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-50_cvtbr/PyPDF4-1.27.0/egg-dist-tmp-728xrwdr
/usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 156, in save_modules
yield saved
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 198, in setup_context
yield
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 259, in run_setup
_execfile(setup_script, ns)
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 46, in _execfile
exec(code, globals, locals)
File "/tmp/easy_install-50_cvtbr/PyPDF4-1.27.0/setup.py", line 32, in
'Programming Language :: Python :: 3.9',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/core.py", line 185, in setup
return run_commands(dist)
^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/core.py", line 201, in run_commands
dist.run_commands()
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 969, in run_commands
self.run_command(cmd)
File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 1213, in run_command
super().run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 988, in run_command
cmd_obj.run()
File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 165, in run
cmd = self.call_command('install_lib', warn_dir=0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 151, in call_command
self.run_command(cmdname)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 318, in run_command
self.distribution.run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 1213, in run_command
super().run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 987, in run_command
cmd_obj.ensure_finalized()
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 111, in ensure_finalized
self.finalize_options()
File "/usr/lib/python3/dist-packages/setuptools/command/install_lib.py", line 17, in finalize_options
self.set_undefined_options('install',('install_layout','install_layout'))
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 296, in set_undefined_options
setattr(self, dst_option, getattr(src_cmd_obj, src_option))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 107, in getattr
raise AttributeError(attr)
AttributeError: install_layout. Did you mean: 'install_platlib'?

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/gizzmo/git/factur-x/./setup.py", line 16, in
setup(
File "/usr/lib/python3/dist-packages/setuptools/init.py", line 108, in setup
return distutils.core.setup(**attrs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/core.py", line 185, in setup
return run_commands(dist)
^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/core.py", line 201, in run_commands
dist.run_commands()
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 969, in run_commands
self.run_command(cmd)
File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 1213, in run_command
super().run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 988, in run_command
cmd_obj.run()
File "/usr/lib/python3/dist-packages/setuptools/command/install.py", line 74, in run
self.do_egg_install()
File "/usr/lib/python3/dist-packages/setuptools/command/install.py", line 131, in do_egg_install
cmd.run(show_deprecation=False)
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 444, in run
self.easy_install(spec, not self.no_deps)
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 686, in easy_install
return self.install_item(None, spec, tmpdir, deps, True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 733, in install_item
self.process_distribution(spec, dist, deps)
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 778, in process_distribution
distros = WorkingSet([]).resolve(
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/pkg_resources/init.py", line 815, in resolve
dist = self._resolve_dist(
^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/pkg_resources/init.py", line 851, in _resolve_dist
dist = best[req.key] = env.best_match(
^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/pkg_resources/init.py", line 1123, in best_match
return self.obtain(req, installer)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/pkg_resources/init.py", line 1135, in obtain
return installer(requirement)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 705, in easy_install
return self.install_item(spec, dist.location, tmpdir, deps)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 731, in install_item
dists = self.install_eggs(spec, download, tmpdir)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 924, in install_eggs
return self.build_and_install(setup_script, setup_base)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1198, in build_and_install
self.run_setup(setup_script, setup_base, args)
File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1182, in run_setup
run_setup(setup_script, args)
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 249, in run_setup
with setup_context(setup_dir):
File "/usr/lib/python3.11/contextlib.py", line 155, in exit
self.gen.throw(typ, value, traceback)
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 190, in setup_context
with save_modules():
File "/usr/lib/python3.11/contextlib.py", line 155, in exit
self.gen.throw(typ, value, traceback)
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 169, in save_modules
saved_exc.resume()
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 143, in resume
raise exc.with_traceback(self._tb)
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 156, in save_modules
yield saved
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 198, in setup_context
yield
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 259, in run_setup
_execfile(setup_script, ns)
File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 46, in _execfile
exec(code, globals, locals)
File "/tmp/easy_install-50_cvtbr/PyPDF4-1.27.0/setup.py", line 32, in
'Programming Language :: Python :: 3.9',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/core.py", line 185, in setup
return run_commands(dist)
^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/core.py", line 201, in run_commands
dist.run_commands()
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 969, in run_commands
self.run_command(cmd)
File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 1213, in run_command
super().run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 988, in run_command
cmd_obj.run()
File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 165, in run
cmd = self.call_command('install_lib', warn_dir=0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 151, in call_command
self.run_command(cmdname)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 318, in run_command
self.distribution.run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 1213, in run_command
super().run_command(command)
File "/usr/lib/python3/dist-packages/setuptools/_distutils/dist.py", line 987, in run_command
cmd_obj.ensure_finalized()
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 111, in ensure_finalized
self.finalize_options()
File "/usr/lib/python3/dist-packages/setuptools/command/install_lib.py", line 17, in finalize_options
self.set_undefined_options('install',('install_layout','install_layout'))
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 296, in set_undefined_options
setattr(self, dst_option, getattr(src_cmd_obj, src_option))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/setuptools/_distutils/cmd.py", line 107, in getattr
raise AttributeError(attr)
AttributeError: install_layout

PDF A-1 as input?

Silly question, is it OK to use PDF/A-1 as input?
I used http://www.mustangproject.org/MustangGnuaccountingBeispielRE-20170509_506blanko.pdf as a basis but https://www.pdf-online.com/osa/validate.aspx complained on the resulting
python-factur-x.pdf

Validating file "python-factur-x.pdf" for conformance level pdfa-3b

The file header format does not conform to the standard.

The comment, classifying the file as containing 8-bit binary data, is missing.

The file trailer dictionary must have an id key.

XMP packet header missing.

XMP packet trailer missing.

A device-specific color space (DeviceRGB) without an appropriate output intent is used.

File specification 'factur-x.xml' not associated with an object.

The document does not conform to the requested standard.

The file format (header, trailer, objects, xref, streams) is corrupted.

The document doesn't conform to the PDF reference (missing required entries, wrong value types, etc.).

The document contains device-specific color spaces.

The document's meta data is either missing or inconsistent or corrupt.

The document does not conform to the PDF/A-3b standard.

Small hack to allow factur-x.py to work with ZUGFeRD 2.2 reference profile XRECHNUNG

I needed a simple way to create ZUGFeRD 2.2 / Factur-X 1.0.6 compliant German XRECHNUNG reference profile PDFs.

Our accounting application creates and validates compliant CII (NOT UBL) xrechnung.xml files (current version 3.0.1)
Validation is done through KOSIT validator, so all I needed was a way to integrate this into a valid PDFA-3B file also
created by our application. Since I use facturx-pdfgen for all my other ZUGFeRD invoices it was the easiest solution to
integrate it the xrechnung profile into factur-x.py.
Attached is a small diff that acomplishes this. Note that there is no validation against German national KOSIT business rules
(already done before), but an additional EN16931 syntax check is done against the COMFORT xsd.

The generated hybrid PDFs validate 100 % OK against the ZUGFeRD community validator. The fnfe-mpe.org validator lacks
support for XRECHNUNG and there rejects the profile, but also validates the components.

Maybe someone finds this helpful and you can integrate this .

facturx-xrechnung.patch

Factur-X PDF/A-3 Validation on Docker Image

Hello,

First of all, I would like to express my appreciation for the incredible work you have done. Your project is truly outstanding and has left me thoroughly impressed.

I have successfully utilized your web service host on FNFE-MPE to generate Factur-X files, and the results were impeccable. In order to ensure its stability for my company's needs, I aimed to deploy it on my personal server. Consequently, I developed a Docker image which performed well. However, I encountered an issue when I attempted to validate Factur-X files using the FNFE-MPE Factur-x Validator. The error I encountered is as follows:

Specification: ISO_19005_3, Clause 6.8, Test 4 - PDF/A-3 Error
The supplemental data provided for associated files, along with the utilization requirements for said associated files, establish a correlation between the embedded file and either the PDF document itself or a specific section within the PDF document.
Severity Level: CosDocument
Context:
root/indirectObjects[60](5 0)/directObject[0]**

I have taken care to utilize the identical files that were used successfully with your web service. Here is my Dockerfile for your reference:

FROM python:3.8
RUN apt-get update && \
    apt-get install -y sudo
RUN sudo pip3 install --upgrade pip
RUN sudo pip3 install --upgrade factur-x
RUN sudo pip install Flask
WORKDIR /app
EXPOSE 5000
CMD ["facturx-webservice","--host=0.0.0.0"]

I am wondering if you are familiar with this issue and whether you could extend your assistance to resolve it.

Thank you for your time and consideration.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.