diff --git a/pyproject.toml b/pyproject.toml
index 99ca352..b993f20 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,9 +29,14 @@ classifiers = [
dependencies = [
"beautifulsoup4",
+ "click",
+ "lxml",
"pyperclip",
]
+[project.scripts]
+xinvoice = "xinvoice:main"
+
[project.urls]
Source = "https://git.cpi.imtek.uni-freiburg.de/CPI/xinvoice.git"
diff --git a/tests/carl-roth-1.xml b/tests/carl-roth-1.xml
new file mode 100644
index 0000000..1cdf753
--- /dev/null
+++ b/tests/carl-roth-1.xml
@@ -0,0 +1,152 @@
+
+
+ urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0
+ XRechnung 2.0
+ 44334826
+ 2022-07-13
+ 380
+ EUR
+ EUR
+ 9930: 08311000-DE142116817-24
+
+ 4100107978
+ 24347050
+
+
+ 36383746
+
+
+
+
+ Carl Roth GmbH + Co.KG
+
+
+ Schoemperlenstraße 1-5
+ Karlsruhe
+ 76185
+ DE
+
+ DE
+
+
+
+ DE143621073
+
+ VAT
+
+
+
+ Carl Roth GmbH + Co.KG
+ CarlRoth
+
+
+ Nadine Tänzer
+ +49/721/5606-136
+ n.taenzer@carlroth.de
+
+
+
+
+
+
+ ALBEFRE3
+
+
+ Postfach
+ Freiburg
+ 79085
+
+ DE
+
+
+
+ Albert-Ludwigs-Universität Freiburg Finanzbuchhaltung u. Universitätskasse
+ ALBEFRE3
+
+
+
+
+ 2022-07-01
+
+
+ Stefan-Meier-Str. 31
+ Freiburg im Breisgau
+ 79104
+
+ DE
+
+
+
+
+
+ Albert-Ludwigs-Universität Freiburg Institut für Makromolekulare Chemie Leitung Magazin / Calvino, Stock:-02 Raum: 018
+
+
+
+
+ 30
+ 44334826
+
+ DE52660100750000180751
+ Carl Roth GmbH + Co.KG
+
+ PBNKDEFF660
+
+
+
+
+ 14 Tage 2%, 30 Tage netto
+
+
+ 4.51
+
+ 23.72
+ 4.51
+
+ S
+ 19
+
+ VAT
+
+
+
+
+
+ 23.72
+ 23.72
+ 28.23
+ 0
+ 0
+ 0
+ 0
+ 28.23
+
+
+ 50
+ 1
+ 23.72
+
+ Schwefelsäure 96 % rein
+ Schwefelsäure 96 %
+
+ 9316.1
+
+
+ S
+ 19
+
+ VAT
+
+
+
+
+ 23.72
+ 1
+
+ false
+ 4.18
+ 27.90
+
+
+
+
diff --git a/tests/fisher-1.xml b/tests/fisher-1.xml
new file mode 100644
index 0000000..098ca86
--- /dev/null
+++ b/tests/fisher-1.xml
@@ -0,0 +1,124 @@
+
+
+ 2.1
+ urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0
+ urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
+ 4023255950
+ 2022-07-11
+ 2022-08-10
+ 380
+ 21 Tage 3%, 30 Tage netto
+ EUR
+
+ 4100108427
+ 1022710610
+
+
+
+ DE166515700
+
+ Fisher Scientific GmbH
+
+
+ Im Heiligen Feld 17
+ Schwerte
+ 58239
+ NRW
+
+ DE
+
+
+
+ DE166515700
+
+ VAT
+
+
+
+ Fisher Scientific GmbH
+ DE166515700
+
+
+ Debitorenbuchhaltung
+ +492304932899
+ debitoren.desch@thermofisher.com
+
+
+
+
+
+ DE142116817
+
+ Albert-Ludwigs-Universität Freiburg, Finanzbuchhaltung und Universitätskasse
+
+
+ Postfach
+ Freiburg
+ 79085
+
+ DE
+
+
+
+ DE142116817
+
+ VAT
+
+
+
+ Albert-Ludwigs-Universität Freiburg, Finanzbuchhaltung und Universitätskasse
+ DE142116817
+
+
+ XRechnung@zv.uni-freiburg.de
+
+
+
+
+ 30
+
+ DE61506700090030390900
+
+ DEUTDEFF506
+
+
+
+
+ 34.61
+
+ 182.16
+ 34.61
+
+ S
+ 19.00
+
+ VAT
+
+
+
+
+
+ 182.16
+ 182.16
+ 216.77
+ 216.77
+
+
+ 1
+ 1.0
+ 182.16
+
+ MTT, 1g, (3(4,5dimethylthiazol2yl) 2,
+
+ S
+ 19.00
+
+ VAT
+
+
+
+
+ 182.16
+
+
+
diff --git a/tests/test_xinvoice.py b/tests/test_xinvoice.py
index 72ce5b2..2b2ac85 100644
--- a/tests/test_xinvoice.py
+++ b/tests/test_xinvoice.py
@@ -24,18 +24,92 @@ mistakes.
import pytest
-def test_example_unittest():
- """ example unittest
+@pytest.fixture
+def root_dir(request):
+ import pathlib
- will be run by 'make test' and 'make testall' but not 'make coverage'
- """
- assert True
+ return pathlib.Path(request.path).parent
-@pytest.mark.functional
-def test_example_functional_test():
- """ example unittest
+def test_open_file(root_dir):
+ from bs4 import BeautifulSoup
- will be by 'make coverage' and 'make testall' but not 'make test'
- """
- assert True
+ from xinvoice import open_invoice
+
+ result = open_invoice(root_dir / "carl-roth-1.xml")
+
+ assert isinstance(result, BeautifulSoup)
+
+
+@pytest.mark.parametrize(
+ "file_name, recipient", [("carl-roth-1.xml", "Calvino")]
+)
+def test_get_recipient_with_name(root_dir, file_name, recipient):
+ from xinvoice import open_invoice, get_recipient
+
+ soup = open_invoice(root_dir / file_name)
+
+ result = get_recipient(soup)
+
+ assert recipient in result
+
+
+@pytest.mark.parametrize("file_name", ["fisher-1.xml"])
+def test_get_recipient_without_name(root_dir, file_name):
+ from xinvoice import open_invoice, get_recipient
+
+ soup = open_invoice(root_dir / file_name)
+
+ result = get_recipient(soup)
+
+ assert result is None
+
+
+@pytest.mark.parametrize(
+ "file_name, expected_items",
+ [
+ ("carl-roth-1.xml", ["Schwefelsäure 96 %"]),
+ ("fisher-1.xml", ["MTT, 1g, (3(4,5dimethylthiazol2yl) 2,"]),
+ ],
+)
+def test_get_items(root_dir, file_name, expected_items):
+ from xinvoice import get_items, open_invoice
+
+ soup = open_invoice(root_dir / file_name)
+
+ result = get_items(soup)
+
+ assert result == expected_items
+
+
+@pytest.mark.parametrize(
+ "file_name, expected_name",
+ [
+ ("carl-roth-1.xml", "Céline"),
+ ("fisher-1.xml", "+++ UNKNOWN +++"),
+ ],
+)
+def test_get_recipient_short_name(root_dir, file_name, expected_name):
+ from xinvoice import open_invoice, get_recipient_short_name
+
+ soup = open_invoice(root_dir / file_name)
+
+ result = get_recipient_short_name(soup)
+
+ assert result == expected_name
+
+
+@pytest.mark.parametrize(
+ "file_name, expected_content",
+ [
+ ("carl-roth-1.xml", ["Céline", "Schwefelsäure"]),
+ ("fisher-1.xml", ["UNKNOWN", "MTT"]),
+ ],
+)
+def test_parse(root_dir, file_name, expected_content):
+ from xinvoice import parse
+
+ result = parse(root_dir / file_name)
+
+ for part in expected_content:
+ assert part in result
diff --git a/xinvoice/__init__.py b/xinvoice/__init__.py
index 628bba7..67b77b8 100644
--- a/xinvoice/__init__.py
+++ b/xinvoice/__init__.py
@@ -4,3 +4,68 @@ Parsing X-Invoice files
"""
__version__ = "0.0.1"
+
+# from bs4 import BeautifulSoup
+
+import click
+import pyperclip
+from bs4 import BeautifulSoup
+
+
+def open_invoice(file_path):
+ with open(file_path, "r") as handle:
+ return BeautifulSoup(handle, "xml")
+
+
+def drill_down(soup, tags):
+ current_tag, *rest_tags = tags
+ findings = soup.find_all(current_tag)
+ if not findings:
+ yield None
+ elif not rest_tags:
+ yield from (f.string for f in findings)
+ else:
+ for child in findings:
+ yield from drill_down(child, rest_tags)
+
+
+def get_recipient(soup):
+ results = drill_down(
+ soup, ["cac:DeliveryParty", "cac:PartyName", "cbc:Name"]
+ )
+ return next(results)
+
+
+def get_recipient_short_name(soup):
+ full_text = get_recipient(soup)
+ if full_text is None:
+ return "+++ UNKNOWN +++"
+ full_text = full_text.lower()
+ if "calvino" in full_text:
+ return "Céline"
+ if "pappas" in full_text:
+ return "Babis"
+ if "slesarenko" in full_text:
+ return "Slava"
+ return "CPI"
+
+
+def get_items(soup):
+ result = drill_down(soup, ["cac:InvoiceLine", "cac:Item", "cbc:Name"])
+ return list(result)
+
+
+def parse(file_path):
+ soup = open_invoice(file_path)
+ recipient = get_recipient_short_name(soup)
+ items = get_items(soup)
+ lines = [f"for {recipient}:"] + items
+ text = "\n".join(lines)
+ pyperclip.copy(text)
+ return text
+
+
+@click.command()
+@click.argument("file_path", type=click.Path(exists=True))
+def main(file_path):
+ print(parse(file_path))