Compare commits

...

19 Commits

Author SHA1 Message Date
renovate[bot]
bc66aaf4ce Update dependency gradle to v9 2026-03-04 14:57:03 +00:00
Julien Lengrand-Lambert
89dbda55b8 Moves up to next version, and fixes demo-remote 2025-10-26 00:00:32 +01:00
julien Lengrand-Lambert
d982bc94cb Adding retro Java 17 compatibility
* Moving back to Java 17 compatibility. I'll change demo-remote a little later, because otherwise I have to break the project for some time.

* Prepares version 0.1.3
2025-10-26 00:43:04 +02:00
Julien Lengrand-Lambert
f829d56a43 Update publishing plugin to 0.34 2025-10-19 03:21:52 +02:00
renovate[bot]
0364ab5d0c Update plugin org.jetbrains.kotlinx.kover to v0.9.3 (#44)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 03:18:31 +02:00
Julien Lengrand-Lambert
389f16fe1c Update contributing file 2025-10-19 03:17:21 +02:00
Julien Lengrand-Lambert
f2cbbe048d First version of contributing.md 2025-10-19 03:13:08 +02:00
Julien Lengrand-Lambert
4333d077c3 Updates remote demo 2025-10-19 03:09:01 +02:00
Julien Lengrand-Lambert
20dfc326c7 Prepares 0.1.3 2025-10-19 02:59:19 +02:00
Julien Lengrand-Lambert
3ec5410f94 Creates 0.1.2 2025-10-19 02:51:32 +02:00
julien Lengrand-Lambert
12de34aa60 Feat/updates 10 25 (#42)
* Update dependency com.fleeksoft.ksoup:ksoup-network to v0.2.5 (#40)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update whole ksoup to 0.2.5

* Update dependency gradle to v8.14.3 (#37)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency org.junit:junit-bom to v5.14.0 (#36)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency gradle to v8.14.3 (#43)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update plugin org.jetbrains.kotlin.jvm to v2.2.20 (#35)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update ktor from scraper

* Update settings

* Update settings

* Update gradle/actions action to v4.4.4 (#31)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Adds claude init

* Upgrades to Java 24

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 02:48:47 +02:00
Julien Lengrand-Lambert
e38c968151 Next version tagging 2025-06-09 09:13:53 +02:00
Julien Lengrand-Lambert
e2817cb15c Prepares release 2025-06-09 09:03:29 +02:00
julien Lengrand-Lambert
5f390de944 Generates HTML from Object (#30)
* Adds Generator
* Corrects README
2025-06-09 09:02:27 +02:00
Julien Lengrand-Lambert
58abce8cb1 Updates version 2025-06-08 19:49:30 +02:00
Julien Lengrand-Lambert
91da68172f Lining up the version 2025-06-08 14:23:58 +02:00
renovate[bot]
d7cef1714e Update dependency gradle to v8.14.2 (#28)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 14:17:12 +02:00
renovate[bot]
9d94d22a5e Update dependency org.junit:junit-bom to v5.13.1 (#25)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 14:17:02 +02:00
julien Lengrand-Lambert
3c0eed60a7 Makes library multiplatform (#27)
* Replaces JSoup by Ksoup
2025-06-08 14:16:48 +02:00
21 changed files with 2052 additions and 53 deletions

View File

@@ -34,7 +34,7 @@ jobs:
# Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies.
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
- name: Setup Gradle
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
- name: Build with Gradle Wrapper
run: ./gradlew build
@@ -57,7 +57,7 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
- name: Build with Gradle Wrapper
run: ./gradlew koverXmlReport
@@ -88,5 +88,5 @@ jobs:
# Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
# See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
uses: gradle/actions/dependency-submission@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

2
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.21" />
<option name="version" value="2.2.20" />
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

74
CLAUDE.md Normal file
View File

@@ -0,0 +1,74 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
OpenGraphKt is a minimalist Kotlin multiplatform library for parsing Open Graph protocol tags from HTML. It wraps Ksoup (a Kotlin port of JSoup) to extract and structure Open Graph metadata.
**Current Status**: Pre-alpha - Protocol implementation is complete for `og:` tags, but type system needs refinement.
## Project Structure
This is a multi-module Gradle project:
- `opengraphkt/` - Core library module (published to Maven Central as `fr.lengrand:opengraphkt`)
- `demo/` - Local file parsing examples
- `demo-remote/` - Remote URL parsing examples (see Main.kt for usage)
- `scrape-test/` - Testing/scraping utilities
## Common Commands
### Build and Test
```bash
./gradlew build # Build all modules
./gradlew test # Run all tests
./gradlew :opengraphkt:test # Run tests for core library only
```
### Code Coverage
```bash
./gradlew koverXmlReport # Generate XML coverage report
./gradlew koverVerify # Verify coverage meets 70% minimum threshold
```
### Publishing
```bash
./gradlew publishToMavenLocal # Publish to local Maven repo for testing
```
## Architecture
### Core Components
**Parser (`Parser.kt`)**: Main entry point that accepts multiple input types:
- `parse(url: URL)` - Fetches and parses remote HTML
- `parse(html: String)` - Parses raw HTML string
- `parse(file: File)` - Parses local HTML file
- `parse(document: Document)` - Parses Ksoup Document
The parser extracts `meta[property^=og:]` tags and builds structured data models.
**Data Models (`Models.kt`)**: Type-safe representations of Open Graph data:
- `Data` - Main container with `isValid()` method checking required fields (title, type, image, url)
- Base types: `Image`, `Video`, `Audio`
- Content-specific types: `Article`, `Book`, `Profile`
- Music types: `MusicSong`, `MusicAlbum`, `MusicPlaylist`, `MusicRadioStation`
- Video types: `VideoMovie`, `VideoEpisode`
### Key Implementation Details
**Tag Grouping**: Tags are grouped by namespace (prefix before first colon) to handle structured properties like `og:image:width`, `og:image:height` that belong to the preceding `og:image` tag.
**Date Handling**: ISO 8601 datetime parsing with fallback for date-only formats (appends `T00:00:00Z`).
**Structured Property Association**: Images/Videos/Audio with their metadata (width, height, type, etc.) are associated by parsing sequential tags - each base tag (`og:image`) is paired with following attribute tags (`og:image:width`) until the next base tag.
## Development Notes
- **JVM Toolchain**: Java 17 (see `jvmToolchain(17)` in build files)
- **Minimum Java Version**: Java 17 (required by Ksoup dependency)
- **Testing**: CI matrix tests on Java 17 and 23 via GitHub Actions
- **Dependencies**: Core library uses Ksoup (v0.2.5) for HTML parsing and network requests
- **Maven Coordinates**: Group `fr.lengrand`, artifact `opengraphkt`, currently at `0.1.2-SNAPSHOT`
- **Code Coverage**: Kover plugin enforces 70% minimum coverage threshold

158
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,158 @@
# Contributing to OpenGraphKt
Thank you for your interest in contributing to OpenGraphKt! This document provides guidelines and instructions for contributing to the project.
## Development Setup
### Prerequisites
- Java 17 or later (JVM toolchain configured for Java 17)
- Gradle 8.14.3+ (wrapper included)
- Git
### Getting Started
1. Clone the repository:
```bash
git clone https://github.com/jlengrand/OpenGraphKt.git
cd OpenGraphKt
```
2. Build the project:
```bash
./gradlew build
```
3. Run tests:
```bash
./gradlew test
```
4. Check code coverage:
```bash
./gradlew koverXmlReport
./gradlew koverVerify # Enforces 70% minimum coverage
```
## Project Structure
- `opengraphkt/` - Core library module (published to Maven Central)
- `demo/` - Local file parsing examples
- `demo-remote/` - Remote URL parsing examples using published artifact
- `scrape-test/` - Testing/scraping utilities
## Making Changes
### Code Style
- Follow Kotlin coding conventions
- Use meaningful variable and function names
- Add KDoc comments for public APIs
### Testing
- Write tests for all new functionality
- Maintain minimum 70% code coverage (enforced by Kover)
- Run tests locally before submitting PR: `./gradlew test`
### Commits
- Write clear, concise commit messages
- Reference issue numbers when applicable
- Keep commits focused and atomic
## Submitting Changes
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/your-feature-name`
3. Make your changes
4. Run tests and ensure coverage: `./gradlew test koverVerify`
5. Commit your changes
6. Push to your fork
7. Submit a Pull Request
## Publishing New Versions
This section is for maintainers only.
### Version Numbering
OpenGraphKt follows [Semantic Versioning](https://semver.org/):
- **MAJOR** version for incompatible API changes
- **MINOR** version for backwards-compatible functionality additions
- **PATCH** version for backwards-compatible bug fixes
### Pre-Release Checklist
1. **Update version number** in `opengraphkt/build.gradle.kts`:
```kotlin
version = "0.1.3-SNAPSHOT" // Change to "0.1.3" for release
```
2. **Update demo-remote dependency** in `demo-remote/build.gradle.kts`:
```kotlin
dependencies {
implementation("fr.lengrand:opengraphkt:0.1.3") // Match release version
}
```
3. **Run full test suite**:
```bash
./gradlew clean build test koverVerify
```
4. **Test demo applications**:
```bash
./gradlew :demo:run
./gradlew :demo-remote:run
```
### Publishing to Maven Central
The project uses the `com.vanniktech.maven.publish` plugin for publishing. Publishing is automated through GitHub Actions.
1. **Bump version ** in `opengraphkt/build.gradle.kts`:
```kotlin
version = "0.1.4"
```
2. **Publish to local Maven for testing** (optional):
```bash
./gradlew publishToMavenLocal
```
3. **Create GitHub release** (this triggers the publishing workflow):
- Go to GitHub repository → Releases → "Draft a new release"
- Click "Choose a tag" and create a new tag (e.g., `v0.1.3`)
- Set the release title (e.g., `v0.1.3`)
- Add release notes describing changes
- Click "Publish release"
4. **GitHub Actions will automatically**:
- Build the project
- Run tests
- Publish to Maven Central
- The workflow is triggered automatically when you create a new release
5. After a few minutes, you will see the new version in [Maven Central](https://mvnrepository.com/artifact/fr.lengrand/opengraphkt). You can also directly check the real-time status on [Central Sonartype](https://central.sonatype.com/publishing/deployments).
### Post-Release Steps
1. **Bump version to next SNAPSHOT** in `opengraphkt/build.gradle.kts`:
```kotlin
version = "0.1.5-SNAPSHOT"
```
2. **Commit version bump**:
```bash
git commit -am "Bump version to 0.1.5-SNAPSHOT"
git push
```
## Questions?
If you have questions or need help, please:
- Open an issue on GitHub
- Check existing issues and discussions
Thank you for contributing to OpenGraphKt!

View File

@@ -9,13 +9,12 @@
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/jlengrand/OpenGraphKt)
[OpenGraphKt](https://github.com/jlengrand/OpenGraphKt) is a minimalist Kotlin library to work with the [Open Graph tags](https://ogp.me/) protocol.
[OpenGraphKt](https://github.com/jlengrand/OpenGraphKt) is a minimalist Kotlin multiplatform library to work with the [Open Graph tags](https://ogp.me/) protocol.
OpenGraphKt is a tiny wrapper on top of JSoup.
## Current status
* Library can extract OpenGraph tags from HTML via a `URL`, `String` or `File` input.
* Current implementation is JVM only, due to the `JSoup` dependency.
* Protocol implementation is complete for `og:` tags, but types aren't fully correct (most types currently are `String`).
* Library should be considered in pre-alpha, use this in production at your own risks :).
@@ -28,7 +27,7 @@ In short :
* Add dependency to your Maven / Gradle file. For example :
```bash
implementation("fr.lengrand:opengraphkt:0.0.2")
implementation("fr.lengrand:opengraphkt:0.1.0")
```
* Enjoy:
@@ -44,11 +43,6 @@ println("Is valid: ${openGraphDataDoc.isValid()}")
// Is valid: true
```
## Dependencies
- [JSoup](https://jsoup.org/)
## Author
* [Julien Lengrand-Lambert](https://github.com/jlengrand)

View File

@@ -1,3 +1,3 @@
plugins {
kotlin("jvm") version "2.1.21" apply false
kotlin("jvm") version "2.2.20" apply false
}

View File

@@ -10,7 +10,7 @@ repositories {
}
dependencies {
implementation("fr.lengrand:opengraphkt:0.0.2")
implementation("fr.lengrand:opengraphkt:0.1.3")
testImplementation(kotlin("test"))
}
@@ -19,9 +19,9 @@ tasks.test {
}
kotlin {
jvmToolchain(23)
jvmToolchain(17)
}
application {
mainClass = "fr.lengrand.opengraphkt.MainKt"
mainClass = "fr.lengrand.opengraphktremote.MainKt"
}

View File

@@ -12,7 +12,9 @@ repositories {
}
dependencies {
implementation("org.jsoup:jsoup:1.20.1")
implementation("com.fleeksoft.ksoup:ksoup:0.2.5")
implementation("com.fleeksoft.ksoup:ksoup-kotlinx:0.2.5")
implementation("com.fleeksoft.ksoup:ksoup-network:0.2.5")
implementation(project(":opengraphkt"))
testImplementation(kotlin("test"))
}
@@ -22,7 +24,7 @@ tasks.test {
}
kotlin {
jvmToolchain(23)
jvmToolchain(17)
}
application {

View File

@@ -1,6 +1,6 @@
package fr.lengrand.opengraphkt
import org.jsoup.Jsoup
import com.fleeksoft.ksoup.Ksoup
import java.io.File
import java.net.URI
@@ -25,7 +25,7 @@ fun main() {
println("\nExample 2: Parsing from File")
try {
val resourceUrl = object {}.javaClass.getResource("/example.html")
val resourceFile = File(resourceUrl.toURI())
val resourceFile = File(resourceUrl!!.toURI())
// Parse the file
val openGraphData = parser.parse(resourceFile)
@@ -66,7 +66,7 @@ fun main() {
// Example 4: Parse Open Graph data from a Jsoup Document
println("\nExample 4: Parsing from JSoup Document")
val doc = Jsoup.parse(html)
val doc = Ksoup.parse(html)
val openGraphDataDoc = parser.parse(doc)
println("Title: ${openGraphDataDoc.title}")

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

7
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"

3
gradlew.bat vendored
View File

@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,20 +1,20 @@
import com.vanniktech.maven.publish.SonatypeHost
plugins {
kotlin("jvm")
id("com.vanniktech.maven.publish") version "0.32.0"
id("org.jetbrains.kotlinx.kover") version "0.9.1"
id("com.vanniktech.maven.publish") version "0.34.0"
id("org.jetbrains.kotlinx.kover") version "0.9.3"
}
group = "fr.lengrand"
version = "0.0.3-SNAPSHOT"
version = "0.1.4-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jsoup:jsoup:1.20.1")
implementation("com.fleeksoft.ksoup:ksoup:0.2.5")
implementation("com.fleeksoft.ksoup:ksoup-kotlinx:0.2.5")
implementation("com.fleeksoft.ksoup:ksoup-network:0.2.5")
testImplementation(kotlin("test"))
}
@@ -33,12 +33,20 @@ tasks.jar {
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(23)
jvmToolchain(17)
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
publishToMavenCentral()
signAllPublications()

View File

@@ -0,0 +1,337 @@
package fr.lengrand.opengraphkt
import java.time.OffsetDateTime
/**
* A generator for Open Graph protocol HTML meta tags.
*
* This class converts an OpenGraph Data object into HTML meta tags according to the Open Graph protocol specification.
* It can be used to generate the appropriate meta tags for embedding in HTML documents.
*
* @see <a href="https://ogp.me/">Open Graph Protocol</a>
*/
class Generator {
/**
* Generates HTML meta tags from an OpenGraph Data object.
*
* @param data The OpenGraph Data object to convert to HTML meta tags
* @return A string containing the HTML meta tags
*/
fun generate(data: Data): String {
val tags = mutableListOf<String>()
// Add basic metadata tags
addBasicMetaTags(data, tags)
// Add image tags
addImageTags(data.images, tags)
// Add video tags
addVideoTags(data.videos, tags)
// Add audio tags
addAudioTags(data.audios, tags)
// Add type-specific tags
when (data.getType()) {
Type.ARTICLE -> addArticleTags(data.article, tags)
Type.PROFILE -> addProfileTags(data.profile, tags)
Type.BOOK -> addBookTags(data.book, tags)
Type.MUSIC_SONG -> addMusicSongTags(data.musicSong, tags)
Type.MUSIC_ALBUM -> addMusicAlbumTags(data.musicAlbum, tags)
Type.MUSIC_PLAYLIST -> addMusicPlaylistTags(data.musicPlaylist, tags)
Type.MUSIC_RADIO_STATION -> addMusicRadioStationTags(data.musicRadioStation, tags)
Type.VIDEO_MOVIE, Type.VIDEO_TV_SHOW, Type.VIDEO_OTHER -> addVideoMovieTags(data.videoMovie, tags)
Type.VIDEO_EPISODE -> addVideoEpisodeTags(data.videoEpisode, tags)
else -> { /* No additional tags for other types */ }
}
return tags.joinToString("\n")
}
/**
* Adds basic Open Graph meta tags to the list.
*
* @param data The OpenGraph Data object
* @param tags The list to add the tags to
*/
private fun addBasicMetaTags(data: Data, tags: MutableList<String>) {
// Required properties
data.title?.let { tags.add(createMetaTag("og:title", it)) }
data.type?.let { tags.add(createMetaTag("og:type", it)) }
data.url?.let { tags.add(createMetaTag("og:url", it.toString())) }
// Optional properties
data.description?.let { tags.add(createMetaTag("og:description", it)) }
data.siteName?.let { tags.add(createMetaTag("og:site_name", it)) }
data.determiner?.let { tags.add(createMetaTag("og:determiner", it)) }
data.locale?.let { tags.add(createMetaTag("og:locale", it)) }
// Locale alternates
data.localeAlternate.forEach { locale ->
tags.add(createMetaTag("og:locale:alternate", locale))
}
}
/**
* Adds image meta tags to the list.
*
* @param images The list of Image objects
* @param tags The list to add the tags to
*/
private fun addImageTags(images: List<Image>, tags: MutableList<String>) {
images.forEach { image ->
image.url?.let { tags.add(createMetaTag("og:image", it)) }
image.secureUrl?.let { tags.add(createMetaTag("og:image:secure_url", it)) }
image.type?.let { tags.add(createMetaTag("og:image:type", it)) }
image.width?.let { tags.add(createMetaTag("og:image:width", it.toString())) }
image.height?.let { tags.add(createMetaTag("og:image:height", it.toString())) }
image.alt?.let { tags.add(createMetaTag("og:image:alt", it)) }
}
}
/**
* Adds video meta tags to the list.
*
* @param videos The list of Video objects
* @param tags The list to add the tags to
*/
private fun addVideoTags(videos: List<Video>, tags: MutableList<String>) {
videos.forEach { video ->
video.url?.let { tags.add(createMetaTag("og:video", it)) }
video.secureUrl?.let { tags.add(createMetaTag("og:video:secure_url", it)) }
video.type?.let { tags.add(createMetaTag("og:video:type", it)) }
video.width?.let { tags.add(createMetaTag("og:video:width", it.toString())) }
video.height?.let { tags.add(createMetaTag("og:video:height", it.toString())) }
video.duration?.let { tags.add(createMetaTag("og:video:duration", it.toString())) }
}
}
/**
* Adds audio meta tags to the list.
*
* @param audios The list of Audio objects
* @param tags The list to add the tags to
*/
private fun addAudioTags(audios: List<Audio>, tags: MutableList<String>) {
audios.forEach { audio ->
audio.url?.let { tags.add(createMetaTag("og:audio", it)) }
audio.secureUrl?.let { tags.add(createMetaTag("og:audio:secure_url", it)) }
audio.type?.let { tags.add(createMetaTag("og:audio:type", it)) }
}
}
/**
* Adds article-specific meta tags to the list.
*
* @param article The Article object
* @param tags The list to add the tags to
*/
private fun addArticleTags(article: Article?, tags: MutableList<String>) {
if (article == null) return
article.publishedTime?.let { tags.add(createMetaTag("og:article:published_time", formatDateTime(it))) }
article.modifiedTime?.let { tags.add(createMetaTag("og:article:modified_time", formatDateTime(it))) }
article.expirationTime?.let { tags.add(createMetaTag("og:article:expiration_time", formatDateTime(it))) }
article.section?.let { tags.add(createMetaTag("og:article:section", it)) }
article.authors.forEach { author ->
tags.add(createMetaTag("og:article:author", author))
}
article.tags.forEach { tag ->
tags.add(createMetaTag("og:article:tag", tag))
}
}
/**
* Adds profile-specific meta tags to the list.
*
* @param profile The Profile object
* @param tags The list to add the tags to
*/
private fun addProfileTags(profile: Profile?, tags: MutableList<String>) {
if (profile == null) return
profile.firstName?.let { tags.add(createMetaTag("og:profile:first_name", it)) }
profile.lastName?.let { tags.add(createMetaTag("og:profile:last_name", it)) }
profile.username?.let { tags.add(createMetaTag("og:profile:username", it)) }
profile.gender?.let { tags.add(createMetaTag("og:profile:gender", it.toString())) }
}
/**
* Adds book-specific meta tags to the list.
*
* @param book The Book object
* @param tags The list to add the tags to
*/
private fun addBookTags(book: Book?, tags: MutableList<String>) {
if (book == null) return
book.authors.forEach { author ->
tags.add(createMetaTag("og:book:author", author))
}
book.isbn?.let { tags.add(createMetaTag("og:book:isbn", it)) }
book.releaseDate?.let { tags.add(createMetaTag("og:book:release_date", formatDateTime(it))) }
book.tags.forEach { tag ->
tags.add(createMetaTag("og:book:tag", tag))
}
}
/**
* Adds music.song-specific meta tags to the list.
*
* @param musicSong The MusicSong object
* @param tags The list to add the tags to
*/
private fun addMusicSongTags(musicSong: MusicSong?, tags: MutableList<String>) {
if (musicSong == null) return
musicSong.duration?.let { tags.add(createMetaTag("og:music:duration", it.toString())) }
musicSong.album?.let { tags.add(createMetaTag("og:music:album", it)) }
musicSong.albumDisc?.let { tags.add(createMetaTag("og:music:album:disc", it.toString())) }
musicSong.albumTrack?.let { tags.add(createMetaTag("og:music:album:track", it.toString())) }
musicSong.musician.forEach { musician ->
tags.add(createMetaTag("og:music:musician", musician))
}
}
/**
* Adds music.album-specific meta tags to the list.
*
* @param musicAlbum The MusicAlbum object
* @param tags The list to add the tags to
*/
private fun addMusicAlbumTags(musicAlbum: MusicAlbum?, tags: MutableList<String>) {
if (musicAlbum == null) return
musicAlbum.songs.forEach { song ->
tags.add(createMetaTag("og:music:song", song))
}
musicAlbum.songDisc?.let { tags.add(createMetaTag("og:music:song:disc", it.toString())) }
musicAlbum.songTrack?.let { tags.add(createMetaTag("og:music:song:track", it.toString())) }
musicAlbum.musician.forEach { musician ->
tags.add(createMetaTag("og:music:musician", musician))
}
musicAlbum.releaseDate?.let { tags.add(createMetaTag("og:music:release_date", formatDateTime(it))) }
}
/**
* Adds music.playlist-specific meta tags to the list.
*
* @param musicPlaylist The MusicPlaylist object
* @param tags The list to add the tags to
*/
private fun addMusicPlaylistTags(musicPlaylist: MusicPlaylist?, tags: MutableList<String>) {
if (musicPlaylist == null) return
musicPlaylist.songs.forEach { song ->
tags.add(createMetaTag("og:music:song", song))
}
musicPlaylist.songDisc?.let { tags.add(createMetaTag("og:music:song:disc", it.toString())) }
musicPlaylist.songTrack?.let { tags.add(createMetaTag("og:music:song:track", it.toString())) }
musicPlaylist.creator?.let { tags.add(createMetaTag("og:music:creator", it)) }
}
/**
* Adds music.radio_station-specific meta tags to the list.
*
* @param musicRadioStation The MusicRadioStation object
* @param tags The list to add the tags to
*/
private fun addMusicRadioStationTags(musicRadioStation: MusicRadioStation?, tags: MutableList<String>) {
if (musicRadioStation == null) return
musicRadioStation.creator?.let { tags.add(createMetaTag("og:music:creator", it)) }
}
/**
* Adds video.movie-specific meta tags to the list.
*
* @param videoMovie The VideoMovie object
* @param tags The list to add the tags to
*/
private fun addVideoMovieTags(videoMovie: VideoMovie?, tags: MutableList<String>) {
if (videoMovie == null) return
videoMovie.actors.forEach { actor ->
tags.add(createMetaTag("og:video:actor", actor))
}
videoMovie.director.forEach { director ->
tags.add(createMetaTag("og:video:director", director))
}
videoMovie.writer.forEach { writer ->
tags.add(createMetaTag("og:video:writer", writer))
}
videoMovie.duration?.let { tags.add(createMetaTag("og:video:duration", it.toString())) }
videoMovie.releaseDate?.let { tags.add(createMetaTag("og:video:release_date", formatDateTime(it))) }
videoMovie.tags.forEach { tag ->
tags.add(createMetaTag("og:video:tag", tag))
}
}
/**
* Adds video.episode-specific meta tags to the list.
*
* @param videoEpisode The VideoEpisode object
* @param tags The list to add the tags to
*/
private fun addVideoEpisodeTags(videoEpisode: VideoEpisode?, tags: MutableList<String>) {
if (videoEpisode == null) return
videoEpisode.actors.forEach { actor ->
tags.add(createMetaTag("og:video:actor", actor))
}
videoEpisode.director.forEach { director ->
tags.add(createMetaTag("og:video:director", director))
}
videoEpisode.writer.forEach { writer ->
tags.add(createMetaTag("og:video:writer", writer))
}
videoEpisode.duration?.let { tags.add(createMetaTag("og:video:duration", it.toString())) }
videoEpisode.releaseDate?.let { tags.add(createMetaTag("og:video:release_date", formatDateTime(it))) }
videoEpisode.tags.forEach { tag ->
tags.add(createMetaTag("og:video:tag", tag))
}
videoEpisode.series?.let { tags.add(createMetaTag("og:video:series", it)) }
}
/**
* Creates an HTML meta tag with the given property and content.
*
* @param property The property attribute value
* @param content The content attribute value
* @return The HTML meta tag string
*/
private fun createMetaTag(property: String, content: String): String {
val escapedContent = content.replace("\"", "&quot;")
return "<meta property=\"$property\" content=\"$escapedContent\" />"
}
/**
* Formats an OffsetDateTime to a string suitable for OpenGraph tags.
*
* @param dateTime The OffsetDateTime to format
* @return The formatted date string in ISO-8601 format with 'Z' timezone indicator
*/
private fun formatDateTime(dateTime: OffsetDateTime): String {
return dateTime.toInstant().toString()
}
}

View File

@@ -1,8 +1,11 @@
package fr.lengrand.opengraphkt
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.select.Elements
import com.fleeksoft.ksoup.Ksoup
import com.fleeksoft.ksoup.network.parseGetRequestBlocking
import com.fleeksoft.ksoup.nodes.Document
import com.fleeksoft.ksoup.parseFile
import com.fleeksoft.ksoup.select.Elements
import kotlinx.coroutines.runBlocking
import java.io.File
import java.net.URI
import java.net.URL
@@ -61,21 +64,20 @@ class Parser {
* Extracts all Open Graph tags from a URL and returns a structured Data object.
*
* @param url The URL to be parsed for Open Graph information.
* @return An Data object containing all extracted Open Graph data.
* @return A Data object containing all extracted Open Graph data.
*/
fun parse(url: URL) : Data {
val doc = Jsoup.connect(url.toString()).get()
return parse(doc)
return parse(Ksoup.parseGetRequestBlocking(url.toString()))
}
/**
* Extracts all Open Graph tags from a raw HTML String and returns a structured Data object.
*
* @param html The raw HTML String to be parsed for Open Graph information.
* @return An Data object containing all extracted Open Graph data.
* @return A Data object containing all extracted Open Graph data.
*/
fun parse(html: String) : Data {
val doc = Jsoup.parse(html)
val doc = Ksoup.parse(html)
return parse(doc)
}
@@ -84,10 +86,12 @@ class Parser {
*
* @param file The file to parse
* @param charset The charset to use for parsing (default is UTF-8)
* @return An Data object containing all extracted Open Graph data.
* @return A Data object containing all extracted Open Graph data.
*/
fun parse(file: File, charset: String = "UTF-8") : Data {
val doc = Jsoup.parse(file, charset)
val doc = runBlocking {
Ksoup.parseFile(file, file.absolutePath, charset)
}
return parse(doc)
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,18 @@ repositories {
}
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation(platform("org.junit:junit-bom:5.14.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
implementation(kotlin("stdlib-jdk8"))
implementation(project(":opengraphkt"))
implementation("io.ktor:ktor-client-core:3.1.3")
implementation("io.ktor:ktor-client-cio:3.1.3")
implementation("io.ktor:ktor-client-core:3.3.1")
implementation("io.ktor:ktor-client-cio:3.3.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(23)
jvmToolchain(17)
}

View File

@@ -1,6 +1,6 @@
pluginManagement {
plugins {
kotlin("jvm") version "2.1.21"
kotlin("jvm") version "2.2.20"
}
}
plugins {