Compare commits

..

35 Commits

Author SHA1 Message Date
Julien Lengrand-Lambert
909979974a Adding badge 2025-05-19 12:29:11 +02:00
Julien Lengrand-Lambert
f1544bf599 With key 2025-05-19 12:26:10 +02:00
Julien Lengrand-Lambert
9799f3eb14 Explicitely calling task 2025-05-19 12:22:13 +02:00
Julien Lengrand-Lambert
ee84878ef1 Separate code coverage job 2025-05-19 11:34:06 +02:00
Julien Lengrand-Lambert
ca31ffa8f7 Not root, current dir 2025-05-19 11:29:49 +02:00
Julien Lengrand-Lambert
45bb727e13 Trying to upload artifact to see 2025-05-19 10:27:13 +02:00
Julien Lengrand-Lambert
bb5919d892 WTF am I doing 2025-05-19 10:25:24 +02:00
Julien Lengrand-Lambert
d819c2346c Before dependency graph? 2025-05-19 10:22:11 +02:00
Julien Lengrand-Lambert
e4410a7b79 Trying without module info 2025-05-19 10:17:10 +02:00
Julien Lengrand-Lambert
42c8accd3d Adding token 2025-05-19 10:10:19 +02:00
Julien Lengrand-Lambert
4c20ea5dac Adding codecov report 2025-05-19 10:05:59 +02:00
Julien Lengrand-Lambert
60cc4118d9 Adding badges 2025-05-19 08:33:00 +02:00
Julien Lengrand-Lambert
aa34916640 Upgrading next version number 2025-05-18 14:56:23 +02:00
julien Lengrand-Lambert
8e2a256eb8 Prepares 0.0.2 release 2025-05-18 14:51:14 +02:00
julien Lengrand-Lambert
b361b87070 Merge pull request #16 from jlengrand/feat/complete_implementation
Feat/complete implementation
2025-05-18 14:50:36 +02:00
Julien Lengrand-Lambert
8943e7217c Using more concise naming 2025-05-18 14:48:27 +02:00
Julien Lengrand-Lambert
5f5bccbc7c Moves model to their own file 2025-05-18 14:05:59 +02:00
Julien Lengrand-Lambert
aa056eabcf Adds Enum to easily recognize type 2025-05-18 11:15:54 +02:00
Julien Lengrand-Lambert
668b91cbcf Removing unused convenience method 2025-05-18 10:52:18 +02:00
Julien Lengrand-Lambert
dcf69d495b Adding unsupported types 2025-05-18 10:17:25 +02:00
julien Lengrand-Lambert
34400572d4 Merge pull request #11 from jlengrand/renovate/gradle-actions-4.x
Update gradle/actions action to v4.4.0
2025-05-18 09:50:47 +02:00
julien Lengrand-Lambert
019076ff04 Merge pull request #10 from jlengrand/renovate/gradle-8.x
Update dependency gradle to v8.14
2025-05-18 09:49:35 +02:00
julien Lengrand-Lambert
28781409ec Merge pull request #14 from jlengrand/renovate/com.vanniktech.maven.publish-0.x
Update plugin com.vanniktech.maven.publish to v0.32.0
2025-05-18 09:49:08 +02:00
julien Lengrand-Lambert
25cfba57d6 Merge pull request #15 from jlengrand/renovate/org.gradle.toolchains.foojay-resolver-convention-0.x
Update plugin org.gradle.toolchains.foojay-resolver-convention to v0.10.0
2025-05-18 09:48:40 +02:00
renovate[bot]
9daa2a7a4a Update plugin org.gradle.toolchains.foojay-resolver-convention to v0.10.0 2025-05-18 00:04:03 +00:00
renovate[bot]
90b9d7ad35 Update plugin com.vanniktech.maven.publish to v0.32.0 2025-05-18 00:03:59 +00:00
julien Lengrand-Lambert
b61b43c220 Merge pull request #9 from jlengrand/feat/invisible-jsoup
Feat/invisible jsoup

Fixes #8
2025-05-16 23:58:00 +02:00
Julien Lengrand-Lambert
8028f47761 Removing unnecessary checks 2025-05-16 23:55:44 +02:00
Julien Lengrand-Lambert
cd4462ff59 Publish and Release all together 2025-05-16 23:44:16 +02:00
Julien Lengrand-Lambert
ea62c616fb Adds moule to remote check library functionality 2025-05-16 23:32:42 +02:00
renovate[bot]
9e8e979514 Update gradle/actions action to v4.4.0 2025-05-16 21:15:51 +00:00
renovate[bot]
5f9b8de728 Update dependency gradle to v8.14 2025-05-16 21:15:47 +00:00
Julien Lengrand-Lambert
e88b2ca5c4 Fixes Main file typo and adds new use case 2025-05-16 23:11:32 +02:00
Julien Lengrand-Lambert
0803182d88 Fixes #8
Starts fixing #8.

Removes required dependency to JSoup for users of the library.
2025-05-16 23:08:28 +02:00
Julien Lengrand-Lambert
1da49245a4 Minimal README changes 2025-05-16 22:31:05 +02:00
20 changed files with 1233 additions and 693 deletions

View File

@@ -34,24 +34,45 @@ 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@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
- name: Build with Gradle Wrapper
run: ./gradlew build
# NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
# If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
#
# - name: Setup Gradle
# uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
# with:
# gradle-version: '8.9'
#
# - name: Build with Gradle 8.9
# run: gradle build
code-coverage:
strategy:
matrix:
java-version: ['23']
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
- name: Build with Gradle Wrapper
run: ./gradlew koverXmlReport
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: all
path: .
- name: Upload coverage reports
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: opengraphkt/build/reports/kover/report.xml
dependency-submission:
runs-on: ubuntu-latest
permissions:
contents: write
@@ -67,4 +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@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
uses: gradle/actions/dependency-submission@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0

View File

@@ -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 }}

1
.idea/gradle.xml generated
View File

@@ -10,6 +10,7 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/demo" />
<option value="$PROJECT_DIR$/demo-remote" />
<option value="$PROJECT_DIR$/opengraphkt" />
</set>
</option>

View File

@@ -1,11 +1,19 @@
# OpenGraphKt
[OpenGraphKt](https://github.com/jlengrand/OpenGraphKt) is a minimalist Kotlin multiplatform library that extracts [Open Graph tags](https://ogp.me/) from HTML pages.
The input HTML can be an inlined string, a file, or a remote URL. OpenGraphKt is a tiny wrapper on top of JSoup.
[![build](https://github.com/jlengrand/OpenGraphKt/actions/workflows/gradle.yml/badge.svg)](https://github.com/simplex-chat/jlengrand/OpenGraphKt/workflows/gradle.yml)
![Codecov](https://img.shields.io/codecov/c/github/jlengrand/OpenGraphKt)
![GitHub Release Date](https://img.shields.io/github/release-date/jlengrand/OpenGraphKt)
![Maven Central Version](https://img.shields.io/maven-central/v/fr.lengrand/opengraphkt)
![kotlin-version](https://img.shields.io/badge/kotlin-2.1.0-blue?logo=kotlin)
![GitHub License](https://img.shields.io/github/license/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 is a tiny wrapper on top of JSoup.
## Current status
* WIP and absolutely not ready for usage.
* WIP and absolutely not ready for production usage.
* Implementation missing the music, video and audio verticals (see https://ogp.me/)
## Dependencies
@@ -18,4 +26,4 @@ The input HTML can be an inlined string, a file, or a remote URL. OpenGraphKt is
## License
* [See License](./LICENSE)
* [The MIT LICENCE. See License](./LICENSE)

View File

@@ -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"
}

View File

@@ -0,0 +1,39 @@
package fr.lengrand.opengraphktremote
import fr.lengrand.opengraphkt.OpenGraphParser
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 = """
<!DOCTYPE html>
<html>
<head>
<title>Open Graph Example</title>
<meta property="og:title" content="The Rock" />
<meta property="og:type" content="video.movie" />
<meta property="og:url" content="https://example.com/the-rock" />
<meta property="og:image" content="https://example.com/rock.jpg" />
<meta property="og:image:width" content="300" />
<meta property="og:image:height" content="200" />
<meta property="og:description" content="An action movie about a rock" />
<meta property="og:site_name" content="Example Movies" />
</head>
<body>
<h1>Example Page</h1>
</body>
</html>
""".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()}")
}

View File

@@ -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)
}

View File

@@ -1,19 +1,19 @@
package fr.lengrand.opengraphkt
import org.jsoup.Jsoup
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()
val parser = Parser()
// 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 +28,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()}")
@@ -38,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 = """
<!DOCTYPE html>
<html>
@@ -59,9 +58,17 @@ fun main() {
</html>
""".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()}")
// 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()}")
}

Binary file not shown.

View File

@@ -1,6 +1,7 @@
#Wed Apr 30 23:06:11 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

47
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/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/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -133,22 +133,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

183
gradlew.bat vendored
View File

@@ -1,89 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
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" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,12 +1,13 @@
import com.vanniktech.maven.publish.SonatypeHost
plugins {
kotlin("jvm") version "2.1.21"
id("com.vanniktech.maven.publish") version "0.30.0"
kotlin("jvm")
id("com.vanniktech.maven.publish") version "0.32.0"
id("org.jetbrains.kotlinx.kover") version "0.9.1"
}
group = "fr.lengrand"
version = "0.0.1"
version = "0.0.3-SNAPSHOT"
repositories {
mavenCentral()
@@ -68,4 +69,14 @@ mavenPublishing {
developerConnection = "scm:git:ssh://git@github.com/jlengrand/OpenGraphKt.git"
}
}
}
kover {
reports {
verify {
rule {
minBound(70)
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,207 @@
package fr.lengrand.opengraphkt
/**
* Enum representing the different types of Open Graph objects.
*/
enum class Type {
ARTICLE,
PROFILE,
BOOK,
MUSIC_SONG,
MUSIC_ALBUM,
MUSIC_PLAYLIST,
MUSIC_RADIO_STATION,
VIDEO_MOVIE,
VIDEO_TV_SHOW,
VIDEO_OTHER,
VIDEO_EPISODE,
WEBSITE,
UNKNOWN;
companion object {
/**
* Converts a string type to the corresponding enum value.
*
* @param type The string representation of the type
* @return The corresponding Type enum value, or UNKNOWN if not recognized
*/
fun fromString(type: String?): Type {
return when (type) {
"article" -> ARTICLE
"profile" -> PROFILE
"book" -> BOOK
"music.song" -> MUSIC_SONG
"music.album" -> MUSIC_ALBUM
"music.playlist" -> MUSIC_PLAYLIST
"music.radio_station" -> MUSIC_RADIO_STATION
"video.movie" -> VIDEO_MOVIE
"video.tv_show" -> VIDEO_TV_SHOW
"video.other" -> VIDEO_OTHER
"video.episode" -> VIDEO_EPISODE
"website" -> WEBSITE
null -> WEBSITE // No type tag defaults to Website
else -> UNKNOWN // Another type we are not aware of
}
}
}
}
data class Tag(
val property: String,
val content: String,
)
/**
* Represents structured Open Graph data extracted from HTML.
*/
data class Data(
val tags: List<Tag>,
// Basic metadata
val title: String?,
val type: String?,
val url: String?,
val description: String?,
val siteName: String?,
val determiner: String?,
val locale: String?,
val localeAlternate: List<String>,
// Structured properties
val images: List<Image>,
val videos: List<Video>,
val audios: List<Audio>,
// Optional type-specific metadata
val article: Article?,
val profile: Profile?,
val book: Book?,
// Music types
val musicSong: MusicSong?,
val musicAlbum: MusicAlbum?,
val musicPlaylist: MusicPlaylist?,
val musicRadioStation: MusicRadioStation?,
// Video types
val videoMovie: VideoMovie?,
val videoEpisode: VideoEpisode?
) {
/**
* Checks if this Open Graph data contains the minimum required properties.
*
* According to the Open Graph protocol, the minimum required properties are:
* - og:title
* - og:type
* - og:image
* - og:url
*
* @return true if all required properties are present, false otherwise
*/
fun isValid(): Boolean {
return title != null && type != null && images.isNotEmpty() && url != null
}
/**
* Returns the type of this Open Graph data as an enum value.
*
* @return The Type enum value corresponding to the type string, or UNKNOWN if the type is not recognized
*/
fun getType(): Type {
return Type.fromString(type)
}
}
data class Image(
val url: String?,
val secureUrl: String?,
val type: String?,
val width: Int?,
val height: Int?,
val alt: String?
)
data class Video(
val url: String?,
val secureUrl: String?,
val type: String?,
val width: Int?,
val height: Int?,
val duration: Int?
)
data class Audio(
val url: String?,
val secureUrl: String?,
val type: String?
)
/**
* * video.tv_show - same as video.movie
* * video.other - same as video.movie
*/
data class Article(
val publishedTime: String?,
val modifiedTime: String?,
val expirationTime: String?,
val section: String?,
val authors: List<String>,
val tags: List<String>
)
data class Profile(
val firstName: String?,
val lastName: String?,
val username: String?,
val gender: String?
)
data class Book(
val authors: List<String>,
val isbn: String?,
val releaseDate: String?,
val tags: List<String>
)
data class MusicSong(
val duration: Int?,
val album: String?,
val albumDisc: Int?,
val albumTrack: Int?,
val musician: List<String>
)
data class MusicAlbum(
val songs: List<String>,
val musician: List<String>,
val releaseDate: String?
)
data class MusicPlaylist(
val songs: List<String>,
val creator: String?
)
data class MusicRadioStation(
val creator: String?
)
data class VideoMovie(
val actors: List<String>,
val director: List<String>,
val writer: List<String>,
val duration: Int?,
val releaseDate: String?,
val tags: List<String>
)
data class VideoEpisode(
val actors: List<String>,
val director: List<String>,
val writer: List<String>,
val duration: Int?,
val releaseDate: String?,
val tags: List<String>,
val series: String?
)

View File

@@ -1,469 +0,0 @@
package fr.lengrand.opengraphkt
import org.jsoup.nodes.Document
import org.jsoup.select.Elements
data class OpenGraphTag(
val property: String,
val content: String,
)
/**
* Represents structured Open Graph data extracted from HTML.
*/
data class OpenGraphData(
val tags: List<OpenGraphTag>,
// Basic metadata
val title: String?,
val type: String?,
val url: String?,
val description: String?,
val siteName: String?,
val determiner: String?,
val locale: String?,
val localeAlternate: List<String>,
// Structured properties
val images: List<OpenGraphImage>,
val videos: List<OpenGraphVideo>,
val audios: List<OpenGraphAudio>,
// Optional type-specific metadata
val article: OpenGraphArticle?,
val profile: OpenGraphProfile?,
val book: OpenGraphBook?
) {
/**
* Checks if this Open Graph data contains the minimum required properties.
*
* According to the Open Graph protocol, the minimum required properties are:
* - og:title
* - og:type
* - og:image
* - og:url
*
* @return true if all required properties are present, false otherwise
*/
fun isValid(): Boolean {
return title != null && type != null && images.isNotEmpty() && url != null
}
/**
* Gets the first image URL, or null if no images are present.
*
* @return The URL of the first image, or null
*/
fun getFirstImageUrl(): String? {
return images.firstOrNull()?.url
}
}
/**
* Represents an Open Graph image.
*/
data class OpenGraphImage(
val url: String?,
val secureUrl: String?,
val type: String?,
val width: Int?,
val height: Int?,
val alt: String?
)
/**
* Represents an Open Graph video.
*/
data class OpenGraphVideo(
val url: String?,
val secureUrl: String?,
val type: String?,
val width: Int?,
val height: Int?,
val duration: Int?
)
/**
* Represents an Open Graph audio.
*/
data class OpenGraphAudio(
val url: String?,
val secureUrl: String?,
val type: String?
)
/**
* Represents Open Graph article metadata.
*/
data class OpenGraphArticle(
val publishedTime: String?,
val modifiedTime: String?,
val expirationTime: String?,
val section: String?,
val authors: List<String>,
val tags: List<String>
)
/**
* Represents Open Graph profile metadata.
*/
data class OpenGraphProfile(
val firstName: String?,
val lastName: String?,
val username: String?,
val gender: String?
)
/**
* Represents Open Graph book metadata.
*/
data class OpenGraphBook(
val authors: List<String>,
val isbn: String?,
val releaseDate: String?,
val tags: List<String>
)
/**
* A comprehensive parser for Open Graph protocol tags.
*
* The Open Graph protocol enables any web page to become a rich object in a social graph.
* This parser extracts all Open Graph tags from an HTML document and organizes them into
* a structured format according to the Open Graph protocol specification.
*
* @see <a href="https://ogp.me/">Open Graph Protocol</a>
*/
class OpenGraphParser {
/**
* Extracts all Open Graph tags from a JSoup Document and returns a structured OpenGraphData object.
*
* @param document The JSoup Document to parse
* @return An OpenGraphData object containing all extracted Open Graph data
*/
fun parse(document: Document): OpenGraphData {
val tags = document.select("meta[property^=og:]")
val openGraphTags = extractOpenGraphTags(tags)
return buildOpenGraphData(openGraphTags)
}
/**
* Extracts Open Graph tags from JSoup Elements and converts them to OpenGraphTag objects.
*
* @param elements The JSoup Elements containing Open Graph meta tags
* @return A list of OpenGraphTag objects
*/
private fun extractOpenGraphTags(elements: Elements): List<OpenGraphTag> {
return elements.map { element ->
val fullProperty = element.attr("property")
val property = fullProperty.substring(3) // Remove "og:" prefix
val content = element.attr("content")
OpenGraphTag(property, content)
}
}
/**
* Builds an OpenGraphData object from a list of OpenGraphTag objects.
*
* @param tags The list of OpenGraphTag objects
* @return An OpenGraphData object containing structured Open Graph data
*/
private fun buildOpenGraphData(tags: List<OpenGraphTag>): OpenGraphData {
// Group tags by their namespace (before the first colon)
val groupedTags = tags.groupBy { tag ->
if (tag.property.contains(":")) {
val parts = tag.property.split(":", limit = 2)
parts[0]
} else {
tag.property
}
}
// Build basic properties
val title = getFirstTagContent(tags, "title")
val type = getFirstTagContent(tags, "type")
val url = getFirstTagContent(tags, "url")
val description = getFirstTagContent(tags, "description")
val siteName = getFirstTagContent(tags, "site_name")
val determiner = getFirstTagContent(tags, "determiner")
val locale = getFirstTagContent(tags, "locale")
val localeAlternate = getTagsContent(tags, "locale:alternate")
// Build structured properties
val images = buildImages(groupedTags.getOrDefault("image", emptyList()))
val videos = buildVideos(groupedTags.getOrDefault("video", emptyList()))
val audios = buildAudios(groupedTags.getOrDefault("audio", emptyList()))
// Build article-specific properties if the type is "article"
val article = if (type == "article") buildArticle(groupedTags) else null
// Build profile-specific properties if the type is "profile"
val profile = if (type == "profile") buildProfile(groupedTags) else null
// Build book-specific properties if the type is "book"
val book = if (type == "book") buildBook(groupedTags) else null
return OpenGraphData(
tags = tags,
title = title,
type = type,
url = url,
description = description,
siteName = siteName,
determiner = determiner,
locale = locale,
localeAlternate = localeAlternate,
images = images,
videos = videos,
audios = audios,
article = article,
profile = profile,
book = book
)
}
/**
* Gets the content of the first tag with the specified property.
*
* @param tags The list of OpenGraphTag objects
* @param property The property to look for
* @return The content of the first tag with the specified property, or null if not found
*/
private fun getFirstTagContent(tags: List<OpenGraphTag>, property: String): String? {
return tags.firstOrNull { it.property == property }?.content
}
/**
* Gets the content of all tags with the specified property.
*
* @param tags The list of OpenGraphTag objects
* @param property The property to look for
* @return A list of content values from all tags with the specified property
*/
private fun getTagsContent(tags: List<OpenGraphTag>, property: String): List<String> {
return tags.filter { it.property == property }.map { it.content }
}
/**
* Builds a list of OpenGraphImage objects from image tags.
*
* @param imageTags The list of image-related OpenGraphTag objects
* @return A list of OpenGraphImage objects
*/
private fun buildImages(imageTags: List<OpenGraphTag>): List<OpenGraphImage> {
val baseImageTags = imageTags.filter {
it.property == "image" || it.property == "image:url"
}
// No images
if (baseImageTags.isEmpty()) {
return emptyList()
}
val images = mutableListOf<OpenGraphImage>()
// For each base image tag, create an image object and find its attributes
baseImageTags.forEach { baseTag ->
val baseIndex = imageTags.indexOf(baseTag)
val nextBaseIndex = imageTags.subList(baseIndex + 1, imageTags.size)
.indexOfFirst { it.property == "image" || it.property == "image:url" }
val endIndex = if (nextBaseIndex == -1) imageTags.size else baseIndex + 1 + nextBaseIndex
val attributeTags = imageTags.subList(baseIndex + 1, endIndex)
.filter { it.property.startsWith("image:") }
val secureUrl = attributeTags.firstOrNull { it.property == "image:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "image:type" }?.content
val width = attributeTags.firstOrNull { it.property == "image:width" }?.content?.toIntOrNull()
val height = attributeTags.firstOrNull { it.property == "image:height" }?.content?.toIntOrNull()
val alt = attributeTags.firstOrNull { it.property == "image:alt" }?.content
images.add(OpenGraphImage(
url = baseTag.content,
secureUrl = secureUrl,
type = type,
width = width,
height = height,
alt = alt
))
}
return images.toList()
}
/**
* Builds a list of OpenGraphVideo objects from video tags.
*
* @param videoTags The list of video-related OpenGraphTag objects
* @return A list of OpenGraphVideo objects
*/
private fun buildVideos(videoTags: List<OpenGraphTag>): List<OpenGraphVideo> {
val baseVideoTags = videoTags.filter {
it.property == "video" || it.property == "video:url"
}
// No videos
if (baseVideoTags.isEmpty()) {
return emptyList()
}
val videos = mutableListOf<OpenGraphVideo>()
// For each base video tag, create a video object and find its attributes
baseVideoTags.forEach { baseTag ->
val baseIndex = videoTags.indexOf(baseTag)
val nextBaseIndex = videoTags.subList(baseIndex + 1, videoTags.size)
.indexOfFirst { it.property == "video" || it.property == "video:url" }
val endIndex = if (nextBaseIndex == -1) videoTags.size else baseIndex + 1 + nextBaseIndex
val attributeTags = videoTags.subList(baseIndex + 1, endIndex)
.filter { it.property.startsWith("video:") }
val secureUrl = attributeTags.firstOrNull { it.property == "video:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "video:type" }?.content
val width = attributeTags.firstOrNull { it.property == "video:width" }?.content?.toIntOrNull()
val height = attributeTags.firstOrNull { it.property == "video:height" }?.content?.toIntOrNull()
val duration = attributeTags.firstOrNull { it.property == "video:duration" }?.content?.toIntOrNull()
videos.add(OpenGraphVideo(
url = baseTag.content,
secureUrl = secureUrl,
type = type,
width = width,
height = height,
duration = duration
))
}
return videos.toList()
}
/**
* Builds a list of OpenGraphAudio objects from audio tags.
*
* @param audioTags The list of audio-related OpenGraphTag objects
* @return A list of OpenGraphAudio objects
*/
private fun buildAudios(audioTags: List<OpenGraphTag>): List<OpenGraphAudio> {
val baseAudioTags = audioTags.filter {
it.property == "audio" || it.property == "audio:url"
}
// No audio
if (baseAudioTags.isEmpty()) {
return emptyList()
}
val audios = mutableListOf<OpenGraphAudio>()
// For each base audio tag, create an audio object and find its attributes
baseAudioTags.forEach { baseTag ->
val baseIndex = audioTags.indexOf(baseTag)
val nextBaseIndex = audioTags.subList(baseIndex + 1, audioTags.size)
.indexOfFirst { it.property == "audio" || it.property == "audio:url" }
val endIndex = if (nextBaseIndex == -1) audioTags.size else baseIndex + 1 + nextBaseIndex
val attributeTags = audioTags.subList(baseIndex + 1, endIndex)
.filter { it.property.startsWith("audio:") }
val secureUrl = attributeTags.firstOrNull { it.property == "audio:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "audio:type" }?.content
audios.add(OpenGraphAudio(
url = baseTag.content,
secureUrl = secureUrl,
type = type
))
}
return audios.toList()
}
/**
* Builds an OpenGraphArticle object from article-related tags.
*
* @param groupedTags The map of grouped OpenGraphTag objects
* @return An OpenGraphArticle object, or null if no article tags are found
*/
private fun buildArticle(groupedTags: Map<String, List<OpenGraphTag>>): OpenGraphArticle? {
val articleTags = groupedTags.getOrDefault("article", emptyList())
if (articleTags.isEmpty()) {
return null
}
val publishedTime = articleTags.firstOrNull { it.property == "article:published_time" }?.content
val modifiedTime = articleTags.firstOrNull { it.property == "article:modified_time" }?.content
val expirationTime = articleTags.firstOrNull { it.property == "article:expiration_time" }?.content
val section = articleTags.firstOrNull { it.property == "article:section" }?.content
val authors = articleTags.filter { it.property == "article:author" }.map { it.content }
val tags = articleTags.filter { it.property == "article:tag" }.map { it.content }
return OpenGraphArticle(
publishedTime = publishedTime,
modifiedTime = modifiedTime,
expirationTime = expirationTime,
section = section,
authors = authors,
tags = tags
)
}
/**
* Builds an OpenGraphProfile object from profile-related tags.
*
* @param groupedTags The map of grouped OpenGraphTag objects
* @return An OpenGraphProfile object, or null if no profile tags are found
*/
private fun buildProfile(groupedTags: Map<String, List<OpenGraphTag>>): OpenGraphProfile? {
val profileTags = groupedTags.getOrDefault("profile", emptyList())
if (profileTags.isEmpty()) {
return null
}
val firstName = profileTags.firstOrNull { it.property == "profile:first_name" }?.content
val lastName = profileTags.firstOrNull { it.property == "profile:last_name" }?.content
val username = profileTags.firstOrNull { it.property == "profile:username" }?.content
val gender = profileTags.firstOrNull { it.property == "profile:gender" }?.content
return OpenGraphProfile(
firstName = firstName,
lastName = lastName,
username = username,
gender = gender
)
}
/**
* Builds an OpenGraphBook object from book-related tags.
*
* @param groupedTags The map of grouped OpenGraphTag objects
* @return An OpenGraphBook object, or null if no book tags are found
*/
private fun buildBook(groupedTags: Map<String, List<OpenGraphTag>>): OpenGraphBook? {
val bookTags = groupedTags.getOrDefault("book", emptyList())
if (bookTags.isEmpty()) {
return null
}
val authors = bookTags.filter { it.property == "book:author" }.map { it.content }
val isbn = bookTags.firstOrNull { it.property == "book:isbn" }?.content
val releaseDate = bookTags.firstOrNull { it.property == "book:release_date" }?.content
val tags = bookTags.filter { it.property == "book:tag" }.map { it.content }
return OpenGraphBook(
authors = authors,
isbn = isbn,
releaseDate = releaseDate,
tags = tags
)
}
}

View File

@@ -0,0 +1,556 @@
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
/**
* A comprehensive parser for Open Graph protocol tags.
*
* The Open Graph protocol enables any web page to become a rich object in a social graph.
* This parser extracts all Open Graph tags from an HTML document and organizes them into
* a structured format according to the Open Graph protocol specification.
*
* @see <a href="https://ogp.me/">Open Graph Protocol</a>
*/
class Parser {
/**
* Extracts all Open Graph tags from a JSoup Document and returns a structured Data object.
*
* @param document The JSoup Document to parse
* @return An Data object containing all extracted Open Graph data
*/
fun parse(document: Document): Data {
val tags = document.select("meta[property^=og:]")
val openGraphTags = extractOpenGraphTags(tags)
return buildOpenGraphData(openGraphTags)
}
/**
* 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.
*/
fun parse(url: URL) : Data {
val doc = Jsoup.connect(url.toString()).get()
return parse(doc)
}
/**
* 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.
*/
fun parse(html: String) : Data {
val doc = Jsoup.parse(html)
return parse(doc)
}
/**
* Extracts all Open Graph tags from a raw HTML String and returns a structured Data object.
*
* @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.
*/
fun parse(file: File, charset: String = "UTF-8") : Data {
val doc = Jsoup.parse(file, charset)
return parse(doc)
}
/**
* Extracts Open Graph tags from JSoup Elements and converts them to Tag objects.
*
* @param elements The JSoup Elements containing Open Graph meta tags
* @return A list of Tag objects
*/
private fun extractOpenGraphTags(elements: Elements): List<Tag> {
return elements.map { element ->
val fullProperty = element.attr("property")
val property = fullProperty.substring(3) // Remove "og:" prefix
val content = element.attr("content")
Tag(property, content)
}
}
/**
* Builds an Data object from a list of Tag objects.
*
* @param tags The list of Tag objects
* @return An Data object containing structured Open Graph data
*/
private fun buildOpenGraphData(tags: List<Tag>): Data {
// Group tags by their namespace (before the first colon)
val groupedTags = tags.groupBy { tag ->
if (tag.property.contains(":")) {
val parts = tag.property.split(":", limit = 2)
parts[0]
} else {
tag.property
}
}
// Build basic properties
val title = getFirstTagContent(tags, "title")
val type = getFirstTagContent(tags, "type")
val url = getFirstTagContent(tags, "url")
val description = getFirstTagContent(tags, "description")
val siteName = getFirstTagContent(tags, "site_name")
val determiner = getFirstTagContent(tags, "determiner")
val locale = getFirstTagContent(tags, "locale")
val localeAlternate = getTagsContent(tags, "locale:alternate")
// Build structured properties
val images = buildImages(groupedTags.getOrDefault("image", emptyList()))
val videos = buildVideos(groupedTags.getOrDefault("video", emptyList()))
val audios = buildAudios(groupedTags.getOrDefault("audio", emptyList()))
// Build article-specific properties if the type is "article"
val article = if (type == "article") buildArticle(groupedTags) else null
// Build profile-specific properties if the type is "profile"
val profile = if (type == "profile") buildProfile(groupedTags) else null
// Build book-specific properties if the type is "book"
val book = if (type == "book") buildBook(groupedTags) else null
// Build music-specific properties based on type
val musicSong = if (type == "music.song") buildMusicSong(groupedTags) else null
val musicAlbum = if (type == "music.album") buildMusicAlbum(groupedTags) else null
val musicPlaylist = if (type == "music.playlist") buildMusicPlaylist(groupedTags) else null
val musicRadioStation = if (type == "music.radio_station") buildMusicRadioStation(groupedTags) else null
// Build video-specific properties based on type
val videoMovie = if (type == "video.movie" || type == "video.tv_show" || type == "video.other") buildVideoMovie(groupedTags) else null
val videoEpisode = if (type == "video.episode") buildVideoEpisode(groupedTags) else null
return Data(
tags = tags,
title = title,
type = type,
url = url,
description = description,
siteName = siteName,
determiner = determiner,
locale = locale,
localeAlternate = localeAlternate,
images = images,
videos = videos,
audios = audios,
article = article,
profile = profile,
book = book,
musicSong = musicSong,
musicAlbum = musicAlbum,
musicPlaylist = musicPlaylist,
musicRadioStation = musicRadioStation,
videoMovie = videoMovie,
videoEpisode = videoEpisode
)
}
/**
* Gets the content of the first tag with the specified property.
*
* @param tags The list of Tag objects
* @param property The property to look for
* @return The content of the first tag with the specified property, or null if not found
*/
private fun getFirstTagContent(tags: List<Tag>, property: String): String? {
return tags.firstOrNull { it.property == property }?.content
}
/**
* Gets the content of all tags with the specified property.
*
* @param tags The list of Tag objects
* @param property The property to look for
* @return A list of content values from all tags with the specified property
*/
private fun getTagsContent(tags: List<Tag>, property: String): List<String> {
return tags.filter { it.property == property }.map { it.content }
}
/**
* Builds a list of Image objects from image tags.
*
* @param imageTags The list of image-related Tag objects
* @return A list of Image objects
*/
private fun buildImages(imageTags: List<Tag>): List<Image> {
val baseImageTags = imageTags.filter {
it.property == "image" || it.property == "image:url"
}
// No images
if (baseImageTags.isEmpty()) {
return emptyList()
}
val images = mutableListOf<Image>()
// For each base image tag, create an image object and find its attributes
baseImageTags.forEach { baseTag ->
val baseIndex = imageTags.indexOf(baseTag)
val nextBaseIndex = imageTags.subList(baseIndex + 1, imageTags.size)
.indexOfFirst { it.property == "image" || it.property == "image:url" }
val endIndex = if (nextBaseIndex == -1) imageTags.size else baseIndex + 1 + nextBaseIndex
val attributeTags = imageTags.subList(baseIndex + 1, endIndex)
.filter { it.property.startsWith("image:") }
val secureUrl = attributeTags.firstOrNull { it.property == "image:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "image:type" }?.content
val width = attributeTags.firstOrNull { it.property == "image:width" }?.content?.toIntOrNull()
val height = attributeTags.firstOrNull { it.property == "image:height" }?.content?.toIntOrNull()
val alt = attributeTags.firstOrNull { it.property == "image:alt" }?.content
images.add(Image(
url = baseTag.content,
secureUrl = secureUrl,
type = type,
width = width,
height = height,
alt = alt
))
}
return images.toList()
}
/**
* Builds a list of Video objects from video tags.
*
* @param videoTags The list of video-related Tag objects
* @return A list of Video objects
*/
private fun buildVideos(videoTags: List<Tag>): List<Video> {
val baseVideoTags = videoTags.filter {
it.property == "video" || it.property == "video:url"
}
// No videos
if (baseVideoTags.isEmpty()) {
return emptyList()
}
val videos = mutableListOf<Video>()
// For each base video tag, create a video object and find its attributes
baseVideoTags.forEach { baseTag ->
val baseIndex = videoTags.indexOf(baseTag)
val nextBaseIndex = videoTags.subList(baseIndex + 1, videoTags.size)
.indexOfFirst { it.property == "video" || it.property == "video:url" }
val endIndex = if (nextBaseIndex == -1) videoTags.size else baseIndex + 1 + nextBaseIndex
val attributeTags = videoTags.subList(baseIndex + 1, endIndex)
.filter { it.property.startsWith("video:") }
val secureUrl = attributeTags.firstOrNull { it.property == "video:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "video:type" }?.content
val width = attributeTags.firstOrNull { it.property == "video:width" }?.content?.toIntOrNull()
val height = attributeTags.firstOrNull { it.property == "video:height" }?.content?.toIntOrNull()
val duration = attributeTags.firstOrNull { it.property == "video:duration" }?.content?.toIntOrNull()
videos.add(Video(
url = baseTag.content,
secureUrl = secureUrl,
type = type,
width = width,
height = height,
duration = duration
))
}
return videos.toList()
}
/**
* Builds a list of Audio objects from audio tags.
*
* @param audioTags The list of audio-related Tag objects
* @return A list of Audio objects
*/
private fun buildAudios(audioTags: List<Tag>): List<Audio> {
val baseAudioTags = audioTags.filter {
it.property == "audio" || it.property == "audio:url"
}
// No audio
if (baseAudioTags.isEmpty()) {
return emptyList()
}
val audios = mutableListOf<Audio>()
// For each base audio tag, create an audio object and find its attributes
baseAudioTags.forEach { baseTag ->
val baseIndex = audioTags.indexOf(baseTag)
val nextBaseIndex = audioTags.subList(baseIndex + 1, audioTags.size)
.indexOfFirst { it.property == "audio" || it.property == "audio:url" }
val endIndex = if (nextBaseIndex == -1) audioTags.size else baseIndex + 1 + nextBaseIndex
val attributeTags = audioTags.subList(baseIndex + 1, endIndex)
.filter { it.property.startsWith("audio:") }
val secureUrl = attributeTags.firstOrNull { it.property == "audio:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "audio:type" }?.content
audios.add(Audio(
url = baseTag.content,
secureUrl = secureUrl,
type = type
))
}
return audios.toList()
}
/**
* Builds an Article object from article-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An Article object, or null if no article tags are found
*/
private fun buildArticle(groupedTags: Map<String, List<Tag>>): Article? {
val articleTags = groupedTags.getOrDefault("article", emptyList())
if (articleTags.isEmpty()) {
return null
}
val publishedTime = articleTags.firstOrNull { it.property == "article:published_time" }?.content
val modifiedTime = articleTags.firstOrNull { it.property == "article:modified_time" }?.content
val expirationTime = articleTags.firstOrNull { it.property == "article:expiration_time" }?.content
val section = articleTags.firstOrNull { it.property == "article:section" }?.content
val authors = articleTags.filter { it.property == "article:author" }.map { it.content }
val tags = articleTags.filter { it.property == "article:tag" }.map { it.content }
return Article(
publishedTime = publishedTime,
modifiedTime = modifiedTime,
expirationTime = expirationTime,
section = section,
authors = authors,
tags = tags
)
}
/**
* Builds an Profile object from profile-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An Profile object, or null if no profile tags are found
*/
private fun buildProfile(groupedTags: Map<String, List<Tag>>): Profile? {
val profileTags = groupedTags.getOrDefault("profile", emptyList())
if (profileTags.isEmpty()) {
return null
}
val firstName = profileTags.firstOrNull { it.property == "profile:first_name" }?.content
val lastName = profileTags.firstOrNull { it.property == "profile:last_name" }?.content
val username = profileTags.firstOrNull { it.property == "profile:username" }?.content
val gender = profileTags.firstOrNull { it.property == "profile:gender" }?.content
return Profile(
firstName = firstName,
lastName = lastName,
username = username,
gender = gender
)
}
/**
* Builds an Book object from book-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An Book object, or null if no book tags are found
*/
private fun buildBook(groupedTags: Map<String, List<Tag>>): Book? {
val bookTags = groupedTags.getOrDefault("book", emptyList())
if (bookTags.isEmpty()) {
return null
}
val authors = bookTags.filter { it.property == "book:author" }.map { it.content }
val isbn = bookTags.firstOrNull { it.property == "book:isbn" }?.content
val releaseDate = bookTags.firstOrNull { it.property == "book:release_date" }?.content
val tags = bookTags.filter { it.property == "book:tag" }.map { it.content }
return Book(
authors = authors,
isbn = isbn,
releaseDate = releaseDate,
tags = tags
)
}
/**
* Builds an MusicSong object from music.song-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An MusicSong object, or null if no music.song tags are found
*/
private fun buildMusicSong(groupedTags: Map<String, List<Tag>>): MusicSong? {
val musicTags = groupedTags.getOrDefault("music", emptyList())
if (musicTags.isEmpty()) {
return null
}
val duration = musicTags.firstOrNull { it.property == "music:duration" }?.content?.toIntOrNull()
val album = musicTags.firstOrNull { it.property == "music:album" }?.content
val albumDisc = musicTags.firstOrNull { it.property == "music:album:disc" }?.content?.toIntOrNull()
val albumTrack = musicTags.firstOrNull { it.property == "music:album:track" }?.content?.toIntOrNull()
val musicians = musicTags.filter { it.property == "music:musician" }.map { it.content }
return MusicSong(
duration = duration,
album = album,
albumDisc = albumDisc,
albumTrack = albumTrack,
musician = musicians
)
}
/**
* Builds an MusicAlbum object from music.album-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An MusicAlbum object, or null if no music.album tags are found
*/
private fun buildMusicAlbum(groupedTags: Map<String, List<Tag>>): MusicAlbum? {
val musicTags = groupedTags.getOrDefault("music", emptyList())
if (musicTags.isEmpty()) {
return null
}
val songs = musicTags.filter { it.property == "music:song" }.map { it.content }
val musicians = musicTags.filter { it.property == "music:musician" }.map { it.content }
val releaseDate = musicTags.firstOrNull { it.property == "music:release_date" }?.content
return MusicAlbum(
songs = songs,
musician = musicians,
releaseDate = releaseDate
)
}
/**
* Builds an MusicPlaylist object from music.playlist-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An MusicPlaylist object, or null if no music.playlist tags are found
*/
private fun buildMusicPlaylist(groupedTags: Map<String, List<Tag>>): MusicPlaylist? {
val musicTags = groupedTags.getOrDefault("music", emptyList())
if (musicTags.isEmpty()) {
return null
}
val songs = musicTags.filter { it.property == "music:song" }.map { it.content }
val creator = musicTags.firstOrNull { it.property == "music:creator" }?.content
return MusicPlaylist(
songs = songs,
creator = creator
)
}
/**
* Builds an MusicRadioStation object from music.radio_station-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An MusicRadioStation object, or null if no music.radio_station tags are found
*/
private fun buildMusicRadioStation(groupedTags: Map<String, List<Tag>>): MusicRadioStation? {
val musicTags = groupedTags.getOrDefault("music", emptyList())
if (musicTags.isEmpty()) {
return null
}
val creator = musicTags.firstOrNull { it.property == "music:creator" }?.content
return MusicRadioStation(
creator = creator
)
}
/**
* Builds an VideoMovie object from video.movie-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An VideoMovie object, or null if no video.movie tags are found
*/
private fun buildVideoMovie(groupedTags: Map<String, List<Tag>>): VideoMovie? {
val videoTags = groupedTags.getOrDefault("video", emptyList())
if (videoTags.isEmpty()) {
return null
}
val actors = videoTags.filter { it.property == "video:actor" }.map { it.content }
val directors = videoTags.filter { it.property == "video:director" }.map { it.content }
val writers = videoTags.filter { it.property == "video:writer" }.map { it.content }
val duration = videoTags.firstOrNull { it.property == "video:duration" }?.content?.toIntOrNull()
val releaseDate = videoTags.firstOrNull { it.property == "video:release_date" }?.content
val tags = videoTags.filter { it.property == "video:tag" }.map { it.content }
return VideoMovie(
actors = actors,
director = directors,
writer = writers,
duration = duration,
releaseDate = releaseDate,
tags = tags
)
}
/**
* Builds an VideoEpisode object from video.episode-related tags.
*
* @param groupedTags The map of grouped Tag objects
* @return An VideoEpisode object, or null if no video.episode tags are found
*/
private fun buildVideoEpisode(groupedTags: Map<String, List<Tag>>): VideoEpisode? {
val videoTags = groupedTags.getOrDefault("video", emptyList())
if (videoTags.isEmpty()) {
return null
}
val actors = videoTags.filter { it.property == "video:actor" }.map { it.content }
val directors = videoTags.filter { it.property == "video:director" }.map { it.content }
val writers = videoTags.filter { it.property == "video:writer" }.map { it.content }
val duration = videoTags.firstOrNull { it.property == "video:duration" }?.content?.toIntOrNull()
val releaseDate = videoTags.firstOrNull { it.property == "video:release_date" }?.content
val tags = videoTags.filter { it.property == "video:tag" }.map { it.content }
val series = videoTags.firstOrNull { it.property == "video:series" }?.content
return VideoEpisode(
actors = actors,
director = directors,
writer = writers,
duration = duration,
releaseDate = releaseDate,
tags = tags,
series = series
)
}
}

View File

@@ -1,14 +1,15 @@
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
class OpenGraphParserTest {
class ParserTest {
private val parser = OpenGraphParser()
private val fetcher = DocumentFetcher()
private val parser = Parser()
// Sample HTML with all required OpenGraph tags and some structured properties
private val completeHtml = """
@@ -139,14 +140,13 @@ 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)
assertEquals("video.movie", openGraphData.type)
assertEquals("https://example.com/the-rock", openGraphData.url)
// Verify that the OpenGraphData object is valid
assertTrue(openGraphData.isValid())
@@ -180,100 +180,243 @@ class OpenGraphParserTest {
assertEquals(2, openGraphData.localeAlternate.size)
assertTrue(openGraphData.localeAlternate.contains("fr_FR"))
assertTrue(openGraphData.localeAlternate.contains("es_ES"))
// Verify video.movie properties
assertNotNull(openGraphData.videoMovie)
assertEquals(0, openGraphData.videoMovie.actors.size)
assertEquals(0, openGraphData.videoMovie.director.size)
assertEquals(0, openGraphData.videoMovie.writer.size)
assertEquals(null, openGraphData.videoMovie.duration)
assertEquals(null, openGraphData.videoMovie.releaseDate)
assertEquals(0, openGraphData.videoMovie.tags.size)
}
@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)
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
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)
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
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)
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
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)
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)
}
}
@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"))
}
// Sample HTML with video.movie-specific tags
private val videoMovieHtml = """
<!DOCTYPE html>
<html>
<head>
<title>Video Movie Example</title>
<meta property="og:title" content="The Matrix" />
<meta property="og:type" content="video.movie" />
<meta property="og:url" content="https://example.com/movies/the-matrix" />
<meta property="og:image" content="https://example.com/matrix-poster.jpg" />
<meta property="og:description" content="A sci-fi action movie" />
<meta property="og:video:actor" content="Keanu Reeves" />
<meta property="og:video:actor" content="Laurence Fishburne" />
<meta property="og:video:director" content="Lana Wachowski" />
<meta property="og:video:director" content="Lilly Wachowski" />
<meta property="og:video:writer" content="Lana Wachowski" />
<meta property="og:video:writer" content="Lilly Wachowski" />
<meta property="og:video:duration" content="136" />
<meta property="og:video:release_date" content="1999-03-31" />
<meta property="og:video:tag" content="sci-fi" />
<meta property="og:video:tag" content="action" />
</head>
<body>
<h1>The Matrix</h1>
</body>
</html>
""".trimIndent()
private val noTypeHtml = """
<!DOCTYPE html>
<html>
<head>
<title>Video Movie Example</title>
<meta property="og:title" content="The Matrix" />
</head>
<body>
<h1>The Matrix</h1>
</body>
</html>
""".trimIndent()
private val unknownTypeHtml = """
<!DOCTYPE html>
<html>
<head>
<title>Video Movie Example</title>
<meta property="og:title" content="The Matrix" />
<meta property="og:type" content="test" />
</head>
<body>
<h1>The Matrix</h1>
</body>
</html>
""".trimIndent()
@Test
fun `test parse with video movie-specific tags`() {
val openGraphData = parser.parse(videoMovieHtml)
// Verify basic properties
assertEquals("The Matrix", openGraphData.title)
assertEquals("video.movie", openGraphData.type)
assertEquals("https://example.com/movies/the-matrix", openGraphData.url)
assertEquals("A sci-fi action movie", openGraphData.description)
// Verify video.movie-specific properties
assertNotNull(openGraphData.videoMovie)
assertEquals(2, openGraphData.videoMovie.actors.size)
assertTrue(openGraphData.videoMovie.actors.contains("Keanu Reeves"))
assertTrue(openGraphData.videoMovie.actors.contains("Laurence Fishburne"))
assertEquals(2, openGraphData.videoMovie.director.size)
assertTrue(openGraphData.videoMovie.director.contains("Lana Wachowski"))
assertTrue(openGraphData.videoMovie.director.contains("Lilly Wachowski"))
assertEquals(2, openGraphData.videoMovie.writer.size)
assertTrue(openGraphData.videoMovie.writer.contains("Lana Wachowski"))
assertTrue(openGraphData.videoMovie.writer.contains("Lilly Wachowski"))
assertEquals(136, openGraphData.videoMovie.duration)
assertEquals("1999-03-31", openGraphData.videoMovie.releaseDate)
assertEquals(2, openGraphData.videoMovie.tags.size)
assertTrue(openGraphData.videoMovie.tags.contains("sci-fi"))
assertTrue(openGraphData.videoMovie.tags.contains("action"))
}
@Test
fun `test getType method returns correct enum values`() {
// Test video.movie type
val videoMovieData = parser.parse(videoMovieHtml)
assertEquals(Type.VIDEO_MOVIE, videoMovieData.getType())
// Test article type
val articleData = parser.parse(articleHtml)
assertEquals(Type.ARTICLE, articleData.getType())
// Test profile type
val profileData = parser.parse(profileHtml)
assertEquals(Type.PROFILE, profileData.getType())
// Test book type
val bookData = parser.parse(bookHtml)
assertEquals(Type.BOOK, bookData.getType())
// Test website type (should return UNKNOWN as it's not in our enum)
val websiteData = parser.parse(multipleImagesHtml)
assertEquals(Type.WEBSITE, websiteData.getType())
// Test no type defaults to Website
val noTypeData = parser.parse(noTypeHtml)
assertEquals(Type.WEBSITE, noTypeData.getType())
// Test unrecognized type is Unknown
val unkwownData = parser.parse(unknownTypeHtml)
assertEquals(Type.UNKNOWN, unkwownData.getType())
}
}

View File

@@ -1,6 +1,12 @@
pluginManagement {
plugins {
kotlin("jvm") version "2.1.21"
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
}
rootProject.name = "OpenGraphKt"
include("opengraphkt")
include("demo")
include("demo")
include("demo-remote")