Repository: DHI/mikeio
Pull request #474 (2022-11-19 16:57:41)

Fix: Pfs write non unique keywords


Patch:
diff --git a/mikeio/pfs.py b/mikeio/pfs.py
index b0a3a7e7..e1e4a28b 100644
--- a/mikeio/pfs.py
+++ b/mikeio/pfs.py
@@ -33,7 +33,7 @@ def read_pfs(filename, encoding="cp1252", unique_keywords=False):
     return Pfs(filename, encoding=encoding, unique_keywords=unique_keywords)
 
 
-class PfsRepeatedKeywordParams(list):
+class PfsNonUniqueList(list):
     pass
 
 
@@ -71,13 +71,10 @@ class PfsSection(SimpleNamespace):
 
         if isinstance(value, dict):
             d = value.copy() if copy else value
-            self.__setattr__(key, PfsSection(d))  #
-        elif isinstance(value, PfsRepeatedKeywordParams) or (
-            isinstance(value, List)
-            and sum(isinstance(subv, PfsSection) for subv in value) < len(value)
-        ):
+            self.__setattr__(key, PfsSection(d))
+        elif isinstance(value, PfsNonUniqueList):
             # multiple keywords/Sections with same name
-            sections = []
+            sections = PfsNonUniqueList()
             for v in value:
                 if isinstance(v, dict):
                     d = v.copy() if copy else v
@@ -261,14 +258,9 @@ def parse_yaml_preserving_duplicates(src, unique_keywords=False):
         data = {}
         for key, val in zip(keys, vals):
             if key_count[key] > 1:
-                if isinstance(val, dict):
-                    if key not in data:
-                        data[key] = []
-                    data[key].append(val)
-                else:
-                    if key not in data:
-                        data[key] = PfsRepeatedKeywordParams()
-                    data[key].append(val)
+                if key not in data:
+                    data[key] = PfsNonUniqueList()
+                data[key].append(val)
             else:
                 data[key] = val
         return data
@@ -282,7 +274,7 @@ def parse_yaml_preserving_duplicates(src, unique_keywords=False):
             if key_count[key] > 1:
                 if isinstance(val, dict):
                     if key not in data:
-                        data[key] = []
+                        data[key] = PfsNonUniqueList()
                     data[key].append(val)
                 else:
                     warnings.warn(
@@ -586,7 +578,7 @@ class Pfs:
         # lexer = shlex.shlex(s)
         # lexer.whitespace += ","
         # lexer.quotes += "|"
-        # lexer.wordchars += ".-"
+        # lexer.wordchars += ",.-"
         # return list(lexer)
 
     def _parse_token(self, token: str) -> str:
@@ -662,29 +654,20 @@ class Pfs:
                 f.write(f"{lvl_prefix * lvl}[{k}]\n")
                 f.write(f"{lvl_prefix * lvl}EndSect  // {k}\n\n")
 
-            elif isinstance(v, List) and any(
-                isinstance(subv, PfsSection) for subv in v
-            ):
-                # duplicate sections
-                for subv in v:
-                    if isinstance(subv, PfsSection):
-                        self._write_nested_PfsSections(f, PfsSection({k: subv}), lvl)
-                    else:
-                        subv = self._prepare_value_for_write(subv)
-                        f.write(f"{lvl_prefix * lvl}{k} = {subv}\n")
             elif isinstance(v, PfsSection):
                 f.write(f"{lvl_prefix * lvl}[{k}]\n")
                 self._write_nested_PfsSections(f, v, lvl + 1)
                 f.write(f"{lvl_prefix * lvl}EndSect  // {k}\n\n")
-            elif isinstance(v, PfsRepeatedKeywordParams) or (
-                isinstance(v, list) and all([isinstance(vv, list) for vv in v])
-            ):
+            elif isinstance(v, PfsNonUniqueList):
                 if len(v) == 0:
                     # empty list -> keyword with no parameter
                     f.write(f"{lvl_prefix * lvl}{k} = \n")
                 for subv in v:
-                    subv = self._prepare_value_for_write(subv)
-                    f.write(f"{lvl_prefix * lvl}{k} = {subv}\n")
+                    if isinstance(subv, PfsSection):
+                        self._write_nested_PfsSections(f, PfsSection({k: subv}), lvl)
+                    else:
+                        subv = self._prepare_value_for_write(subv)
+                        f.write(f"{lvl_prefix * lvl}{k} = {subv}\n")
             else:
                 v = self._prepare_value_for_write(v)
                 f.write(f"{lvl_prefix * lvl}{k} = {v}\n")


Test patch:
diff --git a/tests/test_pfs.py b/tests/test_pfs.py
index 96d5141d..d2a9f94b 100644
--- a/tests/test_pfs.py
+++ b/tests/test_pfs.py
@@ -443,6 +443,7 @@ def test_non_unique_keywords():
         pfs = mikeio.Pfs(fn, unique_keywords=True)
 
     assert len(pfs.BoundaryExtractor.POINT_1) == 2
+    assert isinstance(pfs.BoundaryExtractor.POINT_1, mikeio.pfs.PfsNonUniqueList)
     assert isinstance(pfs.BoundaryExtractor.POINT_1[1], mikeio.PfsSection)
 
     # first value will be kept (like MIKE FM)
@@ -457,6 +458,7 @@ def test_non_unique_keywords_allowed():
     assert isinstance(pfs.BoundaryExtractor.POINT_1[1], mikeio.PfsSection)
 
     assert len(pfs.BoundaryExtractor.z_min) == 3
+    assert isinstance(pfs.BoundaryExtractor.z_min, mikeio.pfs.PfsNonUniqueList)
     assert pfs.BoundaryExtractor.z_min == [-3000, 9, 19]
 
 
@@ -920,6 +922,7 @@ def test_nonunique_mixed_keywords_sections1(tmpdir):
       A = 0
       [A]
          B = 0
+         B = 2
       EndSect  // A
       A = 3
    EndSect  // ROOT 
@@ -928,12 +931,22 @@ def test_nonunique_mixed_keywords_sections1(tmpdir):
     pfs = mikeio.Pfs(StringIO(text))
     assert len(pfs.ROOT.A) == 4
     assert isinstance(pfs.ROOT.A[2], mikeio.PfsSection)
-    assert pfs.ROOT.A[2].B == 0
+    assert isinstance(pfs.ROOT.A[2].B, mikeio.pfs.PfsNonUniqueList)
+    assert pfs.ROOT.A[2].B[0] == 0
     assert pfs.ROOT.A[-1] == 3
 
     filename = os.path.join(tmpdir, "nonunique_mixed_keywords_sections.pfs")
     pfs.write(filename)
 
+    with open(filename) as f:
+        outlines = f.readlines()
+
+    assert outlines[5].strip() == "A = '1'"
+    assert outlines[6].strip() == "A = 0"
+    assert outlines[7].strip() == "[A]"
+    assert outlines[8].strip() == "B = 0"
+    assert outlines[9].strip() == "B = 2"
+
 
 def test_nonunique_mixed_keywords_sections2(tmpdir):
     text = """
@@ -954,6 +967,7 @@ def test_nonunique_mixed_keywords_sections2(tmpdir):
 
     pfs = mikeio.Pfs(StringIO(text))
     assert len(pfs.ROOT.A) == 4
+    assert isinstance(pfs.ROOT.A, mikeio.pfs.PfsNonUniqueList)
     assert isinstance(pfs.ROOT.A[0], mikeio.PfsSection)
     assert isinstance(pfs.ROOT.A[0].B[1], mikeio.PfsSection)
     assert isinstance(pfs.ROOT.A[2], mikeio.PfsSection)