db-pkpass

Convert Deutsche Bahn PDF tickets to PKPass
git clone https://git.ce9e.org/db-pkpass.git

commit
910e0a42cea25427486a7765ca2fa40f6b6ce239
parent
91d58f436f283118b192bf2094f65d0c0ab6ab64
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2025-08-20 22:17
separate legs of different journeys

Diffstat

M db_pkpass.py 49 ++++++++++++++++++++++++++-----------------------
M tests.py 62 +++++++++++++++++++++++++++++++------------------------------

2 files changed, 58 insertions, 53 deletions


diff --git a/db_pkpass.py b/db_pkpass.py

@@ -188,19 +188,19 @@ def extract(pdf):
  188   188     lines = iter_lines(pdf)
  189   189     header = extract_header(lines)
  190   190 
  191    -1     legs = []
   -1   191     journeys = [[]]
  192   192     for line in lines:
  193   193         text = ' '.join(line)
  194   194         if text.startswith('Wichtige Nutzungshinweise') or not text.strip():
  195   195             break
  196   196         elif text.startswith('Ihre Reiseverbindung '):
  197    -1             pass
   -1   197             journeys.append([])
  198   198         elif text.startswith('Halt\nDatum\nZeit\nGleis'):
  199   199             pass
  200   200         else:
  201    -1             legs.append(extract_leg(line, header['valid_from']))
   -1   201             journeys[-1].append(extract_leg(line, header['valid_from']))
  202   202 
  203    -1     return header, legs
   -1   203     return header, [legs for legs in journeys if legs]
  204   204 
  205   205 
  206   206 def format_stop(stop, train=None):
@@ -222,7 +222,7 @@ def format_legs(legs):
  222   222 
  223   223 
  224   224 def extract_content(pdf):
  225    -1     header, legs = extract(pdf)
   -1   225     header, journeys = extract(pdf)
  226   226 
  227   227     data = {
  228   228         'formatVersion': 1,
@@ -255,28 +255,31 @@ def extract_content(pdf):
  255   255                     'value': header['id_value'],
  256   256                 },
  257   257             ],
   -1   258             'secondaryFields': [],
  258   259         },
  259   260     }
  260   261 
  261    -1     if legs:
  262    -1         start = legs[0]['start']['station']
  263    -1         destination = legs[-1]['destination']['station']
  264    -1         date = legs[0]['start']['datetime']
   -1   262     if journeys:
   -1   263         start = journeys[0][0]['start']['station']
   -1   264         destination = journeys[0][-1]['destination']['station']
   -1   265         date = journeys[0][0]['start']['datetime']
  265   266         data['description'] = f'{start} → {destination} ({date.date().isoformat()})'
  266    -1         data['boardingPass']['secondaryFields'] = [
  267    -1             {
  268    -1                 'key': 'date',
  269    -1                 'label': 'Datum',
  270    -1                 'dateStyle': 'PKDateStyleFull',
  271    -1                 'timeStyle': 'PKDateStyleNone',
  272    -1                 'value': date.isoformat(),
  273    -1             },
  274    -1             {
  275    -1                 'key': 'legs',
  276    -1                 'label': 'Reiseplan',
  277    -1                 'value': format_legs(legs),
  278    -1             },
  279    -1         ]
   -1   267 
   -1   268         for legs in journeys:
   -1   269             data['boardingPass']['secondaryFields'] += [
   -1   270                 {
   -1   271                     'key': 'date',
   -1   272                     'label': 'Datum',
   -1   273                     'dateStyle': 'PKDateStyleFull',
   -1   274                     'timeStyle': 'PKDateStyleNone',
   -1   275                     'value': legs[0]['start']['datetime'].isoformat(),
   -1   276                 },
   -1   277                 {
   -1   278                     'key': 'legs',
   -1   279                     'label': 'Reiseplan',
   -1   280                     'value': format_legs(legs),
   -1   281                 },
   -1   282             ]
  280   283 
  281   284     return data
  282   285 

diff --git a/tests.py b/tests.py

@@ -19,7 +19,7 @@ class ExtractLegsTests(unittest.TestCase):
   19    19         self.assertEqual(legs, expected)
   20    20 
   21    21     def test_normalpreis(self):
   22    -1         self._test_extract_leg('muster/Muster 918-9 Normalpreis.pdf', [
   -1    22         self._test_extract_leg('muster/Muster 918-9 Normalpreis.pdf', [[
   23    23             {
   24    24                 'start': {
   25    25                     'station': 'Mainz Hbf',
@@ -33,10 +33,10 @@ class ExtractLegsTests(unittest.TestCase):
   33    33                 },
   34    34                 'train': 'RE 2 (4260)',
   35    35             },
   36    -1         ])
   -1    36         ]])
   37    37 
   38    38     def test_supersparpreis(self):
   39    -1         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreis.pdf', [
   -1    39         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreis.pdf', [[
   40    40             {
   41    41                 'start': {
   42    42                     'station': 'Mannheim ARENA/Maimarkt',
@@ -87,10 +87,10 @@ class ExtractLegsTests(unittest.TestCase):
   87    87                 },
   88    88                 'train': 'Bus SEV',
   89    89             },
   90    -1         ])
   -1    90         ]])
   91    91 
   92    92     def test_supersparpreis_young(self):
   93    -1         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreisYoung.pdf', [
   -1    93         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreisYoung.pdf', [[
   94    94             {
   95    95                 'start': {
   96    96                     'station': 'Essen Hbf',
@@ -156,10 +156,10 @@ class ExtractLegsTests(unittest.TestCase):
  156   156                 },
  157   157                 'train': 'IC 1999',
  158   158             },
  159    -1         ])
   -1   159         ]])
  160   160 
  161   161     def test_supersparpreis_2erw(self):
  162    -1         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreis_2Erw.pdf', [
   -1   162         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreis_2Erw.pdf', [[
  163   163             {
  164   164                 'start': {
  165   165                     'station': 'Köln Hbf',
@@ -173,10 +173,10 @@ class ExtractLegsTests(unittest.TestCase):
  173   173                 },
  174   174                 'train': 'ICE 1059',
  175   175             },
  176    -1         ])
   -1   176         ]])
  177   177 
  178   178     def test_supersparpreis_3erw_rueckfahrt(self):
  179    -1         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreis_3Erw_InklR\udcfcckfahrt.pdf', [
   -1   179         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreis_3Erw_InklR\udcfcckfahrt.pdf', [[
  180   180             {
  181   181                 'start': {
  182   182                     'station': 'Hamburg Hbf',
@@ -229,6 +229,7 @@ class ExtractLegsTests(unittest.TestCase):
  229   229                 },
  230   230                 'train': 'BRB RB54 (79065)',
  231   231             },
   -1   232         ], [
  232   233             {
  233   234                 'start': {
  234   235                     'station': 'Rosenheim',
@@ -268,10 +269,10 @@ class ExtractLegsTests(unittest.TestCase):
  268   269                 },
  269   270                 'train': 'ICE 782',
  270   271             },
  271    -1         ])
   -1   272         ]])
  272   273 
  273   274     def test_supersparpreis_senior_rueckfahrt(self):
  274    -1         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreisSenior_InklR\udcfcckfahrt.pdf', [
   -1   275         self._test_extract_leg('muster/Muster 918-9 FV_SuperSparpreisSenior_InklR\udcfcckfahrt.pdf', [[
  275   276             {
  276   277                 'start': {
  277   278                     'station': 'Flensburg',
@@ -337,6 +338,7 @@ class ExtractLegsTests(unittest.TestCase):
  337   338                 },
  338   339                 'train': 'BRB RB54 (79569)',
  339   340             },
   -1   341         ], [
  340   342             {
  341   343                 'start': {
  342   344                     'station': 'Rosenheim',
@@ -388,10 +390,10 @@ class ExtractLegsTests(unittest.TestCase):
  388   390                 },
  389   391                 'train': 'RE 7 (21082)',
  390   392             },
  391    -1         ])
   -1   393         ]])
  392   394 
  393   395     def test_city_ticket(self):
  394    -1         self._test_extract_leg('muster/Muster 918-9 CityTicket.pdf', [
   -1   396         self._test_extract_leg('muster/Muster 918-9 CityTicket.pdf', [[
  395   397             {
  396   398                 'start': {
  397   399                     'station': 'Kassel-Harleshausen',
@@ -441,10 +443,10 @@ class ExtractLegsTests(unittest.TestCase):
  441   443                 },
  442   444                 'train': 'U 1',
  443   445             },
  444    -1         ])
   -1   446         ]])
  445   447 
  446   448     def test_city_ticket_international(self):
  447    -1         self._test_extract_leg('muster/Muster 918-9 CityTicket_International.pdf', [
   -1   449         self._test_extract_leg('muster/Muster 918-9 CityTicket_International.pdf', [[
  448   450             {
  449   451                 'start': {
  450   452                     'station': 'Mainz Hbf',
@@ -484,10 +486,10 @@ class ExtractLegsTests(unittest.TestCase):
  484   486                 },
  485   487                 'train': 'IC 587',
  486   488             },
  487    -1         ])
   -1   489         ]])
  488   490 
  489   491     def test_laenderticket_bayern_nacht(self):
  490    -1         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Bayern Nacht.pdf', [
   -1   492         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Bayern Nacht.pdf', [[
  491   493             {
  492   494                 'start': {
  493   495                     'station': 'Augsburg Hbf',
@@ -527,10 +529,10 @@ class ExtractLegsTests(unittest.TestCase):
  527   529                 },
  528   530                 'train': 'WBA RB35 (83957)',
  529   531             },
  530    -1         ])
   -1   532         ]])
  531   533 
  532   534     def test_laenderticket_rheinland_pfalz(self):
  533    -1         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Rheinland-Pfalz.pdf', [
   -1   535         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Rheinland-Pfalz.pdf', [[
  534   536             {
  535   537                 'start': {
  536   538                     'station': 'Bingen(Rhein) Hbf',
@@ -557,10 +559,10 @@ class ExtractLegsTests(unittest.TestCase):
  557   559                 },
  558   560                 'train': 'RE 3 (29536)',
  559   561             },
  560    -1         ])
   -1   562         ]])
  561   563 
  562   564     def test_laenderticket_saarland(self):
  563    -1         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Saarland.pdf', [
   -1   565         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Saarland.pdf', [[
  564   566             {
  565   567                 'start': {
  566   568                     'station': 'Saarbrücken Hbf',
@@ -587,10 +589,10 @@ class ExtractLegsTests(unittest.TestCase):
  587   589                 },
  588   590                 'train': 'RB 26 (25398)',
  589   591             },
  590    -1         ])
   -1   592         ]])
  591   593 
  592   594     def test_laenderticket_sachsen_anhalt(self):
  593    -1         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Sachsen-Anhalt.pdf', [
   -1   595         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Sachsen-Anhalt.pdf', [[
  594   596             {
  595   597                 'start': {
  596   598                     'station': 'Magdeburg Hbf',
@@ -604,10 +606,10 @@ class ExtractLegsTests(unittest.TestCase):
  604   606                 },
  605   607                 'train': 'RE 13 (16133)',
  606   608             },
  607    -1         ])
   -1   609         ]])
  608   610 
  609   611     def test_laenderticket_thueringen(self):
  610    -1         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Th\udcfcringen.pdf', [
   -1   612         self._test_extract_leg('muster/Muster 918-9 L\udce4nderticket Th\udcfcringen.pdf', [[
  611   613             {
  612   614                 'start': {
  613   615                     'station': 'Erfurt Hbf',
@@ -621,10 +623,10 @@ class ExtractLegsTests(unittest.TestCase):
  621   623                 },
  622   624                 'train': 'RE 16 (74511)',
  623   625             },
  624    -1         ])
   -1   626         ]])
  625   627 
  626   628     def test_quer_durchs_land_ticket(self):
  627    -1         self._test_extract_leg('muster/Muster 918-9 Quer-durchs-Land Ticket.pdf', [
   -1   629         self._test_extract_leg('muster/Muster 918-9 Quer-durchs-Land Ticket.pdf', [[
  628   630             {
  629   631                 'start': {
  630   632                     'station': 'Groß Gerau-Dornberg',
@@ -638,10 +640,10 @@ class ExtractLegsTests(unittest.TestCase):
  638   640                 },
  639   641                 'train': 'RE 70 (4571)',
  640   642             },
  641    -1         ])
   -1   643         ]])
  642   644 
  643   645     def test_schleswig_holstein_ticket(self):
  644    -1         self._test_extract_leg('muster/Muster 918-9 Schleswig-Holstein Ticket.pdf', [
   -1   646         self._test_extract_leg('muster/Muster 918-9 Schleswig-Holstein Ticket.pdf', [[
  645   647             {
  646   648                 'start': {
  647   649                     'station': 'Schleswig',
@@ -655,4 +657,4 @@ class ExtractLegsTests(unittest.TestCase):
  655   657                 },
  656   658                 'train': 'RE 74 (21225)',
  657   659             },
  658    -1         ])
   -1   660         ]])