Repository: dag-hammarskjold-library/dlx

Issue:
Raise invalid record excpeption in MRK import if looks like two records are combined
Catch MRK files with records that are missing a newline in between. This can be done by checking that the tags are in numerical order.

https://github.com/dag-hammarskjold-library/dlx-rest/issues/1641

Discussion:

Solution patch:
diff --git a/dlx/marc/__init__.py b/dlx/marc/__init__.py
index b9c4741..ca9ed36 100644
--- a/dlx/marc/__init__.py
+++ b/dlx/marc/__init__.py
@@ -40,9 +40,13 @@ def __init__(self, rtype, tag, code):
         super().__init__(f'{tag}, {code} is an authority-controlled field')
 
 class AuthInUse(Exception):
-     def __init__(self):
+    def __init__(self):
         super().__init__('Can\'t delete Auth record because it is in use by other records')
 
+class InvalidRecordString(Exception):
+    def __init__(self, string, message='Can\'t parse record from string:'):
+        super().__init__(f'{message} {string}')
+
 ### Decorators
 
 class Decorators():
@@ -1390,6 +1394,9 @@ def to_mrk(self, *tags, language=None, write_id=True):
 
         if write_id and record.id is not None:
             record.set('001', None, str(record.id))
+
+        if not record.get_value('000'):
+            record.set('000', 'None', '****')
         
         return '\n'.join([field.to_mrk(language=language) for field in record.get_fields()]) + '\n'
 
@@ -1507,12 +1514,19 @@ def from_mrc(cls, string):
     @classmethod
     def from_mrk(cls, string: str, auth_control=True, delete_subfield_zero=True):
         self = cls()
+        last_tag = 0
 
         for line in filter(None, string.split('\n')):
             match = re.match(r'=(\w{3})  (.*)', line)            
             tag, rest = match.group(1), match.group(2)
             if tag == 'LDR': tag = '000'
 
+            if int(tag) < int(last_tag):
+                # this can happen if a newline is missing between records
+                raise InvalidRecordString(string, 'Tag order does not look valid')
+            else:
+                last_tag = tag
+
             if tag[:2] == '00':
                 field = Controlfield(tag, rest)
                 


Test patch:
diff --git a/tests/test_marc.py b/tests/test_marc.py
index e67a4f4..3e2213d 100644
--- a/tests/test_marc.py
+++ b/tests/test_marc.py
@@ -778,8 +778,12 @@ def test_to_mrk(bibs):
     control = '\n'.join([line for line in control.split("\n") if line[:4] != '=001'])
     assert bib.to_mrk(write_id=False) == control
 
+    # add 000 if not exists
+    bib = Bib().set('245', 'a', 'title')
+    assert bib.to_mrk() == '=000  ****\n=245  \\\\$atitle\n'
+
 def test_from_mrk(db):
-    from dlx.marc import Bib
+    from dlx.marc import Bib, InvalidRecordString
     
     control = '=000  leader\n=001  1\n=008  controlfield\n=245  \\\\$aThis$bis the$ctitle\n=520  \\\\$aDescription\n=520  \\\\$aAnother description$aRepeated subfield\n=650  \\\\$aHeader$01\n=710  \\\\$aAnother header$02\n'
 
@@ -788,6 +792,10 @@ def test_from_mrk(db):
     assert bib.to_mrk() == control
     assert bib.commit(auth_check=True)
 
+    # invalid tag order
+    with pytest.raises(InvalidRecordString):
+        Bib.from_mrk('=001  99999\n=000  leader')
+
 def test_from_csv(db, bibs):
     from dlx.marc import Bib