core/src/iosMain/kotlin/licenseplist/LicensePlistParser.kt
changeset 25 a645b4ebb054
child 34 ce299aacc068
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/iosMain/kotlin/licenseplist/LicensePlistParser.kt	Wed Mar 12 20:17:28 2025 -0400
@@ -0,0 +1,107 @@
+/*
+ * AboutOss is an utility library to retrieve and display
+ * opensource licenses in Android applications.
+ *
+ * Copyright (C) 2023 by Frederic-Charles Barthelery.
+ *
+ * This file is part of AboutOss.
+ *
+ * AboutOss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AboutOss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AboutOss.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.core.licenseplist
+
+import kotlinx.cinterop.BetaInteropApi
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlinx.cinterop.ObjCObjectVar
+import kotlinx.cinterop.alloc
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.ptr
+import kotlinx.cinterop.value
+import platform.Foundation.NSBundle
+import platform.Foundation.NSData
+import platform.Foundation.NSError
+import platform.Foundation.NSPropertyListMutableContainers
+import platform.Foundation.NSPropertyListSerialization
+import platform.Foundation.NSURL
+import platform.Foundation.dataWithContentsOfURL
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+class LicensePlistParser {
+
+    @Suppress("UNCHECKED_CAST")
+    suspend fun parseLicenses(
+        licensePlistInput: NSURL
+    ): Map<String, String> {
+        val fileContent = getContent(licensePlistInput)
+        val plist = fileContent.toPropertyList() as Map<String, List<Map<String, String>>>
+        val paneLists = plist["PreferenceSpecifiers"]!!
+            .filter {
+                it["Type"] == "PSChildPaneSpecifier"
+            }
+        val directoryUrl = licensePlistInput.URLByDeletingLastPathComponent()!!
+        return paneLists.associate { pane ->
+            val paneFile = pane["File"]!!
+            val libraryName = pane["Title"]!!
+            val paneUrl = buildPaneUrl(directoryUrl, paneFile)
+            val paneFileContent =  getContent(paneUrl)
+            val paneFilePlist = paneFileContent.toPropertyList() as Map<String, List<Map<String, String>>>
+            val license = getLicenseFromPaneFilePlist(paneFilePlist)
+            libraryName to license
+        }
+    }
+
+    private fun buildPaneUrl(directoryUrl: NSURL, paneName: String) = directoryUrl.URLByAppendingPathComponent(paneName)!!.URLByAppendingPathExtension("plist")!!
+
+    private fun getLicenseFromPaneFilePlist(paneFilePList: Map<String, List<Map<String, String>>>): String {
+        val specifiers = paneFilePList["PreferenceSpecifiers"]!!
+        val licenses = specifiers.map {
+            it["FooterText"]!!
+        }
+        return licenses.joinToString("\n\n")
+    }
+
+    private fun getContent(url: NSURL): NSData {
+        return checkNotNull(NSData.dataWithContentsOfURL(url))
+    }
+
+    @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
+    private suspend fun NSData.toPropertyList(): Any? = suspendCoroutine { cont ->
+        val parsed = memScoped {
+            val error: ObjCObjectVar<NSError?> = alloc()
+            val result = NSPropertyListSerialization.propertyListWithData(this@toPropertyList,
+                options = NSPropertyListMutableContainers,
+                format = null,
+                error.ptr
+            )
+            if (error.value != null) {
+                cont.resumeWithException(Exception(error.value!!.description))
+            }
+            result
+        }
+        cont.resume(parsed!!)
+    }
+
+    companion object {
+        fun getDefaultLicensePlistUrl(): NSURL {
+            val path = NSBundle.mainBundle.pathForResource(
+                "com.mono0926.LicensePlist",
+                ofType = "plist",
+                inDirectory = "licenseplist"
+            )
+            return NSURL.fileURLWithPath(path!!)
+        }
+    }
+}
\ No newline at end of file