Add support for some basic standard P5 elements

Change-Id: I70fcc2c2d6222d75c9baa59c1eb3311c171d6ed6
diff --git a/app/src/main/kotlin/de/ids_mannheim/korapxmltools/KorapXmlTool.kt b/app/src/main/kotlin/de/ids_mannheim/korapxmltools/KorapXmlTool.kt
index 066c0a8..f4c8425 100644
--- a/app/src/main/kotlin/de/ids_mannheim/korapxmltools/KorapXmlTool.kt
+++ b/app/src/main/kotlin/de/ids_mannheim/korapxmltools/KorapXmlTool.kt
@@ -2090,9 +2090,23 @@
                         val headerRoot = headerDoc.documentElement
                         headerRoot.normalize()
 
-                        val textSigle = headerRoot.firstText("textSigle")
-                        val docSigle = headerRoot.firstText("dokumentSigle")
-                        val corpusSigle = headerRoot.firstText("korpusSigle")
+                        val entryPath = headerEntry.name
+                        val pathParts = entryPath.split('/').filter { it.isNotEmpty() && !it.endsWith("header.xml") }
+
+                        var textSigle = headerRoot.firstText("textSigle")
+                        var docSigle = headerRoot.firstText("dokumentSigle")
+                        var corpusSigle = headerRoot.firstText("korpusSigle")
+
+                        if (textSigle == null && docSigle == null && corpusSigle == null) {
+                            if (pathParts.size == 1) {
+                                corpusSigle = pathParts[0]
+                            } else if (pathParts.size == 2) {
+                                docSigle = "${pathParts[0]}/${pathParts[1]}"
+                            } else if (pathParts.size >= 3) {
+                                textSigle = getTextIdFromPath(entryPath)
+                            }
+                        }
+
                         val docId = textSigle?.replace('/', '_')
 
                         // Call appropriate metadata collection function based on what the header contains
@@ -3261,9 +3275,22 @@
                 val headerRoot = headerDoc.documentElement
                 headerRoot.normalize()
 
-                val textSigle = headerRoot.firstText("textSigle")
-                val docSigle = headerRoot.firstText("dokumentSigle")
-                val corpusSigle = headerRoot.firstText("korpusSigle")
+                val entryPath = zipEntry.name
+                val pathParts = entryPath.split('/').filter { it.isNotEmpty() && !it.endsWith("header.xml") }
+
+                var textSigle = headerRoot.firstText("textSigle")
+                var docSigle = headerRoot.firstText("dokumentSigle")
+                var corpusSigle = headerRoot.firstText("korpusSigle")
+
+                if (textSigle == null && docSigle == null && corpusSigle == null) {
+                    if (pathParts.size == 1) {
+                        corpusSigle = pathParts[0]
+                    } else if (pathParts.size == 2) {
+                        docSigle = "${pathParts[0]}/${pathParts[1]}"
+                    } else if (pathParts.size >= 3) {
+                        textSigle = getTextIdFromPath(entryPath)
+                    }
+                }
 
                 val docId = textSigle?.replace('/', '_')
                 LOGGER.fine("Processing header file: " + zipEntry.name + " docId: " + docId + " corpusSigle: " + corpusSigle + " docSigle: " + docSigle)
@@ -5414,7 +5441,12 @@
         val textDesc = headerRoot.firstElement("textDesc")
         val textClassElement = headerRoot.firstElement("textClass")
 
-        metadata.putIfNotBlank("author", analytic.firstText("h.author") ?: monogr.firstText("h.author") ?: headerRoot.firstText("h.author"))
+        val author = analytic.firstText("h.author") 
+            ?: monogr.firstText("h.author") 
+            ?: headerRoot.firstText("h.author")
+            ?: headerRoot.firstText("author") { it.getAttribute("role") == "primary" }
+            ?: headerRoot.firstText("author")
+        metadata.putIfNotBlank("author", author)
 
         val mainTitle = analytic.firstText("h.title") { it.getAttribute("type") == "main" }
             ?: analytic.firstText("h.title")
@@ -5423,6 +5455,8 @@
             ?: headerRoot.firstText("d.title")
             ?: headerRoot.firstText("c.title") { it.getAttribute("type") == "main" }
             ?: headerRoot.firstText("c.title")
+            ?: headerRoot.firstText("title") { it.getAttribute("type") == "main" }
+            ?: headerRoot.firstText("title")
         metadata.putIfNotBlank("title", mainTitle)
 
         metadata.putIfNotBlank(
@@ -5430,6 +5464,7 @@
             analytic.firstText("h.title") { it.getAttribute("type") == "sub" }
                 ?: monogr.firstText("h.title") { it.getAttribute("type") == "sub" }
                 ?: headerRoot.firstText("c.title") { it.getAttribute("type") == "sub" }
+                ?: headerRoot.firstText("title") { it.getAttribute("type") == "sub" }
         )
 
         val translator = headerRoot.firstText("editor") { it.getAttribute("role") == "translator" }
@@ -5505,9 +5540,25 @@
         }
         composeKrillPubDate(year, month, day, plainPubDate)?.let { metadata["pubDate"] = it }
 
+        if (!metadata.containsKey("pubDate")) {
+            val dateEl = headerRoot.firstElement("date")
+            val dateVal = dateEl?.getAttribute("when")?.trim()?.takeIf { it.isNotEmpty() }
+                ?: dateEl?.textContent?.trim()?.takeIf { it.isNotEmpty() }
+            if (dateVal != null) {
+                metadata["pubDate"] = dateVal
+            }
+        }
+
         headerRoot.firstElement("ref") { it.getAttribute("type") == "page_url" }
             ?.getAttribute("target")?.takeIf { it.isNotBlank() }?.let { metadata["externalLink"] = it }
 
+        if (!metadata.containsKey("externalLink")) {
+            headerRoot.firstElement("link") { it.hasAttribute("target") }
+                ?.getAttribute("target")?.trim()?.takeIf { it.isNotEmpty() }?.let {
+                    metadata["externalLink"] = it
+                }
+        }
+
         val biblNoteElement = analytic.firstElement("biblNote") { it.getAttribute("n") == "url" }
             ?: monogr.firstElement("biblNote") { it.getAttribute("n") == "url" }
         biblNoteElement?.let {
diff --git a/app/src/test/kotlin/de/ids_mannheim/korapxmltools/KrillJsonGeneratorTest.kt b/app/src/test/kotlin/de/ids_mannheim/korapxmltools/KrillJsonGeneratorTest.kt
index b121a44..fb742fb 100644
--- a/app/src/test/kotlin/de/ids_mannheim/korapxmltools/KrillJsonGeneratorTest.kt
+++ b/app/src/test/kotlin/de/ids_mannheim/korapxmltools/KrillJsonGeneratorTest.kt
@@ -640,6 +640,118 @@
     }
 
     @Test
+    fun krillStandardTeiP5MetadataFallbacks() {
+        val tool = KorapXmlTool()
+
+        // 1. Check basic P5 typical structure from the example
+        val p5Metadata = collectKrillMetadata(
+            tool,
+            "SK_UL.19811",
+            headerElement(
+                """
+                <teiHeader>
+                  <fileDesc>
+                    <titleStmt>
+                      <title>Affenstern</title>
+                      <author role="primary">Udo Lindenberg</author>
+                      <author role="text">Another Author</author>
+                    </titleStmt>
+                    <publicationStmt>
+                      <publisher>Unbekannt</publisher>
+                      <date>1981</date>
+                      <ref>
+                        <name>Udopia</name>
+                      </ref>
+                    </publicationStmt>
+                    <sourceDesc>
+                      <ab>
+                        <link target="https://www.udo-lindenberg.de/affenstern.57680.htm" />
+                      </ab>
+                    </sourceDesc>
+                  </fileDesc>
+                </teiHeader>
+                """
+            )
+        )
+
+        assertEquals("Udo Lindenberg", p5Metadata["author"], "Should prefer primary role author")
+        assertEquals("Affenstern", p5Metadata["title"])
+        assertEquals("1981", p5Metadata["pubDate"])
+        assertEquals("https://www.udo-lindenberg.de/affenstern.57680.htm", p5Metadata["externalLink"])
+
+        // 2. Check <date when="..."> fallback
+        val p5MetadataDateWhen = collectKrillMetadata(
+            KorapXmlTool(),
+            "SK_UL.19812",
+            headerElement(
+                """
+                <teiHeader>
+                  <fileDesc>
+                    <publicationStmt>
+                      <date when="1982-03-04"/>
+                    </publicationStmt>
+                  </fileDesc>
+                </teiHeader>
+                """
+            )
+        )
+        assertEquals("1982-03-04", p5MetadataDateWhen["pubDate"])
+
+        // 3. Check author without role (first author)
+        val p5MetadataNoRole = collectKrillMetadata(
+            KorapXmlTool(),
+            "SK_UL.19813",
+            headerElement(
+                """
+                <teiHeader>
+                  <fileDesc>
+                    <titleStmt>
+                      <author>First Author</author>
+                      <author>Second Author</author>
+                    </titleStmt>
+                  </fileDesc>
+                </teiHeader>
+                """
+            )
+        )
+        assertEquals("First Author", p5MetadataNoRole["author"])
+    }
+
+    @Test
+    fun testSkZipExtraction() {
+        val skZip = loadResource("sk.zip").path
+        
+        val generatedTar = ensureKrillTar("sk_test", "sk.krill.tar") { outputDir ->
+            arrayOf(
+                "-t", "krill",
+                "-q",
+                "-D", outputDir.path,
+                skZip
+            )
+        }
+        assertTrue(generatedTar.exists())
+
+        val jsonByFile = readKrillJson(generatedTar)
+        assertTrue(jsonByFile.containsKey("SK-UL-19811.json"))
+        val json = jsonByFile.getValue("SK-UL-19811.json")
+
+        // Assert our fallbacks were correctly populated in the output JSON
+        assertEquals("Udo Lindenberg", krillFieldValue(json, "author"))
+        assertEquals("Affenstern", krillFieldValue(json, "title"))
+        assertEquals("1981", krillFieldValue(json, "pubDate"))
+        
+        // Assert docTitle, docAuthor, corpusTitle, and corpusAuthor are empty/null
+        kotlin.test.assertNull(krillFieldValue(json, "docTitle"))
+        kotlin.test.assertNull(krillFieldValue(json, "docAuthor"))
+        kotlin.test.assertNull(krillFieldValue(json, "corpusTitle"))
+        kotlin.test.assertNull(krillFieldValue(json, "corpusAuthor"))
+
+        // Check externalLink contains the encoded URL
+        assertTrue(json.contains("https%3A%2F%2Fwww.udo-lindenberg.de%2Faffenstern.57680.htm"), 
+            "JSON should contain the encoded url in the externalLink field")
+    }
+
+    @Test
     fun testCorrectTextCount() {
         val baseZip = loadResource("wud24_sample.zip").path
         
diff --git a/app/src/test/resources/sk.zip b/app/src/test/resources/sk.zip
new file mode 100644
index 0000000..cd51faf
--- /dev/null
+++ b/app/src/test/resources/sk.zip
Binary files differ