Last active
August 4, 2023 09:07
-
-
Save danielquinn/5347d332c4875f2df652265e664454d1 to your computer and use it in GitHub Desktop.
An MT103 parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# This has since been turned into a proper Python3 module, so if you're | |
# running a modern version of Python, you can install it with pip: | |
# | |
# pip install mt103 | |
# | |
# The project is here: https://github.com/danielquinn/mt103 | |
# | |
from __future__ import unicode_literals | |
import re | |
from datetime import date | |
class MT103(object): | |
""" | |
Parses an MT103 standard banking format string into a string-like Python | |
object so you can do things like `mt103.basic_header` or `print(mt103)`. | |
Usage: | |
mt103 = MT103("some-mt-103-string") | |
print("basic header: {}, bank op code: {}, complete message: {}".format( | |
mt103.basic_header, | |
mt103.text.bank_operation_code | |
mt103 | |
)) | |
With considerable help from: | |
http://www.sepaforcorporates.com/swift-for-corporates/read-swift-message-structure/ | |
""" | |
MESSAGE_REGEX = re.compile( | |
"^" | |
"({1:(?P<basic_header>[^}]+)})?" | |
"({2:(?P<application_header>I[^}]+)})?" | |
"({3:(?P<user_header>({113:[A-Z]{4}})?({108:[A-Z0-9]{0,16}}))})?" | |
"({4:\s*(?P<text>.+?)\s*-})?" | |
"({5:(?P<trailer>[^}]+)})?" | |
"$", | |
re.DOTALL | |
) | |
def __init__(self, message): | |
if message is None: | |
message = "" | |
self.raw = message.strip() | |
self.basic_header = None | |
self.application_header = None | |
self.user_header = None | |
self.text = None | |
self.trailer = None | |
self._boolean = False | |
self._populate_by_parsing() | |
def __str__(self): | |
return self.raw | |
def __repr__(self): | |
return str(self) | |
def __bool__(self): | |
return self._boolean | |
__nonzero__ = __bool__ # Python 2 | |
def _populate_by_parsing(self): | |
if not self.raw: | |
return | |
m = self.MESSAGE_REGEX.match(self.raw) | |
self._boolean = bool(m) | |
if not m: | |
return None | |
self.basic_header = m.group("basic_header") | |
self.application_header = m.group("application_header") | |
self.user_header = m.group("user_header") | |
self.trailer = m.group("trailer") | |
self.text = Text(m.group("text") or "") | |
class Text(object): | |
""" | |
With considerable help from: | |
https://en.wikipedia.org/wiki/MT103 and | |
https://gist.github.com/dmcruz/9940a6b217ff701b8f3e | |
""" | |
REGEX = re.compile( | |
"^" | |
"(:20:(?P<transaction_reference>[^\s:]+)\s*)?" | |
"(:23B:(?P<bank_operation_code>[^\s:]+)\s*)?" | |
"(:32A:" | |
"(?P<value_date_year>\d\d)" # NOQA | |
"(?P<value_date_month>\d\d)" | |
"(?P<value_date_day>\d\d)" | |
"(?P<interbank_settled_currency>[A-Z]{3})" | |
"(?P<interbank_settled_amount>[\d,]+)" | |
"\s*)?" | |
"(:33B:" | |
"(?P<original_ordered_currency>[A-Z]{3})" | |
"(?P<original_ordered_amount>[\d,]+)" | |
"\s*)?" | |
"(:50[AFK]:(?P<ordering_customer>.*?)\s*(?=(:\d\d)?))?" | |
"(:52[AD]:(?P<ordering_institution>.*?)\s*(?=(:\d\d)?))?" | |
"(:53[ABD]:(?P<sender_correspondent>[^\s:]*)\s*)?" | |
"(:54[ABD]:(?P<receiver_correspondent>.*?)\s*(?=(:\d\d)?))?" | |
"(:56[ACD]:(?P<intermediary>.*?)\s*(?=(:\d\d)?))?" | |
"(:57[ABCD]:(?P<account_with_institution>.*?)\s*(?=(:\d\d)?))?" | |
"(:59A?:(?P<beneficiary>.*?)\s*(?=(:\d\d)?))?" | |
"(:70:(?P<remittance_information>.*?)\s*(?=(:\d\d)?))?" | |
"(:71A:(?P<details_of_charges>.*?)\s*(?=(:\d\d)?))?" | |
"(:72:(?P<sender_to_receiver_information>.*?)\s*(?=(:\d\d)?))?" | |
"(:77A:(?P<regulatory_reporting>.*?)\s*(?=(:\d\d)?))?" | |
"$", | |
re.DOTALL | |
) | |
def __init__(self, raw): | |
self.raw = raw | |
self.transaction_reference = None | |
self.bank_operation_code = None | |
self.interbank_settled_currency = None | |
self.interbank_settled_amount = None | |
self.original_ordered_currency = None | |
self.original_ordered_amount = None | |
self.ordering_customer = None | |
self.ordering_institution = None | |
self.sender_correspondent = None | |
self.receiver_correspondent = None | |
self.intermediary = None | |
self.account_with_institution = None | |
self.beneficiary = None | |
self.remittance_information = None | |
self.details_of_charges = None | |
self.sender_to_receiver_information = None | |
self.regulatory_reporting = None | |
self.date = None | |
self._boolean = False | |
self._populate_by_parsing() | |
def __str__(self): | |
return self.raw | |
def __repr__(self): | |
return str(self) | |
def __bool__(self): | |
return self._boolean | |
__nonzero__ = __bool__ # Python 2 | |
def _populate_by_parsing(self): | |
if not self.raw: | |
return | |
m = self.REGEX.match(self.raw) | |
self._boolean = bool(m) | |
if not m: | |
return | |
for k, v in m.groupdict().items(): | |
if v is None: | |
continue | |
if k.startswith("value_date_"): | |
continue | |
setattr(self, k, v) | |
try: | |
self.date = date( | |
2000 + int(m.group("value_date_year")), | |
int(m.group("value_date_month")), | |
int(m.group("value_date_day")) | |
) | |
except (ValueError, TypeError): | |
pass # Defaults to None |
@khantrv as I said earlier, you should not be using this gist and instead pip install mt103
. If that fails to support your MT103 message, then you should open a bug report on that repo and one of us will get to it when we have time. Or better yet, you can submit a pull request if you figure it out yourself :-D
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, unfortunately the issue is still present :(. This code is the only option i have found over the internet so far after 2 days of searching and it seems that m = self.REGEX.match(self.raw) always returns none in both MT103 and Text classes. I took the liberty of editing your script and that is how i found the issue, I've tested with many examples but always same response. I think there might be an issue in the MESSAGE_REGEX definition. Will you be so kind and check it out ?