diff --git a/dokumente/management/commands/export_xml.py b/dokumente/management/commands/export_xml.py index ab98e40..fe0bb28 100644 --- a/dokumente/management/commands/export_xml.py +++ b/dokumente/management/commands/export_xml.py @@ -4,6 +4,39 @@ from datetime import datetime from dokumente.models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Checklistenfrage +def _parse_markdown_table(markdown_content): + """ + Parse markdown table content and return XML element with
structure + """ + lines = [line.strip() for line in markdown_content.strip().split('\n') if line.strip()] + if not lines: + return None + + # Create table element + table = ET.Element('table') + + # Parse first row as header + header_row = [cell.strip() for cell in lines[0].split('|') if cell.strip()] + header = ET.SubElement(table, 'header') + for cell in header_row: + column = ET.SubElement(header, 'column') + column.text = cell + + # Parse remaining rows (skip separator row if it exists) + for line in lines[2:] if len(lines) > 1 and all(c in '-| ' for c in lines[1]) else lines[1:]: + # Check if this is a separator row + if all(c in '-| ' for c in line): + continue + + row = ET.SubElement(table, 'row') + row_cells = [cell.strip() for cell in line.split('|') if cell.strip()] + for cell in row_cells: + column = ET.SubElement(row, 'column') + column.text = cell + + return table + + class Command(BaseCommand): help = 'Export all dokumente as XML' @@ -49,20 +82,34 @@ class Command(BaseCommand): geltungsbereich_sections = dokument.geltungsbereich_set.all().order_by('order') if geltungsbereich_sections: geltungsbereich_element = ET.SubElement(doc_element, 'Geltungsbereich') - abschnitt_element = ET.SubElement(geltungsbereich_element, 'Abschnitt') for gb in geltungsbereich_sections: - section = ET.SubElement(abschnitt_element, 'Teil') - section.set('typ', gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text") - section.text = gb.inhalt + section_type = gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(gb.inhalt) + if table is not None: + abschnitt_element = ET.SubElement(geltungsbereich_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.append(table) + else: + abschnitt_element = ET.SubElement(geltungsbereich_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.text = gb.inhalt einleitung_sections = dokument.einleitung_set.all().order_by('order') if einleitung_sections: einleitung_element = ET.SubElement(doc_element, 'Einleitung') - abschnitt_element = ET.SubElement(einleitung_element, 'Abschnitt') for ei in einleitung_sections: - section = ET.SubElement(abschnitt_element, 'Teil') - section.set('typ', ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text") - section.text = ei.inhalt + section_type = ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(ei.inhalt) + if table is not None: + abschnitt_element = ET.SubElement(einleitung_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.append(table) + else: + abschnitt_element = ET.SubElement(einleitung_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.text = ei.inhalt ET.SubElement(doc_element, 'Ziel').text = "" ET.SubElement(doc_element, 'Grundlagen').text = "" @@ -96,20 +143,34 @@ class Command(BaseCommand): kurztext_sections = vorgabe.vorgabekurztext_set.all().order_by('order') if kurztext_sections: kurztext_element = ET.SubElement(vorgabe_el, 'Kurztext') - abschnitt = ET.SubElement(kurztext_element, 'Abschnitt') for kt in kurztext_sections: - teil = ET.SubElement(abschnitt, 'Teil') - teil.set('typ', kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text") - teil.text = kt.inhalt - + section_type = kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(kt.inhalt) + if table is not None: + abschnitt = ET.SubElement(kurztext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.append(table) + else: + abschnitt = ET.SubElement(kurztext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.text = kt.inhalt + langtext_sections = vorgabe.vorgabelangtext_set.all().order_by('order') if langtext_sections: langtext_element = ET.SubElement(vorgabe_el, 'Langtext') - abschnitt = ET.SubElement(langtext_element, 'Abschnitt') for lt in langtext_sections: - teil = ET.SubElement(abschnitt, 'Teil') - teil.set('typ', lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text") - teil.text = lt.inhalt + section_type = lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(lt.inhalt) + if table is not None: + abschnitt = ET.SubElement(langtext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.append(table) + else: + abschnitt = ET.SubElement(langtext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.text = lt.inhalt referenz_element = ET.SubElement(vorgabe_el, 'Referenzen') for ref in vorgabe.referenzen.all(): diff --git a/dokumente/tests.py b/dokumente/tests.py index 9bf141a..d315f89 100644 --- a/dokumente/tests.py +++ b/dokumente/tests.py @@ -1675,6 +1675,54 @@ class ExportXMLCommandTest(TestCase): # Should still contain active document self.assertIn('TEST-001', output) self.assertIn('Test Standard', output) + + def test_export_xml_command_table_structure(self): + """Test export_xml command converts markdown tables to proper XML structure""" + # Create document with table + table_doc = Dokument.objects.create( + nummer="TABLE-001", + dokumententyp=self.dokumententyp, + name="Table Test Document", + aktiv=True + ) + table_doc.autoren.add(self.autor1) + + table_vorgabe = Vorgabe.objects.create( + order=1, + nummer=1, + dokument=table_doc, + thema=self.thema, + titel="Table Test Vorgabe", + gueltigkeit_von=date(2023, 1, 1), + gueltigkeit_bis=date(2025, 12, 31) + ) + + table_content = "| Spalte1 | Spalte2 |\n|---------|---------|\n| Wert1 | Wert2 |\n| Wert3 | Wert4 |" + + self.langtext_table = VorgabeLangtext.objects.create( + abschnitt=table_vorgabe, + abschnitttyp=self.abschnitttyp_table, + inhalt=table_content, + order=1 + ) + + out = StringIO() + call_command('export_xml', stdout=out) + + output = out.getvalue() + + # Check that table structure is properly exported + self.assertIn('
', output) + self.assertIn('
', output) + self.assertIn('Spalte1', output) + self.assertIn('Spalte2', output) + self.assertIn('', output) + self.assertIn('Wert1', output) + self.assertIn('Wert2', output) + self.assertIn('Wert3', output) + self.assertIn('Wert4', output) + # Should not contain the markdown table content as plain text + self.assertNotIn('| Spalte1 | Spalte2 |', output) class StandardJSONViewTest(TestCase): @@ -1915,6 +1963,7 @@ class StandardXMLViewTest(TestCase): # Create text sections self.abschnitttyp_text = AbschnittTyp.objects.create(abschnitttyp="text") + self.abschnitttyp_table = AbschnittTyp.objects.create(abschnitttyp="table") self.geltungsbereich = Geltungsbereich.objects.create( geltungsbereich=self.dokument, @@ -2049,6 +2098,71 @@ class StandardXMLViewTest(TestCase): self.assertIn('
structure + """ + lines = [line.strip() for line in markdown_content.strip().split('\n') if line.strip()] + if not lines: + return None + + # Create table element + table = ET.Element('table') + + # Parse first row as header + header_row = [cell.strip() for cell in lines[0].split('|') if cell.strip()] + header = ET.SubElement(table, 'header') + for cell in header_row: + column = ET.SubElement(header, 'column') + column.text = cell + + # Parse remaining rows (skip separator row if it exists) + for line in lines[2:] if len(lines) > 1 and all(c in '-| ' for c in lines[1]) else lines[1:]: + # Check if this is a separator row + if all(c in '-| ' for c in line): + continue + + row = ET.SubElement(table, 'row') + row_cells = [cell.strip() for cell in line.split('|') if cell.strip()] + for cell in row_cells: + column = ET.SubElement(row, 'column') + column.text = cell + + return table + + def standard_list(request): dokumente = Dokument.objects.all() return render(request, 'standards/standard_list.html', @@ -294,20 +327,34 @@ def standard_xml(request, nummer): geltungsbereich_sections = dokument.geltungsbereich_set.all().order_by('order') if geltungsbereich_sections: geltungsbereich_element = ET.SubElement(root, 'Geltungsbereich') - abschnitt_element = ET.SubElement(geltungsbereich_element, 'Abschnitt') for gb in geltungsbereich_sections: - section = ET.SubElement(abschnitt_element, 'Teil') - section.set('typ', gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text") - section.text = gb.inhalt + section_type = gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(gb.inhalt) + if table is not None: + abschnitt_element = ET.SubElement(geltungsbereich_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.append(table) + else: + abschnitt_element = ET.SubElement(geltungsbereich_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.text = gb.inhalt einleitung_sections = dokument.einleitung_set.all().order_by('order') if einleitung_sections: einleitung_element = ET.SubElement(root, 'Einleitung') - abschnitt_element = ET.SubElement(einleitung_element, 'Abschnitt') for ei in einleitung_sections: - section = ET.SubElement(abschnitt_element, 'Teil') - section.set('typ', ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text") - section.text = ei.inhalt + section_type = ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(ei.inhalt) + if table is not None: + abschnitt_element = ET.SubElement(einleitung_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.append(table) + else: + abschnitt_element = ET.SubElement(einleitung_element, 'Abschnitt') + abschnitt_element.set('typ', section_type) + abschnitt_element.text = ei.inhalt ET.SubElement(root, 'Ziel').text = "" ET.SubElement(root, 'Grundlagen').text = "" @@ -341,20 +388,34 @@ def standard_xml(request, nummer): kurztext_sections = vorgabe.vorgabekurztext_set.all().order_by('order') if kurztext_sections: kurztext_element = ET.SubElement(vorgabe_el, 'Kurztext') - abschnitt = ET.SubElement(kurztext_element, 'Abschnitt') for kt in kurztext_sections: - teil = ET.SubElement(abschnitt, 'Teil') - teil.set('typ', kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text") - teil.text = kt.inhalt + section_type = kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(kt.inhalt) + if table is not None: + abschnitt = ET.SubElement(kurztext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.append(table) + else: + abschnitt = ET.SubElement(kurztext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.text = kt.inhalt langtext_sections = vorgabe.vorgabelangtext_set.all().order_by('order') if langtext_sections: langtext_element = ET.SubElement(vorgabe_el, 'Langtext') - abschnitt = ET.SubElement(langtext_element, 'Abschnitt') for lt in langtext_sections: - teil = ET.SubElement(abschnitt, 'Teil') - teil.set('typ', lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text") - teil.text = lt.inhalt + section_type = lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text" + if section_type in ('tabelle', 'table'): + table = _parse_markdown_table(lt.inhalt) + if table is not None: + abschnitt = ET.SubElement(langtext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.append(table) + else: + abschnitt = ET.SubElement(langtext_element, 'Abschnitt') + abschnitt.set('typ', section_type) + abschnitt.text = lt.inhalt referenz_element = ET.SubElement(vorgabe_el, 'Referenzen') for ref in vorgabe.referenzen.all():