diff --git a/tests/integration_tests.py b/tests/integration_tests.py index 6001a76..cd069a6 100644 --- a/tests/integration_tests.py +++ b/tests/integration_tests.py @@ -240,3 +240,32 @@ def test_convert_preserves_explicit_empty_string_fields(): assert row[header.index("watermark")] == "" assert row[header.index("fieldType")] == "text" assert row[header.index("displayOrder")] == "1" + + +def test_convert_aligns_rows_to_header_order(tmp_path): + """Ensure item key order does not shift values into wrong TSV columns.""" + input_file = "tests/valid/mixed_key_order.yml" + output_file = tmp_path / "mixed_key_order.tsv" + runner = CliRunner() + + result = runner.invoke( + yml2block.__main__.main, + ["convert", input_file, "-o", str(output_file)], + ) + + assert result.exit_code == 0, result.output + lines = output_file.read_text().splitlines() + header_idx = next( + idx for idx, line in enumerate(lines) if line.startswith("#datasetField") + ) + header = lines[header_idx].split("\t") + row = lines[header_idx + 2].split("\t") + + assert row[header.index("name")] == "Second" + assert row[header.index("watermark")] == "Second watermark" + assert row[header.index("fieldType")] == "textbox" + + expected_width = len(header) + assert expected_width > 0 + for line in lines: + assert len(line.split("\t")) == expected_width diff --git a/tests/valid/mixed_key_order.yml b/tests/valid/mixed_key_order.yml new file mode 100644 index 0000000..9f0c170 --- /dev/null +++ b/tests/valid/mixed_key_order.yml @@ -0,0 +1,35 @@ +--- +metadataBlock: + - name: MixedOrder + displayName: Mixed Order +datasetField: + - name: First + title: First + description: First description + watermark: First watermark + fieldType: text + displayOrder: 1 + advancedSearchField: true + allowControlledVocabulary: false + allowmultiples: false + facetable: false + displayoncreate: true + required: true + parent: + metadatablock_id: MixedOrder + termURI: https://example.org/first + - fieldType: textbox + watermark: Second watermark + name: Second + title: Second + description: Second description + displayOrder: 2 + displayFormat: + advancedSearchField: false + allowControlledVocabulary: false + allowmultiples: false + facetable: false + displayoncreate: false + required: false + parent: + metadatablock_id: MixedOrder diff --git a/yml2block/output.py b/yml2block/output.py index b552f26..f6b7376 100644 --- a/yml2block/output.py +++ b/yml2block/output.py @@ -15,12 +15,20 @@ def write_metadata_block(yml_metadata, output_path, longest_line, verbose): block_lines = [] for block_line in content: - new_line = [""] - - for key, entry in block_line.items(): + for key in block_line.keys(): if key not in block_headers: block_headers.append(key) + for block_line in content: + new_line = [""] + + for key in block_headers: + if key not in block_line: + new_line.append("") + continue + + entry = block_line[key] + value = entry.value # TODO: Consider screening for True, False, None # before and replace them. diff --git a/yml2block/validation.py b/yml2block/validation.py index 2b73f5e..159a94c 100644 --- a/yml2block/validation.py +++ b/yml2block/validation.py @@ -85,11 +85,17 @@ def validate_entry(yaml_chunk, tsv_keyword, lint_conf, verbose): lint = lint_conf.get(lint) violations.extend(lint(item, tsv_keyword)) - # Compute the highest number of columns in the block - row_length = ( - len(item.keys()) if tsv_keyword == "metadataBlock" else len(item.keys()) + 1 - ) - longest_row = max(longest_row, row_length) + # Compute the highest number of columns in the block + block_headers = [] + for item in yaml_chunk: + for key in item.keys(): + if key not in block_headers: + block_headers.append(key) + + row_length = ( + len(block_headers) if tsv_keyword == "metadataBlock" else len(block_headers) + 1 + ) + longest_row = max(longest_row, row_length) if verbose and len(violations) == 0: print("SUCCESS!" if verbose == 1 else "SUCCESS!\n")