diff --git a/opensiddur/common/xslt.py b/opensiddur/common/xslt.py
index 3ebf276..9d8a3a8 100644
--- a/opensiddur/common/xslt.py
+++ b/opensiddur/common/xslt.py
@@ -85,7 +85,7 @@ def xslt_transform(
raise
-def main():
+def main(): # pragma: no cover
parser = ArgumentParser()
parser.add_argument("-o", "--output", type=Path, required=False, default=None)
parser.add_argument("transform_file", type=Path)
@@ -96,5 +96,5 @@ def main():
xslt_transform(args.transform_file, args.input_file, args.output)
-if __name__ == "__main__":
+if __name__ == "__main__": # pragma: no cover
main()
\ No newline at end of file
diff --git a/opensiddur/exporter/compiler.py b/opensiddur/exporter/compiler.py
index e57edb2..5a394c7 100644
--- a/opensiddur/exporter/compiler.py
+++ b/opensiddur/exporter/compiler.py
@@ -1006,5 +1006,5 @@ def main(): # pragma: no cover
encoding='utf-8')
-if __name__ == "__main__":
+if __name__ == "__main__": # pragma: no cover
main()
\ No newline at end of file
diff --git a/opensiddur/exporter/pdf/pdf.py b/opensiddur/exporter/pdf/pdf.py
index f6a9e80..bffcbd5 100755
--- a/opensiddur/exporter/pdf/pdf.py
+++ b/opensiddur/exporter/pdf/pdf.py
@@ -199,7 +199,7 @@ def export_to_pdf(input_file, output_pdf):
return True
-def main():
+def main(): # pragma: no cover
"""Main function to handle command line arguments and run the PDF generation."""
parser = argparse.ArgumentParser(
description="Convert JLPTEI XML files to PDF format",
diff --git a/opensiddur/exporter/refdb.py b/opensiddur/exporter/refdb.py
index 11a0ee8..03311e3 100644
--- a/opensiddur/exporter/refdb.py
+++ b/opensiddur/exporter/refdb.py
@@ -615,7 +615,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
-def main():
+def main(): # pragma: no cover
"""Synchronize the reference database with the project directory.
Opens the default database and syncs all projects, printing a summary
@@ -669,5 +669,5 @@ def main():
raise
-if __name__ == '__main__':
+if __name__ == '__main__': # pragma: no cover
main()
diff --git a/opensiddur/exporter/tex/xelatex.py b/opensiddur/exporter/tex/xelatex.py
index 7a373b0..4c4a01e 100644
--- a/opensiddur/exporter/tex/xelatex.py
+++ b/opensiddur/exporter/tex/xelatex.py
@@ -235,10 +235,9 @@ def extract_sources(xml_file_paths: list[Path]) -> tuple[str, str]:
tuple: (preamble_tex, postamble_tex) for the bibliography
"""
index_files = set(get_project_index(fp) for fp in xml_file_paths)
+ bibtex_records_all = []
+ unique_bibtex_records = set()
for index_xml in index_files:
- bibtex_records_all = []
- unique_bibtex_records = set()
-
try:
# Convert the index xml to .bib using bibtex.xslt
index_xml_text = index_xml.read_text(encoding="utf-8")
@@ -251,24 +250,24 @@ def extract_sources(xml_file_paths: list[Path]) -> tuple[str, str]:
except Exception as e:
print(f"Could not extract bibtex from {index_xml}: {e}", file=sys.stderr)
continue
- bibtex_blob = "\n\n".join(bibtex_records_all)
- preamble_tex = ""
- postamble_tex = ""
- if bibtex_blob:
- preamble_tex = f"""\\begin{{filecontents*}}{{job.bib}}
+ bibtex_blob = "\n\n".join(bibtex_records_all)
+ preamble_tex = ""
+ postamble_tex = ""
+ if bibtex_blob:
+ preamble_tex = f"""\\begin{{filecontents*}}{{job.bib}}
{bibtex_blob}
\\end{{filecontents*}}
\\addbibresource{{job.bib}}
"""
- postamble_tex = f"""
+ postamble_tex = f"""
\\begingroup
\\renewcommand{{\\refname}}{{Sources}}
\\nocite{{*}}
\\printbibliography
\\endgroup
"""
- return preamble_tex, postamble_tex
- return "", ""
+ return preamble_tex, postamble_tex
+
def get_file_references(input_file: Path, project_directory: Path = projects_source_root) -> list[Path]:
"""
@@ -346,12 +345,14 @@ def transform_xml_to_tex(input_file, xslt_file=XSLT_FILE, output_file=None):
else:
sys.stdout.write(result)
+ return result
+
except Exception as e:
print(f"Transformation error: {e}", file=sys.stderr)
sys.exit(1)
-def main():
+def main(): # pragma: no cover
"""Main function to handle command line arguments and run the transformation."""
parser = argparse.ArgumentParser(
description="Convert JLPTEI XML files to XeLaTeX format",
diff --git a/opensiddur/tests/exporter/test_xelatex.py b/opensiddur/tests/exporter/test_xelatex.py
index 27d8c1f..968e782 100644
--- a/opensiddur/tests/exporter/test_xelatex.py
+++ b/opensiddur/tests/exporter/test_xelatex.py
@@ -7,6 +7,9 @@
import tempfile
from pathlib import Path
from lxml import etree
+from io import StringIO
+from unittest.mock import patch, MagicMock
+import sys
from opensiddur.exporter.tex.xelatex import (
extract_licenses,
@@ -16,6 +19,8 @@
group_credits,
credits_to_tex,
get_file_references,
+ extract_sources,
+ transform_xml_to_tex,
LicenseRecord,
CreditRecord,
)
@@ -605,6 +610,946 @@ def test_get_file_references_nested_transclusions(self):
self.assertIn(self.project_dir / "project3" / "file3.xml", result)
+class TestExtractSources(unittest.TestCase):
+ """Test source extraction from index.xml files."""
+
+ def setUp(self):
+ """Set up test fixtures."""
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp_dir.cleanup)
+ self.test_dir = Path(self.temp_dir.name) / "project"
+ self.test_dir.mkdir(parents=True)
+
+ def _create_xml_file(self, project: str, filename: str, content: bytes) -> Path:
+ """Helper to create an XML file in a project subdirectory."""
+ project_dir = self.test_dir / project
+ project_dir.mkdir(parents=True, exist_ok=True)
+ file_path = project_dir / filename
+ file_path.write_bytes(content)
+ return file_path
+
+ def test_extract_sources_with_valid_index(self):
+ """Test extracting sources from a valid index.xml file with bibl elements."""
+ index_content = b'''
+
+
+
+ Test Book
+ Test Author
+ 2023
+
+
+'''
+
+ # Create a file in project1
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ index_file = self._create_xml_file("project1", "index.xml", index_content)
+
+ result = extract_sources([file1])
+
+ preamble, postamble = result
+ self.assertIn(r'\begin{filecontents*}{job.bib}', preamble)
+ self.assertIn(r'\addbibresource{job.bib}', preamble)
+ self.assertIn(r'\printbibliography', postamble)
+ self.assertIn(r'\renewcommand{\refname}{Sources}', postamble)
+ # Should contain BibTeX entry with the actual source information
+ self.assertIn('@', preamble)
+ self.assertIn('Test Book', preamble)
+ self.assertIn('Test Author', preamble)
+ self.assertIn('2023', preamble)
+
+ def test_extract_sources_no_bibl_elements(self):
+ """Test handling of index.xml with no bibl elements."""
+ index_content = b'''
+
+ No bibliography
+'''
+
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ index_file = self._create_xml_file("project1", "index.xml", index_content)
+
+ preamble, postamble = extract_sources([file1])
+
+ # Should return empty strings when no bibliography
+ self.assertEqual(preamble, "")
+ self.assertEqual(postamble, "")
+
+ def test_extract_sources_missing_index_file(self):
+ """Test handling of missing index.xml file (graceful skipping)."""
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ # Don't create index.xml
+
+ # Should not raise exception
+ preamble, postamble = extract_sources([file1])
+
+ # Should return empty strings
+ self.assertEqual(preamble, "")
+ self.assertEqual(postamble, "")
+
+ def test_extract_sources_invalid_xml(self):
+ """Test handling of invalid XML in index file."""
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ index_file = self._create_xml_file("project1", "index.xml", b"not valid xml <")
+
+ # Should not raise exception, should skip gracefully
+ preamble, postamble = extract_sources([file1])
+
+ self.assertEqual(preamble, "")
+ self.assertEqual(postamble, "")
+
+ def test_extract_sources_multiple_projects(self):
+ """Test extracting sources from multiple projects with index files."""
+ index1_content = b'''
+
+
+
+ Book 1
+ Author 1
+
+
+'''
+
+ index2_content = b'''
+
+
+
+ Book 2
+ Author 2
+
+
+'''
+
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ file2 = self._create_xml_file("project2", "doc2.xml", b"")
+ index1 = self._create_xml_file("project1", "index.xml", index1_content)
+ index2 = self._create_xml_file("project2", "index.xml", index2_content)
+
+ result = extract_sources([file1, file2])
+
+ preamble, postamble = result
+ # Should contain bibliography entries from both projects
+ self.assertIn(r'\begin{filecontents*}{job.bib}', preamble)
+ # Should contain entries from both index files
+ self.assertIn('Book 1', preamble)
+ self.assertIn('Author 1', preamble)
+ self.assertIn('Book 2', preamble)
+ self.assertIn('Author 2', preamble)
+ # Should have exactly 2 BibTeX entries (one from each project)
+ bibtex_count = preamble.count('@')
+ self.assertEqual(bibtex_count, 2,
+ f"Expected exactly 2 BibTeX entries (one per project), but found {bibtex_count}")
+
+ def test_extract_sources_deduplicates_index_files(self):
+ """Test that same index.xml is only processed once."""
+ index_content = b'''
+
+
+
+ Test Book
+ Test Author
+
+
+'''
+
+ # Multiple files from same project should reference same index
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ file2 = self._create_xml_file("project1", "doc2.xml", b"")
+ file3 = self._create_xml_file("project1", "doc3.xml", b"")
+ index_file = self._create_xml_file("project1", "index.xml", index_content)
+
+ result = extract_sources([file1, file2, file3])
+
+ preamble, postamble = result
+ # Should have bibliography
+ self.assertIn(r'\begin{filecontents*}{job.bib}', preamble)
+ # BibTeX should appear exactly once (deduplicated by set)
+ # Count '@' symbols which appear at the start of each BibTeX entry
+ bibtex_count = preamble.count('@')
+ # Should have exactly one entry since all files reference the same index
+ self.assertEqual(bibtex_count, 1,
+ f"Expected exactly 1 BibTeX entry, but found {bibtex_count}")
+
+
+class TestTransformXmlToTex(unittest.TestCase):
+ """Test the main transform_xml_to_tex function."""
+
+ def setUp(self):
+ """Set up test fixtures."""
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp_dir.cleanup)
+ self.test_dir = Path(self.temp_dir.name) / "project"
+ self.test_dir.mkdir(parents=True)
+
+ def _create_xml_file(self, project: str, filename: str, content: bytes) -> Path:
+ """Helper to create an XML file in a project subdirectory."""
+ project_dir = self.test_dir / project
+ project_dir.mkdir(parents=True, exist_ok=True)
+ file_path = project_dir / filename
+ file_path.write_bytes(content)
+ return file_path
+
+ def test_transform_xml_to_tex_basic(self):
+ """Test basic XML to LaTeX transformation."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+ Hello World
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should produce LaTeX output
+ self.assertIsInstance(result, str)
+ self.assertIn(r'\documentclass{book}', result)
+ self.assertIn(r'\begin{document}', result)
+ self.assertIn('Hello World', result)
+ self.assertIn(r'\end{document}', result)
+
+ def test_transform_xml_to_tex_with_output_file(self):
+ """Test transformation with output file specified."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+ Test content
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+ output_file = Path(self.temp_dir.name) / "output.tex"
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ with patch('sys.stdout'):
+ transform_xml_to_tex(input_file, output_file=str(output_file))
+
+ # Check that output file was created
+ self.assertTrue(output_file.exists())
+ content = output_file.read_text(encoding='utf-8')
+ self.assertIn(r'\documentclass{book}', content)
+ self.assertIn('Test content', content)
+
+ def test_transform_xml_to_tex_integrates_licenses(self):
+ """Test that transform integrates license extraction."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+
+
+ Test License
+
+
+
+
+
+
+ Content
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should include license section in postamble
+ self.assertIn(r'\chapter{Legal}', result)
+ self.assertIn('Test License', result)
+
+ def test_transform_xml_to_tex_integrates_credits(self):
+ """Test that transform integrates credit extraction."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+
+
+ Author
+ Author Name
+
+
+
+
+
+
+ Content
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should include credits section in postamble
+ self.assertIn(r'\chapter{Contributor credits}', result)
+ self.assertIn('Author Name', result)
+
+ def test_transform_xml_to_tex_integrates_sources(self):
+ """Test that transform integrates source extraction."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+ Content
+
+
+'''
+
+ index_content = b'''
+
+
+
+ Source Book
+ Source Author
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+ index_file = self._create_xml_file("project1", "index.xml", index_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should include bibliography in preamble and postamble
+ self.assertIn(r'\addbibresource{job.bib}', result)
+ self.assertIn(r'\printbibliography', result)
+
+ def test_transform_xml_to_tex_handles_invalid_xml(self):
+ """Test error handling for invalid XML."""
+ from unittest.mock import patch, Mock
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ input_file = self._create_xml_file("project1", "invalid.xml", b"not valid xml <")
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ mock_exit = Mock()
+ with patch('sys.exit', mock_exit):
+ # The function should catch the exception and call sys.exit(1)
+ transform_xml_to_tex(input_file)
+ # Verify that sys.exit was called with exit code 1
+ mock_exit.assert_called_once_with(1)
+
+ def test_transform_xml_to_tex_with_stdout(self):
+ """Test transformation output to stdout when output_file is None."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+ Content
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ mock_stdout = StringIO()
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ with patch('sys.stdout', mock_stdout):
+ transform_xml_to_tex(input_file, output_file=None)
+ output = mock_stdout.getvalue()
+
+ # Should have written to stdout
+ self.assertIn(r'\documentclass{book}', output)
+ self.assertIn('Content', output)
+
+
+class TestXSLTTransformation(unittest.TestCase):
+ """Test XSLT transformation directly."""
+
+ def setUp(self):
+ """Set up test fixtures."""
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp_dir.cleanup)
+ self.xslt_file = Path(__file__).parent.parent.parent / "exporter" / "tex" / "xelatex.xslt"
+
+ def test_xslt_tei_div(self):
+ """Test div element conversion."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+ Chapter Title
+ Content
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\part{Chapter Title}', result)
+ self.assertIn('Content', result)
+
+ def test_xslt_tei_p(self):
+ """Test paragraph element conversion."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+ Paragraph text
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn('Paragraph text', result)
+
+ def test_xslt_tei_milestone_chapter(self):
+ """Test milestone with chapter unit."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ Text
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\chapter{1}', result)
+
+ def test_xslt_tei_milestone_verse(self):
+ """Test milestone with verse unit."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ Text
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\textsuperscript{5}', result)
+
+ def test_xslt_tei_choice(self):
+ """Test choice element (kri/ktiv)."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+
+ read
+ written
+
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\textit{read}', result)
+ self.assertIn('(written)', result)
+
+ def test_xslt_tei_emph(self):
+ """Test emphasis element."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ emphasized
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\emph{emphasized}', result)
+
+ def test_xslt_rend_italic(self):
+ """Test rend attribute with italic value."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ italic text
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\textit{italic text}', result)
+
+ def test_xslt_rend_small_caps(self):
+ """Test rend attribute with small-caps value."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ small caps
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\textsc{small caps}', result)
+
+ def test_xslt_rend_superscript(self):
+ """Test rend attribute with superscript value."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ superscript
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\textsuperscript{superscript}', result)
+
+ def test_xslt_rend_align_right(self):
+ """Test rend attribute with align-right value."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ right aligned
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\begin{flushright}', result)
+ self.assertIn(r'\end{flushright}', result)
+
+ def test_xslt_hebrew_language_inline(self):
+ """Test Hebrew language handling for inline text."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ עברית
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\texthebrew{', result)
+ self.assertIn('עברית', result)
+
+ def test_xslt_hebrew_language_block(self):
+ """Test Hebrew language handling for block elements."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ עברית
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\begin{hebrew}', result)
+ self.assertIn(r'\end{hebrew}', result)
+
+ def test_xslt_tei_foreign(self):
+ """Test foreign text element."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ עברית
+ Latin
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\texthebrew{עברית}', result)
+ self.assertIn(r'\textit{Latin}', result)
+
+ def test_xslt_tei_note(self):
+ """Test note element conversion to footnote."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ TextNote content
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\footnote{Note content}', result)
+
+ def test_xslt_tei_lb(self):
+ """Test line break element."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ Line 1Line 2
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\\', result)
+
+ def test_xslt_tei_pb(self):
+ """Test page break element (should be skipped)."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ Text
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ # Should not contain pb-related content
+ self.assertIn('Text', result)
+
+ def test_xslt_tei_lg_l(self):
+ """Test line group and line elements (poetry)."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+
+ Line 1
+ Line 2
+
+
+
+'''
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={"additional-preamble": "", "additional-postamble": ""})
+
+ self.assertIn(r'\begin{verse}', result)
+ self.assertIn(r'\end{verse}', result)
+ self.assertIn('Line 1', result)
+ self.assertIn('Line 2', result)
+
+ def test_xslt_additional_preamble_postamble(self):
+ """Test that additional-preamble and additional-postamble parameters work."""
+ from opensiddur.common.xslt import xslt_transform_string
+
+ xml_content = '''
+
+
+
+ Content
+
+
+'''
+
+ preamble = "\\usepackage{testpackage}\n"
+ postamble = "\\chapter{Appendix}\nAppendix content\n"
+
+ result = xslt_transform_string(self.xslt_file, xml_content,
+ xslt_params={
+ "additional-preamble": preamble,
+ "additional-postamble": postamble
+ })
+
+ self.assertIn(preamble, result)
+ self.assertIn(postamble, result)
+
+
+class TestEdgeCases(unittest.TestCase):
+ """Test edge cases and error handling."""
+
+ def setUp(self):
+ """Set up test fixtures."""
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp_dir.cleanup)
+ self.test_dir = Path(self.temp_dir.name) / "project"
+ self.test_dir.mkdir(parents=True)
+
+ def _create_xml_file(self, project: str, filename: str, content: bytes) -> Path:
+ """Helper to create an XML file."""
+ project_dir = self.test_dir / project
+ project_dir.mkdir(parents=True, exist_ok=True)
+ file_path = project_dir / filename
+ file_path.write_bytes(content)
+ return file_path
+
+ def test_extract_sources_empty_xml(self):
+ """Test extract_sources with empty XML file."""
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ index_file = self._create_xml_file("project1", "index.xml", b"")
+
+ preamble, postamble = extract_sources([file1])
+
+ self.assertEqual(preamble, "")
+ self.assertEqual(postamble, "")
+
+ def test_transform_xml_to_tex_minimal_structure(self):
+ """Test transform with minimal valid XML structure."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should still produce valid LaTeX structure
+ self.assertIn(r'\documentclass{book}', result)
+ self.assertIn(r'\begin{document}', result)
+ self.assertIn(r'\end{document}', result)
+
+ def test_transform_xml_to_tex_special_characters(self):
+ """Test transform with special characters that need LaTeX escaping."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+ Text with $special & characters
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should handle special characters (XSLT will pass through)
+ self.assertIn('Text with', result)
+
+ def test_extract_sources_files_outside_project(self):
+ """Test extract_sources with files outside project directory."""
+ # Create a file outside the project structure
+ outside_file = Path(self.temp_dir.name) / "outside.xml"
+ outside_file.write_bytes(b"")
+
+ # Should not crash, but skip the file gracefully
+ # Note: extract_sources expects files in project directories,
+ # so outside files will be skipped
+ preamble, postamble = extract_sources([outside_file])
+
+ self.assertEqual(preamble, "")
+ self.assertEqual(postamble, "")
+
+ def test_transform_xml_to_tex_complex_nested_structure(self):
+ """Test transform with complex nested divs."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+
+ Section 1
+
+ Subsection
+ Nested content
+
+
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should handle nested structures
+ self.assertIn(r'\part{Section 1}', result)
+ self.assertIn('Nested content', result)
+
+ def test_transform_xml_to_tex_mixed_languages(self):
+ """Test transform with mixed English and Hebrew content."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = '''
+
+
+
+ English text עברית more English
+
+
+'''.encode('utf-8')
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should handle mixed languages
+ self.assertIn('English text', result)
+ self.assertIn(r'\texthebrew{', result)
+ self.assertIn('עברית', result)
+
+ def test_extract_sources_multiple_files_same_project(self):
+ """Test extract_sources with multiple files from same project."""
+ index_content = b'''
+
+
+
+ Book
+ Author
+
+
+'''
+
+ file1 = self._create_xml_file("project1", "doc1.xml", b"")
+ file2 = self._create_xml_file("project1", "doc2.xml", b"")
+ index_file = self._create_xml_file("project1", "index.xml", index_content)
+
+ preamble, postamble = extract_sources([file1, file2])
+
+ # Should extract from same index file (deduplicated)
+ self.assertIn(r'\begin{filecontents*}{job.bib}', preamble)
+
+ def test_transform_xml_to_tex_empty_licenses_credits(self):
+ """Test transform with no licenses or credits."""
+ from unittest.mock import patch
+ import opensiddur.exporter.tex.xelatex as xelatex_module
+
+ xml_content = b'''
+
+
+
+ Content only
+
+
+'''
+
+ input_file = self._create_xml_file("project1", "input.xml", xml_content)
+
+ with patch.object(xelatex_module, 'projects_source_root', self.test_dir):
+ result = transform_xml_to_tex(input_file)
+
+ # Should still produce valid LaTeX even without licenses/credits
+ self.assertIn(r'\documentclass{book}', result)
+ # Should not have empty metadata sections
+ # (The postamble will be empty if no licenses/credits/sources)
+
+
if __name__ == '__main__':
unittest.main()