Compare commits

...

29 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
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
13 changed files with 750 additions and 308 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

@@ -1,6 +1,11 @@
# OpenGraphKt
[![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.

View File

@@ -1,5 +1,6 @@
package fr.lengrand.opengraphkt
package fr.lengrand.opengraphktremote
import fr.lengrand.opengraphkt.OpenGraphParser
import org.jsoup.Jsoup
/**

View File

@@ -8,7 +8,7 @@ import java.net.URI
* Example demonstrating how to use the OpenGraphParser to extract Open Graph data from HTML.
*/
fun main() {
val parser = OpenGraphParser()
val parser = Parser()
// Example 1: Parse Open Graph data from a URL
println("Example 1: Parsing from URL")

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

@@ -2,11 +2,12 @@ import com.vanniktech.maven.publish.SonatypeHost
plugins {
kotlin("jvm")
id("com.vanniktech.maven.publish") version "0.30.0"
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

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

@@ -6,128 +6,6 @@ import org.jsoup.select.Elements
import java.io.File
import java.net.URL
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.
*
@@ -137,15 +15,15 @@ data class OpenGraphBook(
*
* @see <a href="https://ogp.me/">Open Graph Protocol</a>
*/
class OpenGraphParser {
class Parser {
/**
* Extracts all Open Graph tags from a JSoup Document and returns a structured OpenGraphData object.
* Extracts all Open Graph tags from a JSoup Document and returns a structured Data object.
*
* @param document The JSoup Document to parse
* @return An OpenGraphData object containing all extracted Open Graph data
* @return An Data object containing all extracted Open Graph data
*/
fun parse(document: Document): OpenGraphData {
fun parse(document: Document): Data {
val tags = document.select("meta[property^=og:]")
val openGraphTags = extractOpenGraphTags(tags)
@@ -153,62 +31,62 @@ class OpenGraphParser {
}
/**
* Extracts all Open Graph tags from a URL and returns a structured OpenGraphData object.
* 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 OpenGraphData object containing all extracted Open Graph data.
* @return An Data object containing all extracted Open Graph data.
*/
fun parse(url: URL) : OpenGraphData {
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 OpenGraphData object.
* 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 OpenGraphData object containing all extracted Open Graph data.
* @return An Data object containing all extracted Open Graph data.
*/
fun parse(html: String) : OpenGraphData {
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 OpenGraphData object.
* 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 OpenGraphData object containing all extracted Open Graph data.
* @return An Data object containing all extracted Open Graph data.
*/
fun parse(file: File, charset: String = "UTF-8") : OpenGraphData {
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 OpenGraphTag objects.
* 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 OpenGraphTag objects
* @return A list of Tag objects
*/
private fun extractOpenGraphTags(elements: Elements): List<OpenGraphTag> {
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")
OpenGraphTag(property, content)
Tag(property, content)
}
}
/**
* Builds an OpenGraphData object from a list of OpenGraphTag objects.
* Builds an Data object from a list of Tag objects.
*
* @param tags The list of OpenGraphTag objects
* @return An OpenGraphData object containing structured Open Graph data
* @param tags The list of Tag objects
* @return An Data object containing structured Open Graph data
*/
private fun buildOpenGraphData(tags: List<OpenGraphTag>): OpenGraphData {
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(":")) {
@@ -243,7 +121,17 @@ class OpenGraphParser {
// Build book-specific properties if the type is "book"
val book = if (type == "book") buildBook(groupedTags) else null
return OpenGraphData(
// 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,
@@ -258,39 +146,45 @@ class OpenGraphParser {
audios = audios,
article = article,
profile = profile,
book = book
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 OpenGraphTag objects
* @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<OpenGraphTag>, property: String): String? {
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 OpenGraphTag objects
* @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<OpenGraphTag>, property: String): List<String> {
private fun getTagsContent(tags: List<Tag>, property: String): List<String> {
return tags.filter { it.property == property }.map { it.content }
}
/**
* Builds a list of OpenGraphImage objects from image tags.
* Builds a list of Image objects from image tags.
*
* @param imageTags The list of image-related OpenGraphTag objects
* @return A list of OpenGraphImage objects
* @param imageTags The list of image-related Tag objects
* @return A list of Image objects
*/
private fun buildImages(imageTags: List<OpenGraphTag>): List<OpenGraphImage> {
private fun buildImages(imageTags: List<Tag>): List<Image> {
val baseImageTags = imageTags.filter {
it.property == "image" || it.property == "image:url"
}
@@ -300,7 +194,7 @@ class OpenGraphParser {
return emptyList()
}
val images = mutableListOf<OpenGraphImage>()
val images = mutableListOf<Image>()
// For each base image tag, create an image object and find its attributes
baseImageTags.forEach { baseTag ->
@@ -319,7 +213,7 @@ class OpenGraphParser {
val height = attributeTags.firstOrNull { it.property == "image:height" }?.content?.toIntOrNull()
val alt = attributeTags.firstOrNull { it.property == "image:alt" }?.content
images.add(OpenGraphImage(
images.add(Image(
url = baseTag.content,
secureUrl = secureUrl,
type = type,
@@ -333,12 +227,12 @@ class OpenGraphParser {
}
/**
* Builds a list of OpenGraphVideo objects from video tags.
* Builds a list of Video objects from video tags.
*
* @param videoTags The list of video-related OpenGraphTag objects
* @return A list of OpenGraphVideo objects
* @param videoTags The list of video-related Tag objects
* @return A list of Video objects
*/
private fun buildVideos(videoTags: List<OpenGraphTag>): List<OpenGraphVideo> {
private fun buildVideos(videoTags: List<Tag>): List<Video> {
val baseVideoTags = videoTags.filter {
it.property == "video" || it.property == "video:url"
}
@@ -348,7 +242,7 @@ class OpenGraphParser {
return emptyList()
}
val videos = mutableListOf<OpenGraphVideo>()
val videos = mutableListOf<Video>()
// For each base video tag, create a video object and find its attributes
baseVideoTags.forEach { baseTag ->
@@ -367,7 +261,7 @@ class OpenGraphParser {
val height = attributeTags.firstOrNull { it.property == "video:height" }?.content?.toIntOrNull()
val duration = attributeTags.firstOrNull { it.property == "video:duration" }?.content?.toIntOrNull()
videos.add(OpenGraphVideo(
videos.add(Video(
url = baseTag.content,
secureUrl = secureUrl,
type = type,
@@ -381,12 +275,12 @@ class OpenGraphParser {
}
/**
* Builds a list of OpenGraphAudio objects from audio tags.
* Builds a list of Audio objects from audio tags.
*
* @param audioTags The list of audio-related OpenGraphTag objects
* @return A list of OpenGraphAudio objects
* @param audioTags The list of audio-related Tag objects
* @return A list of Audio objects
*/
private fun buildAudios(audioTags: List<OpenGraphTag>): List<OpenGraphAudio> {
private fun buildAudios(audioTags: List<Tag>): List<Audio> {
val baseAudioTags = audioTags.filter {
it.property == "audio" || it.property == "audio:url"
}
@@ -396,7 +290,7 @@ class OpenGraphParser {
return emptyList()
}
val audios = mutableListOf<OpenGraphAudio>()
val audios = mutableListOf<Audio>()
// For each base audio tag, create an audio object and find its attributes
baseAudioTags.forEach { baseTag ->
@@ -412,7 +306,7 @@ class OpenGraphParser {
val secureUrl = attributeTags.firstOrNull { it.property == "audio:secure_url" }?.content
val type = attributeTags.firstOrNull { it.property == "audio:type" }?.content
audios.add(OpenGraphAudio(
audios.add(Audio(
url = baseTag.content,
secureUrl = secureUrl,
type = type
@@ -423,12 +317,12 @@ class OpenGraphParser {
}
/**
* Builds an OpenGraphArticle object from article-related tags.
* Builds an Article 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
* @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<OpenGraphTag>>): OpenGraphArticle? {
private fun buildArticle(groupedTags: Map<String, List<Tag>>): Article? {
val articleTags = groupedTags.getOrDefault("article", emptyList())
if (articleTags.isEmpty()) {
@@ -442,7 +336,7 @@ class OpenGraphParser {
val authors = articleTags.filter { it.property == "article:author" }.map { it.content }
val tags = articleTags.filter { it.property == "article:tag" }.map { it.content }
return OpenGraphArticle(
return Article(
publishedTime = publishedTime,
modifiedTime = modifiedTime,
expirationTime = expirationTime,
@@ -453,12 +347,12 @@ class OpenGraphParser {
}
/**
* Builds an OpenGraphProfile object from profile-related tags.
* Builds an Profile 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
* @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<OpenGraphTag>>): OpenGraphProfile? {
private fun buildProfile(groupedTags: Map<String, List<Tag>>): Profile? {
val profileTags = groupedTags.getOrDefault("profile", emptyList())
if (profileTags.isEmpty()) {
@@ -470,7 +364,7 @@ class OpenGraphParser {
val username = profileTags.firstOrNull { it.property == "profile:username" }?.content
val gender = profileTags.firstOrNull { it.property == "profile:gender" }?.content
return OpenGraphProfile(
return Profile(
firstName = firstName,
lastName = lastName,
username = username,
@@ -479,12 +373,12 @@ class OpenGraphParser {
}
/**
* Builds an OpenGraphBook object from book-related tags.
* Builds an Book 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
* @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<OpenGraphTag>>): OpenGraphBook? {
private fun buildBook(groupedTags: Map<String, List<Tag>>): Book? {
val bookTags = groupedTags.getOrDefault("book", emptyList())
if (bookTags.isEmpty()) {
@@ -496,11 +390,167 @@ class OpenGraphParser {
val releaseDate = bookTags.firstOrNull { it.property == "book:release_date" }?.content
val tags = bookTags.filter { it.property == "book:tag" }.map { it.content }
return OpenGraphBook(
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

@@ -7,9 +7,9 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class OpenGraphParserTest {
class ParserTest {
private val parser = OpenGraphParser()
private val parser = Parser()
// Sample HTML with all required OpenGraph tags and some structured properties
private val completeHtml = """
@@ -180,6 +180,15 @@ 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
@@ -296,4 +305,118 @@ class OpenGraphParserTest {
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

@@ -4,7 +4,7 @@ pluginManagement {
}
}
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")