class OFXHeaderV2(OFXHeaderBase): """Header for OFX version 2""" ofxheader = Types.OneOf(200) version = Types.OneOf(200, 201, 202, 203, 210, 211, 220) security = Types.OneOf("NONE", "TYPE1") oldfileuid = Types.String(36) newfileuid = Types.String(36) regex = re.compile( r"""<\?OFX\s+ OFXHEADER=\"(?P<ofxheader>\d+)\"\s+ VERSION=\"(?P<version>\d+)\"\s+ SECURITY=\"(?P<security>[\w]+)\"\s+ OLDFILEUID=\"(?P<oldfileuid>[\w-]+)\"\s+ NEWFILEUID=\"(?P<newfileuid>[\w-]+)\"\s* \?>\s*""", re.VERBOSE, ) # UTF-8 encoding required by OFXv2 spec; explicitly listed here to # conform to v1 class interface above. codec = "utf_8" def __init__( self, version: Union[int, str], ofxheader: Optional[int] = None, security: Optional[str] = None, oldfileuid: Optional[str] = None, newfileuid: Optional[str] = None, ): try: self.version = int(version) self.ofxheader = int(ofxheader or 200) self.security = security or "NONE" self.oldfileuid = oldfileuid or "NONE" self.newfileuid = newfileuid or "NONE" except ValueError as err: raise OFXHeaderError(f"Invalid OFX header - {err.args[0]}") def __str__(self) -> str: # XML header xml_decl = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' fields = ( ("OFXHEADER", str(self.ofxheader)), ("VERSION", str(self.version)), ("SECURITY", self.security), ("OLDFILEUID", self.oldfileuid), ("NEWFILEUID", self.newfileuid), ) attrs = ["=".join((attr, '"{}"'.format(val))) for attr, val in fields] ofx_decl = "<?OFX {}?>".format(" ".join(attrs)) return "\r\n".join((xml_decl, ofx_decl, ""))
class v2(object): ofxheader = Types.OneOf(200, ) version = Types.OneOf(200, 201, 202, 203, 210, 211, 220) security = Types.OneOf('NONE', 'TYPE1') oldfileuid = Types.String(36) newfileuid = Types.String(36) regex = re.compile( r"""<\?OFX\s+ OFXHEADER=\"(?P<ofxheader>\d+)\"\s+ VERSION=\"(?P<version>\d+)\"\s+ SECURITY=\"(?P<security>[\w]+)\"\s+ OLDFILEUID=\"(?P<oldfileuid>[\w-]+)\"\s+ NEWFILEUID=\"(?P<newfileuid>[\w-]+)\"\s* \?>\s*""", re.VERBOSE) codec = 'utf8' def __init__(self, version, xmlversion=None, encoding=None, standalone=None, ofxheader=None, security=None, oldfileuid=None, newfileuid=None): try: self.version = int(version) self.ofxheader = int(ofxheader or 200) self.security = security or 'NONE' self.oldfileuid = oldfileuid or 'NONE' self.newfileuid = newfileuid or 'NONE' except ValueError as e: raise OFXHeaderError('Invalid OFX header - %s' % e.args[0]) def __str__(self): # XML header xml_decl = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' fields = ( ('OFXHEADER', str(self.ofxheader)), ('VERSION', str(self.version)), ('SECURITY', self.security), ('OLDFILEUID', self.oldfileuid), ('NEWFILEUID', self.newfileuid), ) attrs = ['='.join((attr, '"%s"' % val)) for attr, val in fields] ofx_decl = '<?OFX %s?>' % ' '.join(attrs) return '\r\n'.join((xml_decl, ofx_decl))
class v1(object): ofxheader = Types.OneOf(100,) data = Types.OneOf('OFXSGML',) version = Types.OneOf(102, 103, 151, 160) security = Types.OneOf('NONE', 'TYPE1') encoding = Types.OneOf('USASCII','UNICODE', 'UTF-8') charset = Types.OneOf('ISO-8859-1', '1252', 'NONE') compression = Types.OneOf('NONE',) oldfileuid = Types.String(36) newfileuid = Types.String(36) regex = re.compile(r"""\s* OFXHEADER:(?P<OFXHEADER>\d+)\s+ DATA:(?P<DATA>[A-Z]+)\s+ VERSION:(?P<VERSION>\d+)\s+ SECURITY:(?P<SECURITY>[\w]+)\s+ ENCODING:(?P<ENCODING>[A-Z]+)\s+ CHARSET:(?P<CHARSET>\w+)\s+ COMPRESSION:(?P<COMPRESSION>[A-Z]+)\s+ OLDFILEUID:(?P<OLDFILEUID>[\w-]+)\s+ NEWFILEUID:(?P<NEWFILEUID>[\w-]+)\s+ """, re.VERBOSE) def __init__(self, version, ofxheader=None, data=None, security=None, encoding=None, charset=None, compression=None, oldfileuid=None, newfileuid=None): try: self.ofxheader = int(ofxheader or 100) self.data = data or 'OFXSGML' self.version = int(version) self.security = security or 'NONE' self.encoding = encoding or 'USASCII' self.charset = charset or 'NONE' self.compression = compression or 'NONE' self.oldfileuid = oldfileuid or 'NONE' self.newfileuid = newfileuid or 'NONE' except ValueError as e: raise OFXHeaderError('Invalid OFX header - %s' % e.args[0]) def __str__(self): # Flat text header fields = ( ('OFXHEADER', str(self.ofxheader)), ('DATA', self.data), ('VERSION', str(self.version)), ('SECURITY', self.security), ('ENCODING', self.encoding), ('CHARSET', self.charset), ('COMPRESSION', self.compression), ('OLDFILEUID', self.oldfileuid), ('NEWFILEUID', self.newfileuid), ) lines = [':'.join(field) for field in fields] lines = '\r\n'.join(lines) lines += '\r\n'*2 return lines
class v2(object): xmlversion = Types.OneOf('1.0',) encoding = Types.OneOf('UTF-8',) standalone = Types.OneOf('no',) ofxheader = Types.OneOf(200,) version = Types.OneOf(200, 201, 202, 203, 210, 211, 220) security = Types.OneOf('NONE', 'TYPE1') oldfileuid = Types.String(36) newfileuid = Types.String(36) regex = re.compile(r"""(<\?xml\s+ (version=\"(?P<XMLVERSION>[\d.]+)\")?\s* (encoding=\"(?P<ENCODING>[\w-]+)\")?\s* (standalone=\"(?P<STANDALONE>[\w]+)\")?\s* \?>)\s* <\?OFX\s+ OFXHEADER=\"(?P<OFXHEADER>\d+)\"\s+ VERSION=\"(?P<VERSION>\d+)\"\s+ SECURITY=\"(?P<SECURITY>[\w]+)\"\s+ OLDFILEUID=\"(?P<OLDFILEUID>[\w-]+)\"\s+ NEWFILEUID=\"(?P<NEWFILEUID>[\w-]+)\"\s* \?>\s*""", re.VERBOSE) def __init__(self, version, xmlversion=None, encoding=None, standalone=None, ofxheader=None, security=None, oldfileuid=None, newfileuid=None): try: self.version = int(version) self.xmlversion = xmlversion or '1.0' self.encoding = encoding or 'UTF-8' self.standalone = standalone or 'no' self.ofxheader = int(ofxheader or 200) self.security = security or 'NONE' self.oldfileuid = oldfileuid or 'NONE' self.newfileuid = newfileuid or 'NONE' except ValueError as e: raise OFXHeaderError('Invalid OFX header - %s' % e.args[0]) def __str__(self): # XML header xmlfields = (('version', self.xmlversion), ('encoding', self.encoding), ('standalone', self.standalone), ) xmlattrs = ['='.join((attr, '"%s"' %val)) for attr,val in xmlfields] xml_decl = '<?xml %s?>' % ' '.join(xmlattrs) fields = (('OFXHEADER', str(self.ofxheader)), ('VERSION', str(self.version)), ('SECURITY', self.security), ('OLDFILEUID', self.oldfileuid), ('NEWFILEUID', self.newfileuid), ) attrs = ['='.join((attr, '"%s"' %val)) for attr,val in fields] ofx_decl = '<?OFX %s?>' % ' '.join(attrs) return '\r\n'.join((xml_decl, ofx_decl))
class OFXHeaderV1(OFXHeaderBase): """Header for OFX version 1""" ofxheader = Types.OneOf(100) data = Types.OneOf("OFXSGML") version = Types.OneOf(102, 103, 151, 160) security = Types.OneOf("NONE", "TYPE1") encoding = Types.OneOf("USASCII", "UNICODE", "UTF-8") # DRY - mapping of CHARSET: codec used below in codec() # https://docs.python.org/3/library/codecs.html#standard-encodings codecs = {"ISO-8859-1": "latin_1", "1252": "cp1252", "NONE": "utf_8"} charset = Types.OneOf(*codecs.keys()) compression = Types.OneOf("NONE") oldfileuid = Types.String(36) newfileuid = Types.String(36) # 1) Although the OFX spec requires formatting OFX headers as # "HEADER:VALUE", apparently some FIs are inserting whitespace between # the colon and the header value. We'll allow this noncompliant # format, because it's pretty harmless. # # 2) The OFXv1 spec doesn't require line breaks between header fields, # and # apparently some FIs are sending OFX files (including header) # all as one line. # # 3) The OFXv1 spec requires a line break between the OFX header and # the SGML data, but some FIs disregard this requirement. We allow it. # Therefore the regex doesn't capture whitespace at the end of the header; # instead ``parse_header()`` strips whitespace from the start of the data. regex = re.compile( r"""\s* OFXHEADER:\s*(?P<OFXHEADER>\d+)\s* DATA:\s*(?P<DATA>[A-Z]+)\s* VERSION:\s*(?P<VERSION>\d+)\s* SECURITY:\s*(?P<SECURITY>[\w]+)\s* ENCODING:\s*(?P<ENCODING>[A-Z0-9-]+)\s* CHARSET:\s*(?P<CHARSET>[\w-]+)\s* COMPRESSION:\s*(?P<COMPRESSION>[A-Z]+)\s* OLDFILEUID:\s*(?P<OLDFILEUID>[\w-]+)\s* NEWFILEUID:\s*(?P<NEWFILEUID>[\w-]+) """, re.VERBOSE, ) @property def codec(self) -> str: """ String codec used to decode OFX message body. Maps from OFX character set name to Python codec name. """ return self.codecs[self.charset] def __init__( self, version: Union[int, str], ofxheader: Optional[int] = None, data: Optional[str] = None, security: Optional[str] = None, encoding: Optional[str] = None, charset: Optional[str] = None, compression: Optional[str] = None, oldfileuid: Optional[str] = None, newfileuid: Optional[str] = None, ): try: self.ofxheader = int(ofxheader or 100) self.data = data or "OFXSGML" self.version = int(version) self.security = security or "NONE" self.encoding = encoding or "USASCII" self.charset = charset or "NONE" self.compression = compression or "NONE" self.oldfileuid = oldfileuid or "NONE" self.newfileuid = newfileuid or "NONE" except ValueError as err: raise OFXHeaderError(f"Invalid OFX header - {err.args[0]}") def __str__(self) -> str: # Flat text header fields = ( ("OFXHEADER", str(self.ofxheader)), ("DATA", self.data), ("VERSION", str(self.version)), ("SECURITY", self.security), ("ENCODING", self.encoding), ("CHARSET", self.charset), ("COMPRESSION", self.compression), ("OLDFILEUID", self.oldfileuid), ("NEWFILEUID", self.newfileuid), ) lines = "\r\n".join([":".join(field) for field in fields]) # More recent versions of the OFXv1 spec require newlines to demarcate # the message header from the message body lines += "\r\n" * 2 return lines
class OFXHeaderV1(OFXHeaderBase): """ Header for OFX version 1 """ ofxheader = Types.OneOf(100) data = Types.OneOf("OFXSGML") version = Types.OneOf(102, 103, 151, 160) security = Types.OneOf("NONE", "TYPE1") encoding = Types.OneOf("USASCII", "UNICODE", "UTF-8") # DRY - mapping of CHARSET: codec used below in codec() # https://docs.python.org/3/library/codecs.html#standard-encodings codecs = {"ISO-8859-1": "latin_1", "1252": "cp1252", "NONE": "utf_8"} charset = Types.OneOf(*codecs.keys()) compression = Types.OneOf("NONE") oldfileuid = Types.String(36) newfileuid = Types.String(36) regex = re.compile( r"""\s* OFXHEADER:(?P<OFXHEADER>\d+)\s+ DATA:(?P<DATA>[A-Z]+)\s+ VERSION:(?P<VERSION>\d+)\s+ SECURITY:(?P<SECURITY>[\w]+)\s+ ENCODING:(?P<ENCODING>[A-Z0-9-]+)\s+ CHARSET:(?P<CHARSET>[\w-]+)\s+ COMPRESSION:(?P<COMPRESSION>[A-Z]+)\s+ OLDFILEUID:(?P<OLDFILEUID>[\w-]+)\s+ NEWFILEUID:(?P<NEWFILEUID>[\w-]+)\s+ """, re.VERBOSE, ) @property def codec(self): """ String codec used to decode OFX message body. Maps from OFX character set name to Python codec name. """ return self.codecs[self.charset] def __init__( self, version, ofxheader=None, data=None, security=None, encoding=None, charset=None, compression=None, oldfileuid=None, newfileuid=None, ): try: self.ofxheader = int(ofxheader or 100) self.data = data or "OFXSGML" self.version = int(version) self.security = security or "NONE" self.encoding = encoding or "USASCII" self.charset = charset or "NONE" self.compression = compression or "NONE" self.oldfileuid = oldfileuid or "NONE" self.newfileuid = newfileuid or "NONE" except ValueError as err: raise OFXHeaderError("Invalid OFX header - %s" % err.args[0]) def __str__(self): # Flat text header fields = ( ("OFXHEADER", str(self.ofxheader)), ("DATA", self.data), ("VERSION", str(self.version)), ("SECURITY", self.security), ("ENCODING", self.encoding), ("CHARSET", self.charset), ("COMPRESSION", self.compression), ("OLDFILEUID", self.oldfileuid), ("NEWFILEUID", self.newfileuid), ) lines = [":".join(field) for field in fields] lines = "\r\n".join(lines) # More recent versions of the OFXv1 spec require newlines to demarcate # the message header from the message body lines += "\r\n" * 2 return lines