From 0803182d88c17c4c253ea6a97ac7313fc85a2a67 Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Fri, 16 May 2025 23:08:28 +0200 Subject: [PATCH 1/5] Fixes #8 Starts fixing #8. Removes required dependency to JSoup for users of the library. --- README.md | 3 ++ .../kotlin/fr/lengrand/opengraphkt/Main.kt | 11 ++---- .../src/main/resources/example.html | 0 .../lengrand/opengraphkt/DocumentFetcher.kt | 29 --------------- .../lengrand/opengraphkt/OpenGraphParser.kt | 37 +++++++++++++++++++ .../opengraphkt/OpenGraphParserTest.kt | 16 +++----- 6 files changed, 49 insertions(+), 47 deletions(-) rename {opengraphkt => demo}/src/main/resources/example.html (100%) delete mode 100644 opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/DocumentFetcher.kt diff --git a/README.md b/README.md index 0230aed..ea90f46 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # OpenGraphKt +![Maven Central Version](https://img.shields.io/maven-central/v/fr.lengrand/opengraphkt) + + [OpenGraphKt](https://github.com/jlengrand/OpenGraphKt) is a minimalist Kotlin library to work with the [Open Graph tags](https://ogp.me/) protocol. OpenGraphKt is a tiny wrapper on top of JSoup. diff --git a/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt b/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt index bf6f773..8b37c9c 100644 --- a/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt +++ b/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt @@ -1,19 +1,18 @@ package fr.lengrand.opengraphkt import java.io.File +import java.net.URI /** * Example demonstrating how to use the OpenGraphParser to extract Open Graph data from HTML. */ fun main() { val parser = OpenGraphParser() - val fetcher = DocumentFetcher() // Example 1: Parse Open Graph data from a URL println("Example 1: Parsing from URL") try { - val document = fetcher.fromUrl("https://www.imdb.com/title/tt0068646/") - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(URI("https://www.imdb.com/title/tt0068646/").toURL()) println("Title: ${openGraphData.title}") println("Is valid: ${openGraphData.isValid()}") @@ -28,8 +27,7 @@ fun main() { val resourceFile = File(resourceUrl.toURI()) // Parse the file - val document = fetcher.fromFile(resourceFile) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(resourceFile) println("Title: ${openGraphData.title}") println("Is valid: ${openGraphData.isValid()}") @@ -59,8 +57,7 @@ fun main() { """.trimIndent() - val document = fetcher.fromString(html) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(html) println("Title: ${openGraphData.title}") println("Is valid: ${openGraphData.isValid()}") diff --git a/opengraphkt/src/main/resources/example.html b/demo/src/main/resources/example.html similarity index 100% rename from opengraphkt/src/main/resources/example.html rename to demo/src/main/resources/example.html diff --git a/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/DocumentFetcher.kt b/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/DocumentFetcher.kt deleted file mode 100644 index e7e78ec..0000000 --- a/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/DocumentFetcher.kt +++ /dev/null @@ -1,29 +0,0 @@ -package fr.lengrand.opengraphkt - -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import java.io.File - -/** - * DocumentFetcher's job is to take any type of input and transform it into a JSoup document for the Parser to then do its job - */ -class DocumentFetcher { - - fun fromUrl(url: String): Document { - return Jsoup.connect(url).get() - } - - fun fromString(html: String): Document { - return Jsoup.parse(html) - } - - /** - * Parses HTML from a file and returns a JSoup Document - * @param file The file to parse - * @param charsetName The charset to use for parsing (default is UTF-8) - * @return A JSoup Document representing the parsed HTML - */ - fun fromFile(file: File, charsetName: String = "UTF-8") : Document { - return Jsoup.parse(file, charsetName) - } -} diff --git a/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/OpenGraphParser.kt b/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/OpenGraphParser.kt index 776d1a7..12a8409 100644 --- a/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/OpenGraphParser.kt +++ b/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/OpenGraphParser.kt @@ -1,7 +1,10 @@ package fr.lengrand.opengraphkt +import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.select.Elements +import java.io.File +import java.net.URL data class OpenGraphTag( val property: String, @@ -149,6 +152,40 @@ class OpenGraphParser { return buildOpenGraphData(openGraphTags) } + /** + * Extracts all Open Graph tags from a URL and returns a structured OpenGraphData object. + * + * @param url The URL to be parsed for Open Graph information. + * @return An OpenGraphData object containing all extracted Open Graph data. + */ + fun parse(url: URL) : OpenGraphData { + val doc = Jsoup.connect(url.toString()).get() + return parse(doc) + } + + /** + * Extracts all Open Graph tags from a raw HTML String and returns a structured OpenGraphData object. + * + * @param html The raw HTML String to be parsed for Open Graph information. + * @return An OpenGraphData object containing all extracted Open Graph data. + */ + fun parse(html: String) : OpenGraphData { + val doc = Jsoup.parse(html) + return parse(doc) + } + + /** + * Extracts all Open Graph tags from a raw HTML String and returns a structured OpenGraphData object. + * + * @param file The file to parse + * @param charset The charset to use for parsing (default is UTF-8) + * @return An OpenGraphData object containing all extracted Open Graph data. + */ + fun parse(file: File, charset: String = "UTF-8") : OpenGraphData { + val doc = Jsoup.parse(file, charset) + return parse(doc) + } + /** * Extracts Open Graph tags from JSoup Elements and converts them to OpenGraphTag objects. * diff --git a/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt b/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt index c1ee33e..34ddd3b 100644 --- a/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt +++ b/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt @@ -8,7 +8,6 @@ import kotlin.test.assertTrue class OpenGraphParserTest { private val parser = OpenGraphParser() - private val fetcher = DocumentFetcher() // Sample HTML with all required OpenGraph tags and some structured properties private val completeHtml = """ @@ -139,8 +138,7 @@ class OpenGraphParserTest { @Test fun `test parse with complete OpenGraph tags`() { - val document = fetcher.fromString(completeHtml) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(completeHtml) // Verify that all required properties are extracted correctly assertEquals("The Rock", openGraphData.title) @@ -184,8 +182,7 @@ class OpenGraphParserTest { @Test fun `test parse with article-specific tags`() { - val document = fetcher.fromString(articleHtml) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(articleHtml) // Verify basic properties assertEquals("Breaking News", openGraphData.title) @@ -208,8 +205,7 @@ class OpenGraphParserTest { @Test fun `test parse with profile-specific tags`() { - val document = fetcher.fromString(profileHtml) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(profileHtml) // Verify basic properties assertEquals("John Doe", openGraphData.title) @@ -227,8 +223,7 @@ class OpenGraphParserTest { @Test fun `test parse with book-specific tags`() { - val document = fetcher.fromString(bookHtml) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(bookHtml) // Verify basic properties assertEquals("The Great Novel", openGraphData.title) @@ -249,8 +244,7 @@ class OpenGraphParserTest { @Test fun `test parse with multiple images`() { - val document = fetcher.fromString(multipleImagesHtml) - val openGraphData = parser.parse(document) + val openGraphData = parser.parse(multipleImagesHtml) // Verify basic properties assertEquals("Photo Gallery", openGraphData.title) From e88b2ca5c41ae4da1c841da4dd75367d985d1568 Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Fri, 16 May 2025 23:11:32 +0200 Subject: [PATCH 2/5] Fixes Main file typo and adds new use case --- demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt b/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt index 8b37c9c..84d4109 100644 --- a/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt +++ b/demo/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt @@ -1,5 +1,6 @@ package fr.lengrand.opengraphkt +import org.jsoup.Jsoup import java.io.File import java.net.URI @@ -36,7 +37,7 @@ fun main() { } // Example 3: Parse Open Graph data from an HTML string - println("\nExample 2: Parsing from HTML string") + println("\nExample 3: Parsing from HTML string") val html = """ @@ -61,4 +62,13 @@ fun main() { println("Title: ${openGraphData.title}") println("Is valid: ${openGraphData.isValid()}") + + // Example 4: Parse Open Graph data from a Jsoup Document + println("\nExample 4: Parsing from JSoup Document") + + val doc = Jsoup.parse(html) + val openGraphDataDoc = parser.parse(doc) + + println("Title: ${openGraphDataDoc.title}") + println("Is valid: ${openGraphDataDoc.isValid()}") } From ea62c616fb5843bb9175558e3e99bfc72676c0f0 Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Fri, 16 May 2025 23:32:42 +0200 Subject: [PATCH 3/5] Adds moule to remote check library functionality --- .idea/gradle.xml | 1 + demo-remote/build.gradle.kts | 28 ++++++++++++++ .../kotlin/fr/lengrand/opengraphkt/Main.kt | 38 +++++++++++++++++++ demo/build.gradle.kts | 17 +-------- opengraphkt/build.gradle.kts | 2 +- settings.gradle.kts | 8 +++- 6 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 demo-remote/build.gradle.kts create mode 100644 demo-remote/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 8012ec0..a537b7e 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -10,6 +10,7 @@ diff --git a/demo-remote/build.gradle.kts b/demo-remote/build.gradle.kts new file mode 100644 index 0000000..9b66ba2 --- /dev/null +++ b/demo-remote/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + kotlin("jvm") + application +} + +group = "fr.lengrand" + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.jsoup:jsoup:1.20.1") + implementation("fr.lengrand:opengraphkt:0.0.1") + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(23) +} + +application { + mainClass = "fr.lengrand.opengraphkt.MainKt" +} diff --git a/demo-remote/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt b/demo-remote/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt new file mode 100644 index 0000000..fdb0624 --- /dev/null +++ b/demo-remote/src/main/kotlin/fr/lengrand/opengraphkt/Main.kt @@ -0,0 +1,38 @@ +package fr.lengrand.opengraphkt + +import org.jsoup.Jsoup + +/** + * This module is only here to verify that the latest Maven Central release can be imported and used as intended. + */ +fun main() { + val parser = OpenGraphParser() + + val html = """ + + + + Open Graph Example + + + + + + + + + + +

Example Page

+ + + """.trimIndent() + + println("Parsing from JSoup Document") + + val doc = Jsoup.parse(html) + val openGraphDataDoc = parser.parse(doc) + + println("Title: ${openGraphDataDoc.title}") + println("Is valid: ${openGraphDataDoc.isValid()}") +} diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 60bfb31..a5b42ca 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -1,12 +1,11 @@ import org.gradle.kotlin.dsl.implementation plugins { - kotlin("jvm") version "2.1.21" + kotlin("jvm") application } -group = "nl.lengrand" -version = "0.1-SNAPSHOT" +group = "fr.lengrand" repositories { mavenCentral() @@ -18,22 +17,10 @@ dependencies { testImplementation(kotlin("test")) } -java { - withSourcesJar() -} - tasks.test { useJUnitPlatform() } -tasks.jar { - manifest { - attributes(mapOf("Implementation-Title" to project.name, - "Implementation-Version" to project.version)) - } -} - - kotlin { jvmToolchain(23) } diff --git a/opengraphkt/build.gradle.kts b/opengraphkt/build.gradle.kts index 337ee2f..513f22e 100644 --- a/opengraphkt/build.gradle.kts +++ b/opengraphkt/build.gradle.kts @@ -1,7 +1,7 @@ import com.vanniktech.maven.publish.SonatypeHost plugins { - kotlin("jvm") version "2.1.21" + kotlin("jvm") id("com.vanniktech.maven.publish") version "0.30.0" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5867f24..9c6f925 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,12 @@ +pluginManagement { + plugins { + kotlin("jvm") version "2.1.21" + } +} plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } rootProject.name = "OpenGraphKt" include("opengraphkt") -include("demo") \ No newline at end of file +include("demo") +include("demo-remote") \ No newline at end of file From cd4462ff59120e38fced28bef5db8f89cdbcb9b9 Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Fri, 16 May 2025 23:44:16 +0200 Subject: [PATCH 4/5] Publish and Release all together --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b487ce2..91a3e02 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: distribution: 'zulu' java-version: 21 - name: Publish to MavenCentral - run: ./gradlew publishToMavenCentral --no-configuration-cache + run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} From 8028f4776124fa506d8bb36850844b724c2366fa Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Fri, 16 May 2025 23:55:44 +0200 Subject: [PATCH 5/5] Removing unnecessary checks --- .../opengraphkt/OpenGraphParserTest.kt | 84 ++++++++++++------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt b/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt index 34ddd3b..b66c726 100644 --- a/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt +++ b/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/OpenGraphParserTest.kt @@ -1,6 +1,8 @@ package fr.lengrand.opengraphkt import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -144,7 +146,7 @@ class OpenGraphParserTest { assertEquals("The Rock", openGraphData.title) assertEquals("video.movie", openGraphData.type) assertEquals("https://example.com/the-rock", openGraphData.url) - + // Verify that the OpenGraphData object is valid assertTrue(openGraphData.isValid()) @@ -189,18 +191,18 @@ class OpenGraphParserTest { assertEquals("article", openGraphData.type) assertEquals("https://example.com/news/breaking", openGraphData.url) assertEquals("Latest breaking news", openGraphData.description) - + // Verify article-specific properties assertNotNull(openGraphData.article) - assertEquals("2023-01-01T00:00:00Z", openGraphData.article?.publishedTime) - assertEquals("2023-01-02T12:00:00Z", openGraphData.article?.modifiedTime) - assertEquals("News", openGraphData.article?.section) - assertEquals(2, openGraphData.article?.authors?.size) - assertTrue(openGraphData.article?.authors?.contains("John Doe") ?: false) - assertTrue(openGraphData.article?.authors?.contains("Jane Smith") ?: false) - assertEquals(2, openGraphData.article?.tags?.size) - assertTrue(openGraphData.article?.tags?.contains("breaking") ?: false) - assertTrue(openGraphData.article?.tags?.contains("news") ?: false) + assertEquals("2023-01-01T00:00:00Z", openGraphData.article.publishedTime) + assertEquals("2023-01-02T12:00:00Z", openGraphData.article.modifiedTime) + assertEquals("News", openGraphData.article.section) + assertEquals(2, openGraphData.article.authors.size) + assertTrue(openGraphData.article.authors.contains("John Doe")) + assertTrue(openGraphData.article.authors.contains("Jane Smith")) + assertEquals(2, openGraphData.article.tags.size) + assertTrue(openGraphData.article.tags.contains("breaking")) + assertTrue(openGraphData.article.tags.contains("news")) } @Test @@ -212,13 +214,13 @@ class OpenGraphParserTest { assertEquals("profile", openGraphData.type) assertEquals("https://example.com/profile/johndoe", openGraphData.url) assertEquals("John Doe's profile", openGraphData.description) - + // Verify profile-specific properties assertNotNull(openGraphData.profile) - assertEquals("John", openGraphData.profile?.firstName) - assertEquals("Doe", openGraphData.profile?.lastName) - assertEquals("johndoe", openGraphData.profile?.username) - assertEquals("male", openGraphData.profile?.gender) + assertEquals("John", openGraphData.profile.firstName) + assertEquals("Doe", openGraphData.profile.lastName) + assertEquals("johndoe", openGraphData.profile.username) + assertEquals("male", openGraphData.profile.gender) } @Test @@ -230,16 +232,16 @@ class OpenGraphParserTest { assertEquals("book", openGraphData.type) assertEquals("https://example.com/books/great-novel", openGraphData.url) assertEquals("A great novel", openGraphData.description) - + // Verify book-specific properties assertNotNull(openGraphData.book) - assertEquals(1, openGraphData.book?.authors?.size) - assertEquals("Famous Author", openGraphData.book?.authors?.get(0)) - assertEquals("1234567890123", openGraphData.book?.isbn) - assertEquals("2023-01-01", openGraphData.book?.releaseDate) - assertEquals(2, openGraphData.book?.tags?.size) - assertTrue(openGraphData.book?.tags?.contains("fiction") ?: false) - assertTrue(openGraphData.book?.tags?.contains("novel") ?: false) + assertEquals(1, openGraphData.book.authors.size) + assertEquals("Famous Author", openGraphData.book.authors.get(0)) + assertEquals("1234567890123", openGraphData.book.isbn) + assertEquals("2023-01-01", openGraphData.book.releaseDate) + assertEquals(2, openGraphData.book.tags.size) + assertTrue(openGraphData.book.tags.contains("fiction")) + assertTrue(openGraphData.book.tags.contains("novel")) } @Test @@ -251,23 +253,47 @@ class OpenGraphParserTest { assertEquals("website", openGraphData.type) assertEquals("https://example.com/gallery", openGraphData.url) assertEquals("A gallery of images", openGraphData.description) - + // Verify multiple images assertEquals(3, openGraphData.images.size) - + // First image assertEquals("https://example.com/image1.jpg", openGraphData.images[0].url) assertEquals(800, openGraphData.images[0].width) assertEquals(600, openGraphData.images[0].height) - + // Second image assertEquals("https://example.com/image2.jpg", openGraphData.images[1].url) assertEquals(1024, openGraphData.images[1].width) assertEquals(768, openGraphData.images[1].height) - + // Third image assertEquals("https://example.com/image3.jpg", openGraphData.images[2].url) assertEquals(1200, openGraphData.images[2].width) assertEquals(900, openGraphData.images[2].height) } -} \ No newline at end of file + + @Test + fun `test parse with File`(@TempDir tempDir: File) { + // Create a temporary HTML file + val htmlFile = File(tempDir, "test.html") + htmlFile.writeText(articleHtml) + + val openGraphData = parser.parse(htmlFile) + + // Verify basic properties + assertEquals("Breaking News", openGraphData.title) + assertEquals("article", openGraphData.type) + assertEquals("https://example.com/news/breaking", openGraphData.url) + assertEquals("Latest breaking news", openGraphData.description) + + // Verify article-specific properties + assertNotNull(openGraphData.article) + assertEquals("2023-01-01T00:00:00Z", openGraphData.article.publishedTime) + assertEquals("2023-01-02T12:00:00Z", openGraphData.article.modifiedTime) + assertEquals("News", openGraphData.article.section) + assertEquals(2, openGraphData.article.authors.size) + assertTrue(openGraphData.article.authors.contains("John Doe")) + assertTrue(openGraphData.article.authors.contains("Jane Smith")) + } +}