@@ -27,6 +27,10 @@ class StringNotASCIIException(Exception):
2727 pass
2828
2929
30+ class UnknownDataTypeException (Exception ):
31+ pass
32+
33+
3034REGEX_ASCII = re .compile (r'[ -~]*' )
3135
3236
@@ -48,18 +52,29 @@ def unpack(data: str) -> dict:
4852
4953 end = data .index ('>' , start )
5054 tag = data [start + 1 :end ]
55+ dtype = None
5156 try :
52- param , length = tag .split (':' )
57+ tag_def = tag .split (':' )
58+ param = tag_def [0 ]
59+ length = tag_def [1 ]
60+ if len (tag_def ) == 3 :
61+ dtype = tag_def [2 ]
5362 except ValueError :
54- raise TagDefinitionException ('Wrong param:length ' )
63+ raise TagDefinitionException ('Wrong tag definition ' )
5564
5665 try :
5766 length = int (length )
5867 except ValueError :
5968 raise TagDefinitionException ('Wrong length' )
6069
6170 value = data [end + 1 :end + 1 + length ]
62- unpacked [param .upper ()] = value
71+ if param .upper ().startswith ('USERDEF' ):
72+ if 'USERDEFS' not in unpacked :
73+ unpacked ['USERDEFS' ] = []
74+ unpacked ['USERDEFS' ].append ({'dtype' : dtype ,
75+ 'userdef' : value })
76+ else :
77+ unpacked [param .upper ()] = value
6378
6479 return unpacked
6580
@@ -95,18 +110,24 @@ def adi2dict(adi: str) -> dict:
95110 return doc
96111
97112
98- def pack (param : str , value : str ) -> str :
113+ def pack (param : str , value : str , dtype : str = None ) -> str :
99114 """Generates ADI tag if value is not empty
100115 Does not generate tags for *_INTL types as required by specification.
101116
102117 :param param: the tag parameter (converte to uppercase)
103- :param value: the tag value
118+ :param value: the tag value (or tag definition if param is a USERDEF field)
119+ :param dtype: the optional datatype (mainly used for USERDEFx in header)
104120 :return: <param:length>value
105121 """
106122
107- if not param .endswith ('_INTL' ):
123+ if not param .upper (). endswith ('_INTL' ):
108124 if re .fullmatch (REGEX_ASCII , value ):
109- return f'<{ param .upper ()} :{ len (str (value ))} >{ value } ' if value else ''
125+ if dtype :
126+ if len (dtype ) > 1 or dtype not in 'BNDTSIMGEL' :
127+ raise UnknownDataTypeException (f'Datatype "{ dtype } " in "{ param } "' )
128+ return f'<{ param .upper ()} :{ len (str (value ))} :{ dtype } >{ value } ' if value else ''
129+ else :
130+ return f'<{ param .upper ()} :{ len (str (value ))} >{ value } ' if value else ''
110131 else :
111132 raise StringNotASCIIException (f'Value "{ value } " in parameter "{ param } " contains non ASCII characters' )
112133 else :
@@ -115,11 +136,14 @@ def pack(param: str, value: str) -> str:
115136
116137def dict2adi (data_dict : dict , comment : str = 'ADIF export by ' + __proj_name__ ) -> str :
117138 """Takes a dictionary and converts it to ADI format
118- Parameters can be in upper or lower case. The output is upper case. The user must take care that parameters are not doubled!
139+ Parameters can be in upper or lower case. The output is upper case. The user must take care
140+ that parameters are not doubled!
119141 *_INTL parameters are ignored as they are not allowed in ADI.
120142 Empty records are skipped.
121143
122144 If 'HEADER' is present the comment is added and missing header fields are filled with defaults.
145+ The header can contain a list of user definitions as USERDEFS. Each user definition is expected as a dictionary
146+ with datatype as "dtype" and field definition as "userdef" instead of a string value.
123147
124148 :param data_dict: the dictionary with header and records
125149 :param comment: the comment to induce the header"""
@@ -139,6 +163,9 @@ def dict2adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__)
139163 if p .upper () in ('ADIF_VER' , 'PROGRAMID' , 'PROGRAMVERSION' , 'CREATED_TIMESTAMP' ):
140164 data += pack (p .upper (), data_dict ['HEADER' ][p ]) + '\n '
141165 default .pop (p .upper ())
166+ elif p .upper () == 'USERDEFS' :
167+ for i , u in enumerate (data_dict ['HEADER' ][p ], 1 ):
168+ data += pack (f'USERDEF{ i } ' , u ['userdef' ], u ['dtype' ]) + '\n '
142169 for p in default :
143170 data += pack (p , default [p ]) + '\n '
144171 data += '<EOH>\n \n '
0 commit comments