diff --git a/.gitignore b/.gitignore
index 4654a1b..77fff54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,6 @@
/javadoc.xml
*.class
/*.jardesc
-*.jar
*.war
*.ear
/local.properties
@@ -17,5 +16,9 @@
*.iml
*.iws
-# Maven
+# Gradle
+**/.gradle
/build/
+/buildSrc/build/
+
+/CHANGES.txt
diff --git a/LICENSE.txt b/LICENSE.txt
index dfe0da1..f2dd46d 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005-2019, Carlos Amengual
+ * Copyright (c) 2005-2022, Carlos Amengual
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
index 464d7e2..9318a11 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,102 @@
# css4j - dom4j module
-Subclasses several [dom4j](https://dom4j.github.io/) classes and provides CSS functionality to it. Licence is BSD 3-clause.
+Subclasses several [dom4j](https://dom4j.github.io/) classes and provides CSS functionality to it.
-Please refer to the `css4j-dist` repository for build instructions.
+[License](LICENSE.txt) is BSD 3-clause.
+
+See the [latest Release Notes](RELEASE_NOTES.md).
+
+## Java™ Runtime Environment requirements
+All the classes in the binary package have been compiled with a [Java compiler](https://adoptium.net/)
+set to 1.7 compiler compliance level, except the `module-info.java` file.
+
+Building the library requires JDK 11 or higher.
+
+
+
+## Build from source
+To build css4j-dom4j from the code that is currently at the Git repository, Java
+11 or later is needed, although the resulting jar files can be run with a 1.7 JRE.
+
+You can run a variety of Gradle tasks with the Gradle wrapper (on Windows shells you can omit the `./`):
+
+- `./gradlew build` (normal build)
+- `./gradlew build publishToMavenLocal` (to install in local Maven repository)
+- `./gradlew copyJars` (to copy jar files into a top-level _jar_ directory)
+- `./gradlew lineEndingConversion` (to convert line endings of top-level text files to CRLF)
+- `./gradlew publish` (to deploy to a Maven repository, as described in the `publishing.repositories.maven` block of
+[build.gradle](https://github.com/css4j/css4j-dom4j/blob/1-stable/build.gradle))
+
+
+
+## Usage from a Gradle project
+If your Gradle project depends on css4j-dom4j, you can use this project's own Maven repository in a `repositories` section of
+your build file:
+```groovy
+repositories {
+ maven {
+ url "https://css4j.github.io/maven/"
+ mavenContent {
+ releasesOnly()
+ }
+ content {
+ includeGroup 'io.sf.carte'
+ includeGroup 'io.sf.jclf'
+ includeGroup 'xmlpull'
+ includeGroup 'xpp3'
+ }
+ }
+}
+```
+please use this repository **only** for the artifact groups listed in the `includeGroup` statements.
+
+Then, in your `build.gradle` file:
+```groovy
+dependencies {
+ api "io.sf.carte:css4j-dom4j:${css4jDom4jVersion}"
+}
+```
+where `css4jDom4jVersion` would be defined in a `gradle.properties` file.
+
+
+
+## Software dependencies
+
+In case that you do not use a Gradle or Maven build (which would manage the
+dependencies according to the relevant `.module` or `.pom` files), the required
+and optional library packages are the following:
+
+### Compile-time dependencies
+
+- The [css4j](https://github.com/css4j/css4j/releases) library (and its transitive
+ dependencies); version 1.3.1 or higher (but below 2.0) is recommended.
+
+- The [css4j-agent](https://github.com/css4j/css4j-agent/releases) library;
+ version 1.3.0 or higher (but below 2.0) is recommended.
+ **It is optional at runtime.**
+
+- The [dom4j](https://github.com/dom4j/dom4j) JAR package (tested with 2.1.1).
+ Requires at least version 2.0 to compile and 2.1.1 to run the tests, but you
+ should be able to run the resulting jar file with dom4j 1.6 if you are stuck with it.
+
+- The [XPP3 Pull Parser](https://github.com/xmlpull-xpp3/xmlpull-xpp3) (which
+ can be used with this library but beware that it does not support [character
+ entities](https://dev.w3.org/html5/html-author/charref)).
+ **It is optional at runtime.**
+
+### Test dependencies
+
+- A recent version of [JUnit 4](https://junit.org/junit4/).
+
+- The `batik-css`, `batik-util`and `batik-i18n` artifacts from
+ [Apache Batik](https://xmlgraphics.apache.org/batik/).
+
+- [Jaxen](https://github.com/jaxen-xpath/jaxen), this software was tested with
+ version 1.2.0.
+
+- [SLF4J](http://www.slf4j.org/), which is a logging package.
+
+
+
+## Website
+For more information please visit https://css4j.github.io/
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
new file mode 100644
index 0000000..41fa65a
--- /dev/null
+++ b/RELEASE_NOTES.md
@@ -0,0 +1,27 @@
+# css4j-dom4j version 1.3.0 Release Notes
+
+### February 15, 2022
+
+
+
+## Release Highlights
+
+### `:dir()` pseudo-class
+
+The `:dir()` pseudo-class is now partially supported in computed styles.
+
+Full support could not be implemented due to dom4j's limited DOM support.
+
+Users are advised to switch to css4j's native DOM if they need a full `:dir`
+implementation.
+
+### Build
+
+The build system was switched from Maven to Gradle.
+
+
+## Project Sites
+
+Project home: https://css4j.github.io/
+
+Development site: https://github.com/css4j/css4j-dom4j
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..87854d7
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,288 @@
+plugins {
+ id 'java-library'
+ id 'maven-publish'
+ id 'org.gradlex.extra-java-module-info' version '1.2'
+}
+
+group = 'io.sf.carte'
+version = '1.3.1'
+
+description = 'css4j-dom4j'
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ withJavadocJar()
+ withSourcesJar()
+ registerFeature('xmlpull') {
+ usingSourceSet(sourceSets.main)
+ }
+ registerFeature('useragent') {
+ usingSourceSet(sourceSets.main)
+ }
+}
+
+dependencies {
+ api('io.sf.carte:css4j') {
+ version {
+ strictly '[1.3.0,2.0['
+ prefer '1.3.1'
+ }
+ }
+ useragentImplementation('io.sf.carte:css4j-agent') {
+ version {
+ strictly '[1.2.0,2.0['
+ prefer '1.3.0'
+ }
+ }
+ /*
+ * Any dom4j version can be used, but tests may not pass with versions prior
+ * to 2.1.1, due to changes in dom4j's defaults.
+ */
+ api('org.dom4j:dom4j') {
+ version {
+ require '[1.6.0,)'
+ prefer '2.1.4'
+ }
+ }
+ xmlpullImplementation 'xmlpull:xmlpull:1.2.0'
+ xmlpullImplementation 'xpp3:xpp3_min:1.2.0'
+ testImplementation(group: 'io.sf.carte', name: 'css4j', classifier: 'tests') {
+ version {
+ strictly '[1.3.0,2.0['
+ prefer '1.3.1'
+ }
+ }
+ testImplementation 'org.apache.xmlgraphics:batik-css:1.15'
+ testImplementation 'org.apache.xmlgraphics:batik-util:1.15'
+ testImplementation 'org.apache.xmlgraphics:batik-i18n:1.15'
+ testImplementation 'jaxen:jaxen:1.2.0'
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ require '[1.7.28,)'
+ prefer '2.0.6'
+ }
+ }
+ testImplementation 'junit:junit:4.13.2'
+}
+
+extraJavaModuleInfo {
+ failOnMissingModuleInfo.set(false)
+ automaticModule('org.dom4j:dom4j', 'org.dom4j')
+ automaticModule('nu.validator:htmlparser', 'htmlparser')
+ automaticModule('org.w3c.css:sac', 'sac')
+}
+
+repositories {
+ maven {
+ url = uri('https://repo.maven.apache.org/maven2/')
+ }
+ maven {
+ url "https://css4j.github.io/maven/"
+ mavenContent {
+ releasesOnly()
+ }
+ content {
+ includeGroup 'io.sf.carte'
+ includeGroup 'io.sf.jclf'
+ includeGroup 'xmlpull'
+ includeGroup 'xpp3'
+ }
+ }
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs = ['src']
+ includes += ["**/*.java"]
+ }
+ resources {
+ srcDirs = ['src']
+ excludes += ["**/*.java"]
+ }
+ }
+ test {
+ java {
+ srcDirs = ['junit']
+ includes += ["**/*.java"]
+ }
+ resources {
+ srcDirs = ['junit']
+ excludes += ["**/*.java"]
+ }
+ }
+}
+
+tasks.register('testsJar', Jar) {
+ archiveClassifier = 'tests'
+ from(sourceSets.test.output)
+}
+
+configurations {
+ tests
+}
+
+artifacts {
+ tests testsJar
+}
+
+tasks.compileJava {
+ excludes += ['module-info.java']
+ modularity.inferModulePath = false
+}
+
+tasks.register('compileModuleInfo', JavaCompile) {
+ description = 'Compile module-info to Java 11 bytecode'
+ dependsOn tasks.compileJava
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ source = sourceSets.main.java
+ classpath = sourceSets.main.compileClasspath
+ destinationDirectory = sourceSets.main.java.destinationDirectory
+ modularity.inferModulePath = true
+ includes = ['module-info.java']
+}
+
+classes.dependsOn compileModuleInfo
+
+// Check bytecode version, in case some other task screws it
+tasks.register('checkLegacyJava') {
+ description = 'Check that classes are Java 7 bytecode (except module-info)'
+ def classdir = sourceSets.main.output.classesDirs.files.stream().findAny().get()
+ def classfiles = fileTree(classdir).matching({it.exclude('module-info.class')}).files
+ doFirst() {
+ if (!classfiles.isEmpty()) {
+ def classfile = classfiles.stream().findAny().get()
+ if (classfile != null) {
+ def classbytes = classfile.bytes
+ def bcversion = classbytes[6] * 128 + classbytes[7]
+ if (bcversion != 51) {
+ throw new GradleException("Bytecode on " + classfile +
+ " is not valid Java 7. Version should be 51, instead is " + bcversion)
+ }
+ }
+ }
+ }
+}
+
+classes.finalizedBy checkLegacyJava
+
+// Copy jar files to 'jar' directory
+tasks.register('copyJars', Copy) {
+ description = 'Copy jar files to \'jar\' directory'
+ dependsOn tasks.build
+ dependsOn 'testsJar'
+ include '**/*.jar'
+ excludes = ['slf4j*.jar']
+ from layout.buildDirectory.dir("libs")
+ from configurations.runtimeClasspath
+ into "${rootDir}/jar"
+}
+
+tasks.register('lineEndingConversion', CRLFConvert) {
+ file "$rootDir/LICENSE.txt"
+ file "$rootDir/CHANGES.txt"
+ file "$rootDir/RELEASE_NOTES.md"
+}
+
+tasks.register('cleanBuildSrc') {
+ description = 'Clean the buildSrc directory'
+ doLast {
+ delete("$rootDir/buildSrc/build")
+ }
+}
+
+tasks.named('clean') {
+ finalizedBy('cleanBuildSrc')
+}
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+tasks.withType(Javadoc) {
+ options.addStringOption('Xdoclint:none', '-quiet')
+ options.addStringOption('encoding', 'UTF-8')
+ options.addStringOption('charset', 'UTF-8')
+ options.overview = 'src/overview.html'
+ options.links 'https://docs.oracle.com/en/java/javase/11/docs/api/'
+ options.links 'https://css4j.github.io/api/1.0/'
+ options.links 'https://dom4j.github.io/javadoc/2.1.1/'
+}
+
+tasks.withType(AbstractArchiveTask).configureEach {
+ // Reproducible build
+ preserveFileTimestamps = false
+ reproducibleFileOrder = true
+ // Copy license file
+ dependsOn lineEndingConversion
+ from ('LICENSE.txt') {
+ into 'META-INF'
+ }
+}
+
+tasks.withType(PublishToMavenRepository) { task ->
+ doFirst {
+ if (repository == publishing.repositories.getByName('mavenRepo')) {
+ logger.lifecycle "Deploying artifacts to \"${it.repository.url}\""
+ }
+ }
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ description = 'css4j DOM4J module'
+ from(components.java)
+ suppressAllPomMetadataWarnings()
+ artifact(testsJar)
+ pom {
+ description = 'css4j DOM4J module'
+ url = "https://github.com/css4j/css4j-dom4j/"
+ licenses {
+ license {
+ name = "BSD 3-clause license"
+ url = "https://css4j.github.io/LICENSE.txt"
+ }
+ }
+ }
+ }
+ }
+ repositories {
+ maven {
+ name = 'mavenRepo'
+ /*
+ * The following section applies to the 'publish' task:
+ *
+ * If you plan to deploy to a repository, please configure the
+ * 'mavenReleaseRepoUrl' and/or 'mavenSnapshotRepoUrl' properties
+ * (for example in GRADLE_USER_HOME/gradle.properties).
+ *
+ * Otherwise, Gradle shall create a 'build/repository' subdirectory
+ * at ${rootDir} and deploy there.
+ *
+ * Properties 'mavenRepoUsername' and 'mavenRepoPassword' can also
+ * be set (generally from command line).
+ */
+ def releasesUrl
+ def snapshotsUrl
+ if (project.hasProperty('mavenReleaseRepoUrl') && project.mavenReleaseRepoUrl) {
+ releasesUrl = mavenReleaseRepoUrl
+ } else {
+ releasesUrl = "${buildDir}/repository/releases"
+ }
+ if (project.hasProperty('mavenSnapshotRepoUrl') && project.mavenSnapshotRepoUrl) {
+ snapshotsUrl = mavenSnapshotRepoUrl
+ } else {
+ snapshotsUrl = "${buildDir}/repository/snapshots"
+ }
+ url = version.endsWith('-SNAPSHOT') ? snapshotsUrl : releasesUrl
+ if (project.hasProperty('mavenRepoUsername') &&
+ project.hasProperty('mavenRepoPassword')) {
+ credentials.username = mavenRepoUsername
+ credentials.password = mavenRepoPassword
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/groovy/CRLFConvert.groovy b/buildSrc/src/main/groovy/CRLFConvert.groovy
new file mode 100644
index 0000000..d6b941d
--- /dev/null
+++ b/buildSrc/src/main/groovy/CRLFConvert.groovy
@@ -0,0 +1,43 @@
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Converts line endings to CRLF (Windows)
+ *
+ * Usage: + *
+ *
+ * tasks.register('lineEndingConversion', CRLFConvert) {
+ * file "path/to/file1.txt"
+ * file "path/to/fileN.txt"
+ * }
+ *
+ */
+class CRLFConvert extends DefaultTask {
+
+ private static final String CRLF = "\r\n"
+ private static final String LF = "\n"
+
+ private files = []
+
+ @TaskAction
+ def action() {
+ files.each { path ->
+ File file = new File(path)
+ if (file.exists()) {
+ String content = file.text
+ String newContent = content.replaceAll(/\r\n/, LF)
+ newContent = newContent.replaceAll(/\n|\r/, CRLF)
+ if (content != newContent) {
+ file.write(newContent)
+ }
+ } else {
+ logger.warn('File ' + path + ' does not exist.')
+ }
+ }
+ }
+
+ def file(String path) {
+ this.files << path
+ }
+}
diff --git a/changes.sh b/changes.sh
new file mode 100755
index 0000000..9562880
--- /dev/null
+++ b/changes.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# This script writes the changelog in a human-readable format.
+#
+# You'll probably want to edit manually the result of executing the script.
+#
+if [[ $# -eq 0 ]] ; then
+ echo "No version supplied (e.g. '1.3.1')"
+ exit 1
+fi
+OLDTAG=`git tag -l --merged 1-stable --sort=-taggerdate|head -1`
+echo "Writing changes from tag $OLDTAG"
+TITLE="CSS4J-DOM4J Module changes"
+VERHDR="Version ${1}"
+OUTFILE="CHANGES.txt"
+echo -en "${TITLE}\\r\\n${TITLE//?/=}\\r\\n\\r\\n${VERHDR}\\r\\n${VERHDR//?/-}\\r\\n\\r\\n">${OUTFILE}
+git log --reverse --pretty=format:%s ${OLDTAG}..|sed -e 's/^/- /'|fold -s|sed -r 's/^([^-])/ \1/'|sed -e 's/$/\r/'>>${OUTFILE}
+echo -en "\\n">>${OUTFILE}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ae04661
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..a69d9cb
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# 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
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+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"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+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.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ 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.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# 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.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f127cfd
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,91 @@
+@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% equ 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% 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
diff --git a/junit/io/sf/carte/doc/dom4j/BaseURLElementTest.java b/junit/io/sf/carte/doc/dom4j/BaseURLElementTest.java
index f8d1df3..2095ad5 100644
--- a/junit/io/sf/carte/doc/dom4j/BaseURLElementTest.java
+++ b/junit/io/sf/carte/doc/dom4j/BaseURLElementTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -75,10 +75,10 @@ public void childAddedNode() throws Exception {
Reader re = DOMCSSStyleSheetFactoryTest.sampleHTMLReader();
InputSource isrc = new InputSource(re);
CSSDocument xhtmlDoc = XHTMLDocumentFactoryTest.parseXML(isrc);
+ re.close();
assertNotNull(xhtmlDoc.getBaseURL());
assertEquals("http://www.example.com/",
xhtmlDoc.getBaseURL().toExternalForm());
- re.close();
}
@Test
diff --git a/junit/io/sf/carte/doc/dom4j/CSSStylableElementTest.java b/junit/io/sf/carte/doc/dom4j/CSSStylableElementTest.java
index 2254766..e69ee32 100644
--- a/junit/io/sf/carte/doc/dom4j/CSSStylableElementTest.java
+++ b/junit/io/sf/carte/doc/dom4j/CSSStylableElementTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -67,13 +67,14 @@ public void getStyle2() {
assertNull(body.getStyle());
assertFalse(body.hasAttributes());
xhtmlDoc.getDocumentElement().appendChild(body);
- body.setAttribute("style", "font-family: Arial");
+ body.setAttribute("style", "font-family:Arial");
assertTrue(body.hasAttributes());
assertTrue(body.hasAttribute("style"));
- assertEquals("font-family: Arial; ", body.getAttribute("style"));
+ assertEquals("font-family:Arial", body.getAttribute("style"));
CSSStyleDeclaration style = body.getStyle();
assertNotNull(style);
assertEquals(1, style.getLength());
+ assertEquals("font-family: Arial; ", body.getAttribute("style"));
assertEquals("font-family: Arial; ", style.getCssText());
style.setCssText("font-family: Helvetica");
assertEquals("font-family: Helvetica; ", style.getCssText());
diff --git a/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleDeclarationTest.java b/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleDeclarationTest.java
index d651b76..c70c833 100644
--- a/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleDeclarationTest.java
+++ b/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleDeclarationTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
diff --git a/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleSheetTest.java b/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleSheetTest.java
index f5b99ea..fcc83d0 100644
--- a/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleSheetTest.java
+++ b/junit/io/sf/carte/doc/dom4j/DOM4JCSSStyleSheetTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
diff --git a/junit/io/sf/carte/doc/dom4j/DOM4JSelectorMatcherTest.java b/junit/io/sf/carte/doc/dom4j/DOM4JSelectorMatcherTest.java
index ca7b1cc..60ec4a9 100644
--- a/junit/io/sf/carte/doc/dom4j/DOM4JSelectorMatcherTest.java
+++ b/junit/io/sf/carte/doc/dom4j/DOM4JSelectorMatcherTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -11,6 +11,7 @@
package io.sf.carte.doc.dom4j;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
@@ -19,6 +20,7 @@
import java.util.LinkedList;
import java.util.List;
+import org.dom4j.DocumentType;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -157,6 +159,42 @@ public void testMatchSelector1Class() throws Exception {
document.getDocumentElement().add(elm);
SelectorMatcher matcher = elm.getSelectorMatcher();
assertTrue(matcher.matches(selist) >= 0);
+ // Case insensitive
+ elm.setAttribute("class", "exampleClass");
+ assertEquals(0, matcher.matches(selist));
+ }
+
+ @Test
+ public void testMatchSelector1ClassCI() throws Exception {
+ AbstractCSSStyleSheet css = parseStyle(".exampleClass {color: blue;}");
+ CSSStyleDeclarationRule rule = (CSSStyleDeclarationRule) css.getCssRules().item(0);
+ SelectorList selist = CSSOMBridge.getSelectorList(rule);
+ CSSStylableElement elm = createElement("p");
+ elm.addAttribute("class", "exampleclass");
+ document.getDocumentElement().add(elm);
+ SelectorMatcher matcher = elm.getSelectorMatcher();
+ assertTrue(matcher.matches(selist) >= 0);
+ // Case insensitive
+ elm.setAttribute("class", "exampleClass");
+ assertEquals(0, matcher.matches(selist));
+ }
+
+ @Test
+ public void testMatchSelector1ClassStrict() throws Exception {
+ DocumentType docType = factory.createDocType("html", null, null);
+ document.setDocType(docType);
+ AbstractCSSStyleSheet css = parseStyle(".exampleclass {color: blue;}");
+ CSSStyleDeclarationRule rule = (CSSStyleDeclarationRule) css.getCssRules().item(0);
+ SelectorList selist = CSSOMBridge.getSelectorList(rule);
+ CSSStylableElement elm = createElement("p");
+ elm.addAttribute("class", "exampleclass");
+ document.getDocumentElement().add(elm);
+ SelectorMatcher matcher = elm.getSelectorMatcher();
+ assertTrue(matcher.matches(selist) >= 0);
+ // Case sensitive
+ elm.setAttribute("class", "exampleClass");
+ matcher = elm.getSelectorMatcher();
+ assertEquals(-1, matcher.matches(selist));
}
@Test
@@ -287,6 +325,44 @@ public void testMatchSelector1Id() throws Exception {
document.getDocumentElement().add(elm);
SelectorMatcher matcher = elm.getSelectorMatcher();
assertTrue(matcher.matches(selist) >= 0);
+ // Case insensitive
+ elm.addAttribute("id", "exampleId");
+ assertTrue(matcher.matches(selist) >= 0);
+ }
+
+ @Test
+ public void testMatchSelector1IdCI() throws Exception {
+ AbstractCSSStyleSheet css = parseStyle("#exampleId {color: blue;}");
+ CSSStyleDeclarationRule rule = (CSSStyleDeclarationRule) css.getCssRules().item(0);
+ SelectorList selist = CSSOMBridge.getSelectorList(rule);
+ CSSStylableElement elm = createElement("p");
+ elm.addAttribute("class", "exampleclass");
+ elm.addAttribute("id", "exampleid");
+ document.getDocumentElement().add(elm);
+ SelectorMatcher matcher = elm.getSelectorMatcher();
+ assertTrue(matcher.matches(selist) >= 0);
+ // Case insensitive
+ elm.addAttribute("id", "exampleId");
+ assertTrue(matcher.matches(selist) >= 0);
+ }
+
+ @Test
+ public void testMatchSelector1IdCIStrict() throws Exception {
+ DocumentType docType = factory.createDocType("html", null, null);
+ document.setDocType(docType);
+ AbstractCSSStyleSheet css = parseStyle("#exampleId {color: blue;}");
+ CSSStyleDeclarationRule rule = (CSSStyleDeclarationRule) css.getCssRules().item(0);
+ SelectorList selist = CSSOMBridge.getSelectorList(rule);
+ CSSStylableElement elm = createElement("p");
+ elm.addAttribute("class", "exampleclass");
+ elm.addAttribute("id", "exampleid");
+ document.getDocumentElement().add(elm);
+ SelectorMatcher matcher = elm.getSelectorMatcher();
+ assertEquals(-1, matcher.matches(selist));
+ // Case insensitive
+ elm.addAttribute("id", "exampleId");
+ matcher = elm.getSelectorMatcher();
+ assertEquals(0, matcher.matches(selist));
}
@Test
@@ -486,6 +562,50 @@ public void testMatchSelectorPseudoOfType() throws Exception {
assertTrue(matcher.matches(selist) < 0);
}
+ @Test
+ public void testMatchSelectorPseudoAnyLink() throws Exception {
+ CSSStylableElement a = createElement("a");
+ a.setAttribute("href", "foo");
+ CSSStylableElement elm = document.getDocumentElement();
+ elm.appendChild(a);
+ SelectorMatcher matcher = a.getSelectorMatcher();
+ AbstractCSSStyleSheet css = parseStyle(":any-link {color: blue;}");
+ CSSStyleDeclarationRule rule = (CSSStyleDeclarationRule) css.getCssRules().item(0);
+ SelectorList selist = CSSOMBridge.getSelectorList(rule);
+ assertEquals(0, matcher.matches(selist));
+ //
+ matcher = elm.getSelectorMatcher();
+ assertEquals(-1, matcher.matches(selist));
+ elm.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "bar");
+ assertEquals(0, matcher.matches(selist));
+ //
+ a.removeAttribute("href");
+ matcher = a.getSelectorMatcher();
+ assertEquals(-1, matcher.matches(selist));
+ }
+
+ @Test
+ public void testMatchSelectorPseudoLink() throws Exception {
+ CSSStylableElement a = createElement("a");
+ a.setAttribute("href", "foo");
+ CSSStylableElement elm = document.getDocumentElement();
+ elm.appendChild(a);
+ SelectorMatcher matcher = a.getSelectorMatcher();
+ AbstractCSSStyleSheet css = parseStyle(":link {color: blue;}");
+ CSSStyleDeclarationRule rule = (CSSStyleDeclarationRule) css.getCssRules().item(0);
+ SelectorList selist = CSSOMBridge.getSelectorList(rule);
+ assertEquals(0, matcher.matches(selist));
+ //
+ matcher = elm.getSelectorMatcher();
+ assertEquals(-1, matcher.matches(selist));
+ elm.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "bar");
+ assertEquals(0, matcher.matches(selist));
+ //
+ a.removeAttribute("href");
+ matcher = a.getSelectorMatcher();
+ assertEquals(-1, matcher.matches(selist));
+ }
+
@Test
public void testMatchSelectorPseudoRoot() throws Exception {
CSSStylableElement root = document.getDocumentElement();
diff --git a/junit/io/sf/carte/doc/dom4j/DOM4JUserAgentTest.java b/junit/io/sf/carte/doc/dom4j/DOM4JUserAgentTest.java
index d6b9f22..bdc4e42 100644
--- a/junit/io/sf/carte/doc/dom4j/DOM4JUserAgentTest.java
+++ b/junit/io/sf/carte/doc/dom4j/DOM4JUserAgentTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
diff --git a/junit/io/sf/carte/doc/dom4j/DirMatcherTest.java b/junit/io/sf/carte/doc/dom4j/DirMatcherTest.java
new file mode 100644
index 0000000..12dbae8
--- /dev/null
+++ b/junit/io/sf/carte/doc/dom4j/DirMatcherTest.java
@@ -0,0 +1,110 @@
+/*
+
+ Copyright (c) 2005-2022, Carlos Amengual.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+ Licensed under a BSD-style License. You can find the license here:
+ https://css4j.github.io/LICENSE.txt
+
+ */
+
+package io.sf.carte.doc.dom4j;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.dom4j.DocumentException;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.css.sac.SelectorList;
+
+import io.sf.carte.doc.style.css.om.DOMCSSStyleSheetFactoryTest;
+import io.sf.carte.doc.style.css.parser.CSSParser;
+
+public class DirMatcherTest {
+
+ XHTMLDocument document;
+
+ @Before
+ public void setUp() throws IOException, DocumentException {
+ document = TestDocumentFactory.loadDocument(DOMCSSStyleSheetFactoryTest.directionalityHTMLReader());
+ }
+
+ @Test
+ public void testMatchDirectionality() {
+ // Prepare selectors
+ CSSParser parser = new CSSParser();
+ SelectorList ltr = parser.parseSelectors(":dir(ltr)");
+ SelectorList rtl = parser.parseSelectors(":dir(rtl)");
+ //
+ CSSStylableElement elm = document.getElementById("head");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("h1");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("para1");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("para2");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("h2");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("tableid");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("cell12");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("tr1");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("form1");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("username");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("phonelabel");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("telephone");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ /* FAIL
+ elm = document.getElementById("textareartl");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("textarealtr");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("textareaempty");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ */
+ elm = document.getElementById("bdirtl");
+ assertTrue(elm.matches(rtl, null));
+ assertFalse(elm.matches(ltr, null));
+ elm = document.getElementById("bdiltr");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("bdiauto");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("bdiempty");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("bdiautoempty");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ elm = document.getElementById("bdibadempty");
+ assertFalse(elm.matches(rtl, null));
+ assertTrue(elm.matches(ltr, null));
+ }
+
+}
diff --git a/junit/io/sf/carte/doc/dom4j/LinkElementTest.java b/junit/io/sf/carte/doc/dom4j/LinkElementTest.java
index 12daea5..58f54c2 100644
--- a/junit/io/sf/carte/doc/dom4j/LinkElementTest.java
+++ b/junit/io/sf/carte/doc/dom4j/LinkElementTest.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -17,11 +17,15 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import java.net.MalformedURLException;
+
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.junit.Before;
import org.junit.Test;
+import io.sf.carte.doc.style.css.om.AbstractCSSStyleSheet;
+
public class LinkElementTest {
XHTMLDocument xDoc = null;
@@ -30,12 +34,10 @@ public class LinkElementTest {
QName link_qname = null;
- LinkElement linkElement = null;
-
@Before
public void setUp() {
XHTMLDocumentFactory factory = new TestDocumentFactory();
- headElement = (HeadElement) factory.createElement("head");
+ headElement = (HeadElement) factory.createElement("head", XHTMLDocument.XHTML_NAMESPACE_URI);
xDoc = factory.createDocument(headElement);
link_qname = new QName("link", new Namespace("", XHTMLDocument.XHTML_NAMESPACE_URI));
link_qname.setDocumentFactory(factory);
@@ -44,7 +46,7 @@ public void setUp() {
@Test
public void addRemove() {
assertEquals(0, xDoc.linkedStyle.size());
- linkElement = (LinkElement) headElement.addElement(link_qname);
+ LinkElement linkElement = (LinkElement) headElement.addElement(link_qname);
assertEquals(1, xDoc.linkedStyle.size());
headElement.remove(linkElement);
assertEquals(0, xDoc.linkedStyle.size());
@@ -52,7 +54,7 @@ public void addRemove() {
@Test
public void addRemoveAttribute() {
- linkElement = (LinkElement) headElement.addElement(link_qname);
+ LinkElement linkElement = (LinkElement) headElement.addElement(link_qname);
xDoc.getStyleSheet();
int iniSerial = xDoc.getStyleCacheSerial();
linkElement.addAttribute("href", "http://www.example.com/css/example.css");
@@ -74,7 +76,7 @@ public void addRemoveAttribute() {
@Test
public void attributeSetValue() {
- linkElement = (LinkElement) headElement.addElement(link_qname);
+ LinkElement linkElement = (LinkElement) headElement.addElement(link_qname);
linkElement.addAttribute("rel", "stylesheet");
linkElement.addAttribute("href", "http://www.example.com/css/example.css");
xDoc.getStyleSheet();
@@ -85,4 +87,47 @@ public void attributeSetValue() {
assertEquals(iniSerial, xDoc.getStyleCacheSerial());
}
+ @Test
+ public void getSheet() throws MalformedURLException {
+ LinkElement linkElement = (LinkElement) headElement.addElement(link_qname);
+ linkElement.addAttribute("rel", "stylesheet");
+ linkElement.addAttribute("href", "http://www.example.com/css/common.css");
+ xDoc.getStyleSheet();
+ assertFalse(xDoc.getErrorHandler().hasErrors());
+ AbstractCSSStyleSheet sheet = linkElement.getSheet();
+ assertNotNull(sheet);
+ assertEquals(3, sheet.getCssRules().getLength());
+ //
+ int iniSerial = xDoc.getStyleCacheSerial();
+ linkElement.attribute("href").setValue("jar:http://www.example.com/evil.jar!/file");
+ sheet = linkElement.getSheet();
+ assertNotNull(sheet);
+ assertEquals(0, sheet.getCssRules().getLength());
+ assertTrue(xDoc.getErrorHandler().hasErrors());
+ assertTrue(xDoc.getErrorHandler().hasPolicyErrors());
+ iniSerial++;
+ assertEquals(iniSerial, xDoc.getStyleCacheSerial());
+ // Setting BASE does not change things
+ xDoc.getErrorHandler().reset();
+ CSSStylableElement base = xDoc.getDocumentFactory().createElement("base");
+ base.setAttribute("href", "jar:http://www.example.com/evil.jar!/dir/file");
+ headElement.appendChild(base);
+ sheet = linkElement.getSheet();
+ assertNotNull(sheet);
+ assertEquals(0, sheet.getCssRules().getLength());
+ assertTrue(xDoc.getErrorHandler().hasErrors());
+ assertTrue(xDoc.getErrorHandler().hasPolicyErrors());
+ // Set document URI to enable policy enforcement
+ xDoc.setDocumentURI("http://www.example.com/example.html");
+ //
+ xDoc.getErrorHandler().reset();
+ linkElement.attribute("href").setValue("file:/dev/zero");
+ sheet = linkElement.getSheet();
+ assertNotNull(sheet);
+ assertEquals(0, sheet.getCssRules().getLength());
+ assertTrue(xDoc.getErrorHandler().hasErrors());
+ assertTrue(xDoc.getErrorHandler().hasPolicyErrors());
+ assertEquals(iniSerial, xDoc.getStyleCacheSerial());
+ }
+
}
diff --git a/junit/io/sf/carte/doc/dom4j/PermissiveErrorHandler.java b/junit/io/sf/carte/doc/dom4j/PermissiveErrorHandler.java
new file mode 100644
index 0000000..06d8a1e
--- /dev/null
+++ b/junit/io/sf/carte/doc/dom4j/PermissiveErrorHandler.java
@@ -0,0 +1,37 @@
+/*
+
+ Copyright (c) 2020-2022, Carlos Amengual.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+ Licensed under a BSD-style License. You can find the license here:
+ https://css4j.github.io/LICENSE.txt
+
+ */
+
+package io.sf.carte.doc.dom4j;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+class PermissiveErrorHandler implements ErrorHandler {
+
+ public PermissiveErrorHandler() {
+ super();
+ }
+
+ @Override
+ public void warning(SAXParseException exception) throws SAXException {
+ }
+
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ }
+
+ @Override
+ public void fatalError(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+
+}
diff --git a/junit/io/sf/carte/doc/dom4j/PseudoClassTest.java b/junit/io/sf/carte/doc/dom4j/PseudoClassTest.java
new file mode 100644
index 0000000..59bbdf0
--- /dev/null
+++ b/junit/io/sf/carte/doc/dom4j/PseudoClassTest.java
@@ -0,0 +1,145 @@
+/*
+
+ Copyright (c) 2005-2022, Carlos Amengual.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+ Licensed under a BSD-style License. You can find the license here:
+ https://css4j.github.io/LICENSE.txt
+
+ */
+
+package io.sf.carte.doc.dom4j;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.dom4j.DocumentException;
+import org.dom4j.io.SAXReader;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import io.sf.carte.doc.style.css.CSSComputedProperties;
+import io.sf.carte.doc.style.css.CSSDocument;
+import io.sf.carte.doc.style.css.CSSElement;
+import io.sf.carte.doc.xml.dtd.DefaultEntityResolver;
+
+public class PseudoClassTest {
+
+ private static XHTMLDocument htmlDoc;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws DocumentException, SAXException, IOException {
+ TestDocumentFactory factory = new TestDocumentFactory();
+ factory.getDefaultStyleSheet(CSSDocument.ComplianceMode.STRICT).getCssRules().clear();
+ SAXReader reader = new SAXReader(factory);
+ reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true);
+ reader.setEntityResolver(new DefaultEntityResolver());
+ String str = "Test | . |
. | . |
. | . |
. | . |
. | . |
+ * This is also the {@code XHTMLDocument} object used to create new nodes. + *
+ * + * @return the {@code XHTMLDocument} object which is the root ancestor of this + * node, or {@code null} if this node is an {@code XHTMLDocument}, a + * {@code DocumentType} which is not used inside any + * {@code XHTMLDocument} yet, or this node is not part of a document. + */ @Override public XHTMLDocument getOwnerDocument() { return (XHTMLDocument) super.getDocument(); @@ -87,10 +101,40 @@ protected XHTMLDocumentFactory getDocumentFactory() { return (XHTMLDocumentFactory) super.getDocumentFactory(); } + @Override + public Attr setAttributeNode(Attr newAttr) throws DOMException { + Attribute attribute = attribute(newAttr); + if (attribute != newAttr) { + if (newAttr.getOwnerElement() != null) { + throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, "Attribute is already in use"); + } + if (attribute != null) { + attribute.detach(); + add((Attribute) newAttr); + if (attribute instanceof Attr) { + return (Attr) attribute; + } + } else { + add((Attribute) newAttr); + } + } + return null; + } + + @Override + public Attr setAttributeNodeNS(Attr newAttr) throws DOMException { + return setAttributeNode(newAttr); + } + + @Override + protected Attribute attribute(Attr attr) { + return attribute(((DefaultAttribute) attr).getQName()); + } + @Override public String getBaseURI() { URL baseURL = getOwnerDocument().getBaseURL(); - if(baseURL != null) { + if (baseURL != null) { return baseURL.toExternalForm(); } else { return null; @@ -110,11 +154,11 @@ public void setIdAttributeNode(Attr idAttr, boolean isId) { * @return theid
attribute, or the empty string if has no ID.
*/
@Override
- abstract public String getId();
+ abstract public String getId();
/**
- * Gets the inline style declaration from the current contents of the style
- * XHTML attribute.
+ * Gets the inline style declaration from the current contents of the
+ * style
XHTML attribute.
*
* @return the style declaration, or null
if the element has no
* style
attribute.
@@ -149,7 +193,7 @@ public void exportHintsToStyle(CSSStyleDeclaration style) {
@Override
public boolean hasOverrideStyle(String pseudoElt) {
- if(overrideStyleSet == null) {
+ if (overrideStyleSet == null) {
return false;
}
return overrideStyleSet.containsKey(pseudoElt);
@@ -158,12 +202,12 @@ public boolean hasOverrideStyle(String pseudoElt) {
@Override
public ExtendedCSSStyleDeclaration getOverrideStyle(String pseudoElt) {
ExtendedCSSStyleDeclaration overrideStyle = null;
- if(overrideStyleSet == null) {
+ if (overrideStyleSet == null) {
overrideStyleSet = new HashMap- * The copy is a shallow copy (the rule list is new, but the referenced rules are the same - * as in the cloned object. + * The copy is a shallow copy (the rule list is new, but the referenced rules + * are the same as in the cloned object. * * @return a clone of this instance. */ diff --git a/src/io/sf/carte/doc/dom4j/DOM4JComputedStyle.java b/src/io/sf/carte/doc/dom4j/DOM4JComputedStyle.java index 8b8bba5..3396433 100644 --- a/src/io/sf/carte/doc/dom4j/DOM4JComputedStyle.java +++ b/src/io/sf/carte/doc/dom4j/DOM4JComputedStyle.java @@ -1,6 +1,6 @@ /* - Copyright (c) 2005-2019, Carlos Amengual. + Copyright (c) 2005-2022, Carlos Amengual. SPDX-License-Identifier: BSD-3-Clause @@ -58,20 +58,19 @@ public ComputedCSSStyle getParentComputedStyle() { } /** - * Gets the (whitespace-trimmed) text content of the - * node associated to this style. + * Gets the (whitespace-trimmed) text content of the node associated to this + * style. * - * @return the text content, or the empty string if the box - * has no text. + * @return the text content, or the empty string if the box has no text. */ @Override public String getText() { String text; Node node = getOwnerNode(); - if(node instanceof DOMElement) { - text = ((DOMElement)node).getTextTrim(); - } else if(node instanceof org.dom4j.Node) { - text = BoxModelHelper.contractSpaces(((org.dom4j.Node)node).getText()); + if (node instanceof DOMElement) { + text = ((DOMElement) node).getTextTrim(); + } else if (node instanceof org.dom4j.Node) { + text = BoxModelHelper.contractSpaces(((org.dom4j.Node) node).getText()); } else { text = ""; } @@ -86,7 +85,7 @@ public String getText() { @Override public StyleDatabase getStyleDatabase() { Node node = getOwnerNode(); - if(node != null) { + if (node != null) { CSSDocument doc = (CSSDocument) node.getOwnerDocument(); return doc.getStyleDatabase(); } diff --git a/src/io/sf/carte/doc/dom4j/DOM4JDocumentCSSStyleSheet.java b/src/io/sf/carte/doc/dom4j/DOM4JDocumentCSSStyleSheet.java index e808abf..96340c9 100644 --- a/src/io/sf/carte/doc/dom4j/DOM4JDocumentCSSStyleSheet.java +++ b/src/io/sf/carte/doc/dom4j/DOM4JDocumentCSSStyleSheet.java @@ -1,6 +1,6 @@ /* - Copyright (c) 2005-2019, Carlos Amengual. + Copyright (c) 2005-2022, Carlos Amengual. SPDX-License-Identifier: BSD-3-Clause @@ -57,10 +57,8 @@ public void setOwnerDocument(CSSDocument owner) { /** * Gets the computed style for the given DOM4J element and pseudo-element. * - * @param elm - * the element. - * @param pseudoElt - * the pseudo-element. + * @param elm the element. + * @param pseudoElt the pseudo-element. * @return the computed style declaration. */ @Override @@ -76,8 +74,8 @@ public ComputedCSSStyle getComputedStyle(CSSElement elm, String pseudoElt) { /** * Creates and returns a copy of this style sheet. *
- * The copy is a shallow copy (the rule list is new, but the referenced rules are the same - * as in the cloned object. + * The copy is a shallow copy (the rule list is new, but the referenced rules + * are the same as in the cloned object. * * @return a clone of this instance. */ diff --git a/src/io/sf/carte/doc/dom4j/DOM4JUserAgent.java b/src/io/sf/carte/doc/dom4j/DOM4JUserAgent.java index 2da37e8..550ec61 100644 --- a/src/io/sf/carte/doc/dom4j/DOM4JUserAgent.java +++ b/src/io/sf/carte/doc/dom4j/DOM4JUserAgent.java @@ -1,6 +1,6 @@ /* - Copyright (c) 2005-2019, Carlos Amengual. + Copyright (c) 2005-2022, Carlos Amengual. SPDX-License-Identifier: BSD-3-Clause @@ -25,8 +25,6 @@ import org.dom4j.dom.DOMElement; import org.dom4j.io.SAXReader; import org.dom4j.io.XPP3Reader; -import org.w3c.dom.DOMException; -import org.w3c.dom.DocumentType; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; @@ -92,12 +90,13 @@ public void setEntityResolver(EntityResolver resolver) { * * @param url the URL that points to the document. * @return the XHTMLDocument. - * @throws IOException if there is an I/O problem reading the URL. - * @throws io.sf.carte.doc.DocumentException if there is a problem parsing the document. + * @throws IOException if there is an I/O problem reading + * the URL. + * @throws io.sf.carte.doc.DocumentException if there is a problem parsing the + * document. */ @Override - public XHTMLDocument readURL(URL url) - throws IOException, io.sf.carte.doc.DocumentException { + public XHTMLDocument readURL(URL url) throws IOException, io.sf.carte.doc.DocumentException { long time = System.currentTimeMillis(); URLConnection con = openConnection(url, time); con.connect(); @@ -108,38 +107,33 @@ public XHTMLDocument readURL(URL url) try { is = openInputStream(con); xdoc = parseDocument(AgentUtil.inputStreamToReader(is, conType, contentEncoding, "utf-8")); - xdoc.setLoadingTime(time); - if (xdoc.getBaseURL() == null) { - xdoc.setBaseURL(url); - } } catch (IOException e) { throw e; } catch (io.sf.carte.doc.DocumentException e) { - throw new io.sf.carte.doc.DocumentException( - "Error parsing document " + url.toExternalForm(), e.getCause()); + throw new io.sf.carte.doc.DocumentException("Error parsing document " + url.toExternalForm(), e.getCause()); } finally { - if(is != null) { + if (is != null) { is.close(); } } + xdoc.setLoadingTime(time); + xdoc.setDocumentURI(url.toExternalForm()); // Check for preferred style String defStyle = con.getHeaderField("Default-Style"); NodeList list = xdoc.getElementsByTagName("meta"); int listL = list.getLength(); - for(int i = listL - 1; i>=0; i--) { - if("Default-Style".equalsIgnoreCase( - ((DOMElement)list.item(i)).attributeValue("http-equiv"))) { - String metaDefStyle = ((DOMElement)list.item(i)) - .attributeValue("content"); - if(metaDefStyle != null) { + for (int i = listL - 1; i >= 0; i--) { + if ("Default-Style".equalsIgnoreCase(((DOMElement) list.item(i)).attributeValue("http-equiv"))) { + String metaDefStyle = ((DOMElement) list.item(i)).attributeValue("content"); + if (metaDefStyle != null) { // Per HTML4 spec § 14.3.2: - // "If two or more META declarations or HTTP headers specify - // the preferred style sheet, the last one takes precedence." + // "If two or more META declarations or HTTP headers specify + // the preferred style sheet, the last one takes precedence." defStyle = metaDefStyle; } } } - if(defStyle != null) { + if (defStyle != null) { xdoc.setSelectedStyleSheetSet(defStyle); } // Referrer Policy @@ -148,8 +142,8 @@ public XHTMLDocument readURL(URL url) xdoc.setReferrerPolicyHeader(referrerPolicy); } // Read cookies and close connection, if appropriate - if(con instanceof HttpURLConnection) { - HttpURLConnection hcon = (HttpURLConnection)con; + if (con instanceof HttpURLConnection) { + HttpURLConnection hcon = (HttpURLConnection) con; readCookies(hcon, time); hcon.disconnect(); } @@ -162,19 +156,17 @@ protected InputStream openInputStream(URLConnection con) throws IOException { protected AgentXHTMLDocument parseDocument(Reader re) throws io.sf.carte.doc.DocumentException, IOException { try { - if(useXPP3) { + if (useXPP3) { return parseWithXPP3Reader(re); } else { return parseWithSAXReader(re); } } catch (DocumentException e) { - throw new io.sf.carte.doc.DocumentException( - "Error parsing document", e); + throw new io.sf.carte.doc.DocumentException("Error parsing document", e); } } - private AgentXHTMLDocument parseWithSAXReader(Reader re) - throws DocumentException { + private AgentXHTMLDocument parseWithSAXReader(Reader re) throws DocumentException { InputSource isrc = new InputSource(re); XHTMLDocumentFactory factory = getXHTMLDocumentFactory(); SAXReader reader = new SAXReader(factory); @@ -182,8 +174,7 @@ private AgentXHTMLDocument parseWithSAXReader(Reader re) return (AgentXHTMLDocument) reader.read(isrc); } - private AgentXHTMLDocument parseWithXPP3Reader(Reader re) - throws DocumentException, IOException { + private AgentXHTMLDocument parseWithXPP3Reader(Reader re) throws DocumentException, IOException { XHTMLDocumentFactory factory = getXHTMLDocumentFactory(); XPP3Reader reader = new XPP3Reader(factory); try { @@ -194,8 +185,6 @@ private AgentXHTMLDocument parseWithXPP3Reader(Reader re) } public XHTMLDocumentFactory getXHTMLDocumentFactory() { - // Uncomment the next line to use PDF. - //factory.getDeviceFactory().setStyleDatabase("pdf", new PDFStyleDatabase()); return factory; } @@ -215,26 +204,16 @@ public XHTMLDocument createDocument() { } @Override - public XHTMLDocument createDocument(String namespaceURI, String qualifiedName, DocumentType docType) - throws DOMException { - XHTMLDocument document; - if (docType != null) { - DOMDocumentType documentType = asDocumentType(docType); - document = new AgentXHTMLDocument(documentType); - } else { - document = new AgentXHTMLDocument(); - } + protected XHTMLDocument createDocument(DOMDocumentType documentType) { + AgentXHTMLDocument document = new AgentXHTMLDocument(documentType); document.setDocumentFactory(this); - if (qualifiedName != null) { - document.add(createElement(createQName(qualifiedName, namespaceURI))); - } return document; } public class AgentXHTMLDocument extends XHTMLDocument { private static final long serialVersionUID = 3L; - + private long loadingTime; AgentXHTMLDocument() { @@ -265,7 +244,7 @@ public class AgentXHTMLDocument extends XHTMLDocument { public URLConnection openConnection(URL url) throws IOException { return DOM4JUserAgent.this.openConnection(url, loadingTime); } - + @Override public boolean isVisitedURI(String href) { try { diff --git a/src/io/sf/carte/doc/dom4j/DocumentStyleEventAttribute.java b/src/io/sf/carte/doc/dom4j/DocumentStyleEventAttribute.java index 1365301..2f58073 100644 --- a/src/io/sf/carte/doc/dom4j/DocumentStyleEventAttribute.java +++ b/src/io/sf/carte/doc/dom4j/DocumentStyleEventAttribute.java @@ -1,6 +1,6 @@ /* - Copyright (c) 2005-2019, Carlos Amengual. + Copyright (c) 2005-2022, Carlos Amengual. SPDX-License-Identifier: BSD-3-Clause @@ -16,7 +16,8 @@ import org.dom4j.dom.DOMAttribute; /** - * An attribute that fires a parent and document-wide style modify event when changed. + * An attribute that fires a parent and document-wide style modify event when + * changed. * * @author Carlos Amengual * @@ -40,9 +41,13 @@ class DocumentStyleEventAttribute extends DOMAttribute { @Override public void setValue(String value) { super.setValue(value); - StyleDefinerElement element = (StyleDefinerElement) getParent(); - if (element != null) { - element.resetLinkedSheet(); + Element owner = getParent(); + if (owner != null) { + if (owner instanceof StyleDefinerElement) { + ((StyleDefinerElement) owner).resetLinkedSheet(); + } else if (owner instanceof CachedXHTMLElement) { + ((CachedXHTMLElement) owner).onStyleModify(); + } } } diff --git a/src/io/sf/carte/doc/dom4j/FontElement.java b/src/io/sf/carte/doc/dom4j/FontElement.java index 02dfd6a..ea14d74 100644 --- a/src/io/sf/carte/doc/dom4j/FontElement.java +++ b/src/io/sf/carte/doc/dom4j/FontElement.java @@ -1,6 +1,6 @@ /* - Copyright (c) 2005-2019, Carlos Amengual. + Copyright (c) 2005-2022, Carlos Amengual. SPDX-License-Identifier: BSD-3-Clause @@ -17,7 +17,8 @@ import io.sf.carte.doc.style.css.property.AttributeToStyle; /** - * Font element.
+ * Font element. + *
* Provides equivalent CSS style for this deprecated element.
*
* @author Carlos Amengual
@@ -35,9 +36,9 @@ class FontElement extends XHTMLElement {
super(qname);
}
- FontElement(QName qname, int attributeCount) {
- super(qname, attributeCount);
- }
+ FontElement(QName qname, int attributeCount) {
+ super(qname, attributeCount);
+ }
@Override
public boolean hasPresentationalHints() {
diff --git a/src/io/sf/carte/doc/dom4j/HeadElement.java b/src/io/sf/carte/doc/dom4j/HeadElement.java
index feaadbb..95c0d4c 100644
--- a/src/io/sf/carte/doc/dom4j/HeadElement.java
+++ b/src/io/sf/carte/doc/dom4j/HeadElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -33,23 +33,31 @@ class HeadElement extends XHTMLElement {
super(qname);
}
- HeadElement(QName qname, int attributeCount) {
- super(qname, attributeCount);
- }
+ HeadElement(QName qname, int attributeCount) {
+ super(qname, attributeCount);
+ }
@Override
protected void childAdded(Node node) {
super.childAdded(node);
if (node instanceof BaseURLElement) {
- getOwnerDocument().setBaseURL(((BaseURLElement) node).base);
- ((BaseURLElement) node).base = null; // help GC
+ XHTMLDocument doc = getOwnerDocument();
+ if (doc != null) {
+ String href = ((BaseURLElement) node).getAttribute("href").trim();
+ if (href.length() != 0) {
+ doc.setBaseURL(this, href);
+ }
+ }
} else if ("meta".equalsIgnoreCase(node.getName())) {
- Element elt = (Element) node;
- String name = elt.getAttribute("http-equiv");
- if (name.length() == 0) {
- name = elt.getAttribute("name");
+ XHTMLDocument doc = getOwnerDocument();
+ if (doc != null) {
+ Element elt = (Element) node;
+ String name = elt.getAttribute("http-equiv");
+ if (name.length() == 0) {
+ name = elt.getAttribute("name");
+ }
+ doc.onMetaAdded(name, elt.getAttribute("content"));
}
- getOwnerDocument().onMetaAdded(name, elt.getAttribute("content"));
}
}
diff --git a/src/io/sf/carte/doc/dom4j/HrefAttribute.java b/src/io/sf/carte/doc/dom4j/HrefAttribute.java
new file mode 100644
index 0000000..a2150b2
--- /dev/null
+++ b/src/io/sf/carte/doc/dom4j/HrefAttribute.java
@@ -0,0 +1,74 @@
+/*
+
+ Copyright (c) 2005-2022, Carlos Amengual.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+ Licensed under a BSD-style License. You can find the license here:
+ https://css4j.github.io/LICENSE.txt
+
+ */
+
+package io.sf.carte.doc.dom4j;
+
+import java.util.Iterator;
+
+import org.dom4j.Element;
+import org.dom4j.QName;
+import org.dom4j.dom.DOMAttribute;
+
+/**
+ * Href attribute.
+ *
+ * @author Carlos Amengual
+ *
+ */
+class HrefAttribute extends DOMAttribute {
+
+ private static final long serialVersionUID = 3L;
+
+ HrefAttribute(QName qname) {
+ super(qname);
+ }
+
+ HrefAttribute(QName qname, String value) {
+ super(qname, value);
+ }
+
+ HrefAttribute(Element parent, QName qname, String value) {
+ super(parent, qname, value);
+ }
+
+ @Override
+ public void setValue(String value) {
+ super.setValue(value);
+ XHTMLDocument doc = (XHTMLDocument) getDocument();
+ org.w3c.dom.Element owner;
+ if (doc != null && (owner = getOwnerElement()) != null) {
+ if (owner instanceof StyleDefinerElement) {
+ StyleDefinerElement element = (StyleDefinerElement) owner;
+ element.resetLinkedSheet();
+ } else if ("base".equalsIgnoreCase(owner.getLocalName())) {
+ if (value != null && value.length() != 0) {
+ // We set base to null first, in case setBaseURL(owner, base) fails
+ doc.setBaseURL(null);
+ doc.setBaseURL(owner, value);
+ } else {
+ doc.setBaseURL(null);
+ }
+ onBaseModify(doc);
+ }
+ if (owner instanceof CachedXHTMLElement) {
+ ((CachedXHTMLElement) owner).onStyleModify();
+ }
+ }
+ }
+
+ static void onBaseModify(XHTMLDocument doc) {
+ Iterator
* The style sheet is lazily built.
*
@@ -250,22 +252,18 @@ private void mergeStyleSheets() {
}
/**
- * Adds a style sheet (contained by the given InputSource) to the global
- * style sheet defined by the document's default style sheet and all the
- * linked and embedded styles.
+ * Adds a style sheet (contained by the given InputSource) to the global style
+ * sheet defined by the document's default style sheet and all the linked and
+ * embedded styles.
*
- * @param cssSrc
- * the document's InputSource.
- * @return
- * The serial number will be increased by one each time that any of the
- * sheets that conform the style is changed.
+ * The serial number will be increased by one each time that any of the sheets
+ * that conform the style is changed.
*
* @return the serial number for the merged style sheet.
*/
@@ -456,22 +450,19 @@ int getStyleCacheSerial() {
/**
* Gets the override style sheet for an element and pseudo-element.
*
- * The getOverrideStyle method provides a mechanism through which a DOM
- * author could effect immediate change to the style of an element without
- * modifying the explicitly linked style sheets of a document or the inline
- * style of elements in the style sheets.
+ * The getOverrideStyle method provides a mechanism through which a DOM author
+ * could effect immediate change to the style of an element without modifying
+ * the explicitly linked style sheets of a document or the inline style of
+ * elements in the style sheets.
*
- * The override style sheet comes after the author style sheet in the
- * cascade algorithm. DOM Level 2.
+ * The override style sheet comes after the author style sheet in the cascade
+ * algorithm. DOM Level 2.
*
* If the Document's
- * In dom4j, the
+ * If the URL's protocol is not {@code http} nor {@code https} and document's
+ * base URL's scheme is neither {@code file} nor {@code jar}, it is denied.
+ *
* Only applications that repeatedly call the
* {@link CSSStylableElement#getComputedStyle()} method on the same Element
* should turn it on.
*
- * @param onOff
- * set to true to turn on the cache capability, to false to turn
- * it off.
+ * @param onOff set to true to turn on the cache capability, to false to turn it
+ * off.
*/
public void setStyleCache(boolean onOff) {
this.styleCacheOn = onOff;
@@ -239,17 +239,22 @@ public XHTMLDocument createDocument(Element rootElement) {
return mydoc;
}
+ protected XHTMLDocument createDocument(DOMDocumentType documentType) {
+ XHTMLDocument document = new XHTMLDocument(documentType);
+ document.setDocumentFactory(this);
+ return document;
+ }
+
@Override
public XHTMLDocument createDocument(String namespaceURI, String qualifiedName, DocumentType docType)
throws DOMException {
XHTMLDocument document;
if (docType != null) {
DOMDocumentType documentType = asDocumentType(docType);
- document = new XHTMLDocument(documentType);
+ document = createDocument(documentType);
} else {
- document = new XHTMLDocument();
+ document = createDocument();
}
- document.setDocumentFactory(this);
if (qualifiedName != null) {
if (namespaceURI == null) {
namespaceURI = "";
@@ -262,12 +267,13 @@ public XHTMLDocument createDocument(String namespaceURI, String qualifiedName, D
@Override
public Attribute createAttribute(Element owner, QName qname, String value) {
String name = qname.getName();
- if (owner instanceof StyleDefinerElement) {
- return new DocumentStyleEventAttribute(qname, value);
- } else if ((name = qname.getName()).equals("href") && owner instanceof BaseURLElement) {
- return new BaseHrefAttribute(qname, value);
+ if ((name = qname.getName()).equals("href")) {
+ return new HrefAttribute(qname, value);
} else if (name.equalsIgnoreCase("style")) {
return new StyleAttribute(qname, value);
+ } else if (name.equalsIgnoreCase("type") || name.equalsIgnoreCase("media") || name.equalsIgnoreCase("rel")
+ || name.equalsIgnoreCase("title")) {
+ return new DocumentStyleEventAttribute(qname, value);
} else {
return super.createAttribute(owner, qname, value);
}
@@ -325,8 +331,7 @@ protected BaseCSSStyleSheet createRuleStyleSheet(AbstractCSSRule ownerRule, Stri
}
@Override
- protected BaseCSSStyleSheet createLinkedStyleSheet(Node ownerNode, String title,
- MediaQueryList mediaList) {
+ protected BaseCSSStyleSheet createLinkedStyleSheet(Node ownerNode, String title, MediaQueryList mediaList) {
return new MyDOM4JCSSStyleSheet(title, ownerNode, mediaList, null, CSSStyleSheetFactory.ORIGIN_AUTHOR);
}
@@ -499,11 +504,10 @@ private void mergeUserSheets() {
}
/**
- * Sets a default HTML default style sheet as the user agent style
- * sheet.
+ * Sets a default HTML default style sheet as the user agent style sheet.
*
- * This is not necessary in the DOM4J backend, as that style sheet is
- * loaded by default.
+ * This is not necessary in the DOM4J backend, as that style sheet is loaded by
+ * default.
*/
@Override
public void setDefaultHTMLUserAgentSheet() {
diff --git a/src/io/sf/carte/doc/dom4j/XHTMLElement.java b/src/io/sf/carte/doc/dom4j/XHTMLElement.java
index ef0010f..c24774e 100644
--- a/src/io/sf/carte/doc/dom4j/XHTMLElement.java
+++ b/src/io/sf/carte/doc/dom4j/XHTMLElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -86,15 +86,20 @@ public void setAttributeNS(String namespaceURI, String qualifiedName, String val
@Override
protected void childAdded(Node node) {
super.childAdded(node);
- if (node instanceof LinkStyle) {
- getOwnerDocument().onEmbeddedStyleAdd((LinkStyle) node);
+ String nsUri;
+ XHTMLDocument doc;
+ if (node instanceof LinkStyle && (doc = getOwnerDocument()) != null
+ && ((nsUri = ((CSSStylableElement) node).getNamespaceURI()) == null || nsUri.length() == 0
+ || nsUri.equals(doc.getDocumentElement().getNamespaceURI()))) {
+ doc.onLinkStyleAdd((LinkStyle) node);
}
}
@Override
protected void childRemoved(Node node) {
- if (node instanceof LinkStyle) {
- getOwnerDocument().onEmbeddedStyleRemove((LinkStyle) node);
+ XHTMLDocument doc;
+ if (node instanceof LinkStyle && (doc = getOwnerDocument()) != null) {
+ doc.onLinkStyleRemove((LinkStyle) node);
}
super.childRemoved(node);
}
diff --git a/src/io/sf/carte/doc/dom4j/package-info.java b/src/io/sf/carte/doc/dom4j/package-info.java
index fdc64b7..dcf18bf 100644
--- a/src/io/sf/carte/doc/dom4j/package-info.java
+++ b/src/io/sf/carte/doc/dom4j/package-info.java
@@ -1,6 +1,5 @@
/**
- * Built on top of the DOM4J package, provides XHTML parsing with built-in
- * support for CSS style sheets.
+ * Built on top of the DOM4J package, provides it with built-in support for CSS style sheets.
*
* DOM4J is an XML DOM-like software package with support for Java
* language constructs, like Collections. See
@@ -8,49 +7,9 @@
* for more information.
*
- * This implementation integrates DOM4J with the CSS DOM implementation found in
+ * This implementation integrates DOM4J with the CSSOM implementation found in
* the
- * This is the easiest way to use this package:
- *
- * And once you got the element you want style for (see, for example, the
- * DOM4J Quick Start Guide), just get it:
- *
- * The
- * Some CSS default values are system-dependent, and there are other processes that rely
- * on device-specific information. This information can be provided by a user-supplied
- *
- * If you use the AWT, you may consider using the the AWT module. For example:
- *
- * Just use the There is more information in the description of the By default, computed styles only take into account generic styles that are
-common to all media. If you want to target a specific medium, you have to use
-the Built on top of the DOM4J package, gives DOM4J built-in support for CSS style
+sheets. You can use the To use the library with dom4j (using dom4j-like documents, elements and factory) you need the This is the easiest way to use this package with DOM4J, using that library's And once you got the element you want the computed style for (see, for example,
+the DOM4J Quick Start Guide),
+just get it with a procedure analogous to the
+ It is also possible to parse an HTML5 document into a css4j-dom4j tree with the validator.nu HTML5 parser: Or use a SAX parser to parse an XML document into a css4j-dom4j tree, with
+ The code above uses the default JAXP SAX parser. You could use a different A default user agent sheet is automatically loaded by the style sheet factory in
+css4j-dom4j, something that does not happen with the other back-ends where you have to do
+that explicitly (the css4j-dom4j behaviour is kept for backwards compatibility). To clear
+that sheet, first obtain an instance of the factory that you are using:
+ and then, get the user agent sheet and clear the rule list: This is assuming the null
if the sheet is
- * not CSS or the media attribute was not understood. If the URL is invalid or the
- * sheet could not be parsed, the returned sheet will be empty.
+ * @return the associated style sheet for the node, or null
if the
+ * sheet is not CSS or the media attribute was not understood. If the
+ * URL is invalid or the sheet could not be parsed, the returned sheet
+ * will be empty.
*/
@Override
public AbstractCSSStyleSheet getSheet() {
@@ -110,7 +113,13 @@ private void loadStyleSheet(String href, String title) {
if (media == null || media.trim().length() == 0) {
mediaList = MediaList.createMediaList();
} else {
- mediaList = getDocumentFactory().getStyleSheetFactory().createMediaQueryList(media, this);
+ try {
+ mediaList = getDocumentFactory().getStyleSheetFactory().createImmutableMediaQueryList(media, this);
+ } catch (CSSException e) {
+ getErrorHandler().linkedStyleError(this, e.getMessage());
+ linkedSheet = null;
+ return;
+ }
if (mediaList.isNotAllMedia() && mediaList.hasErrors()) {
linkedSheet = null;
return;
@@ -130,10 +139,18 @@ private void loadStyleSheet(String href, String title) {
}
try {
URL url = getOwnerDocument().getURL(href);
- linkedSheet.setHref(url.toExternalForm());
- linkedSheet.loadStyleSheet(url, referrerPolicy);
+ if (getOwnerDocument().isAuthorizedOrigin(url)) {
+ linkedSheet.setHref(url.toExternalForm());
+ linkedSheet.loadStyleSheet(url, referrerPolicy);
+ } else {
+ getErrorHandler().policyError(this, "Unauthorized URL: " + url.toExternalForm());
+ }
} catch (CSSException e) {
getErrorHandler().linkedSheetError(e, linkedSheet);
+ } catch (IOException e) {
+ getErrorHandler().ioError(href, e);
+ } catch (DOMException e) {
+ // Already logged
} catch (Exception e) {
getErrorHandler().linkedSheetError(e, linkedSheet);
}
diff --git a/src/io/sf/carte/doc/dom4j/StyleAttribute.java b/src/io/sf/carte/doc/dom4j/StyleAttribute.java
index d285674..b31ee61 100644
--- a/src/io/sf/carte/doc/dom4j/StyleAttribute.java
+++ b/src/io/sf/carte/doc/dom4j/StyleAttribute.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -54,6 +54,9 @@ public CSSStylableElement getOwnerElement() {
@Override
public String getValue() {
+ if (inlineStyle == null || inlineStyle.getLength() == 0) {
+ return super.getValue();
+ }
return getStyle().getCssText();
}
diff --git a/src/io/sf/carte/doc/dom4j/StyleDefinerElement.java b/src/io/sf/carte/doc/dom4j/StyleDefinerElement.java
index 7ff9d65..0b1f496 100644
--- a/src/io/sf/carte/doc/dom4j/StyleDefinerElement.java
+++ b/src/io/sf/carte/doc/dom4j/StyleDefinerElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
diff --git a/src/io/sf/carte/doc/dom4j/StyleElement.java b/src/io/sf/carte/doc/dom4j/StyleElement.java
index 0ef8060..a8055be 100644
--- a/src/io/sf/carte/doc/dom4j/StyleElement.java
+++ b/src/io/sf/carte/doc/dom4j/StyleElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -16,6 +16,7 @@
import java.io.StringReader;
import org.dom4j.QName;
+import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.InputSource;
import org.w3c.dom.DOMException;
@@ -45,12 +46,44 @@ class StyleElement extends StyleDefinerElement {
super(qname, attributeCount);
}
+ @Override
+ public void normalize() {
+ if (!containsCSS()) {
+ super.normalize();
+ } else {
+ // Local reference to sheet, to avoid race conditions.
+ final AbstractCSSStyleSheet sheet = getSheet();
+ if (sheet == null) {
+ super.normalize();
+ } else {
+ super.setText(sheet.toString());
+ }
+ }
+ }
+
+ private boolean containsCSS() {
+ if (linkedSheet != null) {
+ // Local reference to sheet, to avoid race conditions.
+ final AbstractCSSStyleSheet sheet;
+ String type = attributeValue("type");
+ if ("text/css".equalsIgnoreCase(type) || ((type == null || type.length() == 0)
+ && (sheet = getSheet()) != null && sheet.getCssRules().getLength() != 0)) {
+ return true;
+ }
+ }
+ /*
+ * If the sheet has not been processed yet, we return false as well.
+ */
+ return false;
+ }
+
/**
* Gets the associated style sheet for the node.
*
- * @return the associated style sheet for the node, or null
if the sheet is
- * not CSS or the media attribute was not understood. If the element is empty or
- * the sheet could not be parsed, the returned sheet will be empty.
+ * @return the associated style sheet for the node, or null
if the
+ * sheet is not CSS or the media attribute was not understood. If the
+ * element is empty or the sheet could not be parsed, the returned sheet
+ * will be empty.
*/
@Override
public AbstractCSSStyleSheet getSheet() {
@@ -60,7 +93,7 @@ public AbstractCSSStyleSheet getSheet() {
return null;
}
String type = attributeValue("type");
- if (!"text/css".equalsIgnoreCase(type)) {
+ if (type != null && !"text/css".equalsIgnoreCase(type) && type.length() != 0) {
return null;
}
String media = attributeValue("media");
@@ -68,7 +101,12 @@ public AbstractCSSStyleSheet getSheet() {
if (media == null || media.trim().length() == 0) {
mediaList = MediaList.createMediaList();
} else {
- mediaList = getDocumentFactory().getStyleSheetFactory().createMediaQueryList(media, this);
+ try {
+ mediaList = getDocumentFactory().getStyleSheetFactory().createImmutableMediaQueryList(media, this);
+ } catch (CSSException e) {
+ getErrorHandler().linkedStyleError(this, e.getMessage());
+ return null;
+ }
if (mediaList.isNotAllMedia() && mediaList.hasErrors()) {
return null;
}
diff --git a/src/io/sf/carte/doc/dom4j/TableCellElement.java b/src/io/sf/carte/doc/dom4j/TableCellElement.java
index edb657d..2afc507 100644
--- a/src/io/sf/carte/doc/dom4j/TableCellElement.java
+++ b/src/io/sf/carte/doc/dom4j/TableCellElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -34,14 +34,14 @@ protected TableCellElement(QName qname) {
super(qname);
}
- TableCellElement(QName qname, int attributeCount) {
- super(qname, attributeCount);
- }
+ TableCellElement(QName qname, int attributeCount) {
+ super(qname, attributeCount);
+ }
@Override
public boolean hasPresentationalHints() {
- return hasAttribute("bgcolor") || hasAttribute("width") || hasAttribute("height")
- || hasAttribute("background") || hasAttribute("align");
+ return hasAttribute("bgcolor") || hasAttribute("width") || hasAttribute("height") || hasAttribute("background")
+ || hasAttribute("align");
}
@Override
diff --git a/src/io/sf/carte/doc/dom4j/TableElement.java b/src/io/sf/carte/doc/dom4j/TableElement.java
index 3ab4d7a..aaf1b3c 100644
--- a/src/io/sf/carte/doc/dom4j/TableElement.java
+++ b/src/io/sf/carte/doc/dom4j/TableElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -34,15 +34,14 @@ protected TableElement(QName qname) {
super(qname);
}
- TableElement(QName qname, int attributeCount) {
- super(qname, attributeCount);
- }
+ TableElement(QName qname, int attributeCount) {
+ super(qname, attributeCount);
+ }
@Override
public boolean hasPresentationalHints() {
- return hasAttribute("width") || hasAttribute("height") || hasAttribute("cellspacing")
- || hasAttribute("border") || hasAttribute("bordercolor") || hasAttribute("bgcolor")
- || hasAttribute("background");
+ return hasAttribute("width") || hasAttribute("height") || hasAttribute("cellspacing") || hasAttribute("border")
+ || hasAttribute("bordercolor") || hasAttribute("bgcolor") || hasAttribute("background");
}
@Override
diff --git a/src/io/sf/carte/doc/dom4j/TableRowElement.java b/src/io/sf/carte/doc/dom4j/TableRowElement.java
index 4d29c85..1f4ff69 100644
--- a/src/io/sf/carte/doc/dom4j/TableRowElement.java
+++ b/src/io/sf/carte/doc/dom4j/TableRowElement.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -34,14 +34,13 @@ protected TableRowElement(QName qname) {
super(qname);
}
- TableRowElement(QName qname, int attributeCount) {
- super(qname, attributeCount);
- }
+ TableRowElement(QName qname, int attributeCount) {
+ super(qname, attributeCount);
+ }
@Override
public boolean hasPresentationalHints() {
- return hasAttribute("bgcolor") || hasAttribute("height") || hasAttribute("background")
- || hasAttribute("align");
+ return hasAttribute("bgcolor") || hasAttribute("height") || hasAttribute("background") || hasAttribute("align");
}
@Override
diff --git a/src/io/sf/carte/doc/dom4j/XHTMLDocument.java b/src/io/sf/carte/doc/dom4j/XHTMLDocument.java
index ff7b669..cd75b6d 100644
--- a/src/io/sf/carte/doc/dom4j/XHTMLDocument.java
+++ b/src/io/sf/carte/doc/dom4j/XHTMLDocument.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -62,12 +62,14 @@ public class XHTMLDocument extends DOMDocument implements CSSDocument {
public final static String XHTML_NAMESPACE_URI = "http://www.w3.org/1999/xhtml";
- private static final long serialVersionUID = 6L;
+ private static final long serialVersionUID = 7L;
private DocumentCSSStyleSheet mergedStyleSheet = null;
private int styleCacheSerial = Integer.MIN_VALUE;
+ private String documentURI = null;
+
private URL baseURL = null;
Settrue
if the parsing reported no errors or fatal errors, false
- * otherwise.
- * @throws DOMException
- * if a DOM problem is found parsing the sheet.
- * @throws CSSException
- * if a non-DOM problem is found parsing the sheet.
- * @throws IOException
- * if a problem is found reading the sheet.
+ * @param cssSrc the document's InputSource.
+ * @return true
if the parsing reported no errors or fatal errors,
+ * false otherwise.
+ * @throws DOMException if a DOM problem is found parsing the sheet.
+ * @throws CSSException if a non-DOM problem is found parsing the sheet.
+ * @throws IOException if a problem is found reading the sheet.
*/
- public boolean addStyleSheet(InputSource cssSrc) throws DOMException, IOException {
+ public boolean addStyleSheet(InputSource cssSrc) throws DOMException, CSSException, IOException {
String media = cssSrc.getMedia();
if (media != null && !"all".equalsIgnoreCase(media)) {
// handle as media rule
@@ -303,10 +301,10 @@ public DOMStringList getStyleSheetSets() {
/**
* Gets the title of the currently selected style sheet set.
*
- * @return the title of the currently selected style sheet, the empty string
- * if none is selected, or null
if there are style
- * sheets from different style sheet sets that have their style
- * sheet disabled flag unset.
+ * @return the title of the currently selected style sheet, the empty string if
+ * none is selected, or null
if there are style sheets from
+ * different style sheet sets that have their style sheet disabled flag
+ * unset.
*/
@Override
public String getSelectedStyleSheetSet() {
@@ -334,13 +332,11 @@ public String getSelectedStyleSheetSet() {
}
/**
- * Selects a style sheet set, disabling the other non-persistent sheet sets.
- * If the name is the empty string, all non-persistent sheets will be
- * disabled. Otherwise, if the name does not match any of the sets, does
- * nothing.
+ * Selects a style sheet set, disabling the other non-persistent sheet sets. If
+ * the name is the empty string, all non-persistent sheets will be disabled.
+ * Otherwise, if the name does not match any of the sets, does nothing.
*
- * @param name
- * the case-sensitive name of the set to select.
+ * @param name the case-sensitive name of the set to select.
*/
@Override
public void setSelectedStyleSheetSet(String name) {
@@ -373,11 +369,10 @@ public String getLastStyleSheetSet() {
}
/**
- * Enables a style sheet set. If the name does not match any of the sets,
- * does nothing.
+ * Enables a style sheet set. If the name does not match any of the sets, does
+ * nothing.
*
- * @param name
- * the case-sensitive name of the set to enable.
+ * @param name the case-sensitive name of the set to enable.
*/
@Override
public void enableStyleSheetsForSet(String name) {
@@ -396,10 +391,7 @@ public void enableStyleSheetsForSet(String name) {
}
}
- /*
- * This method should only be called from HeadElement.
- */
- void onEmbeddedStyleAdd(LinkStyle element) {
+ void onLinkStyleAdd(LinkStyle element) {
if (element instanceof LinkElement) {
linkedStyle.add((LinkElement) element);
} else if (element instanceof StyleElement) {
@@ -408,25 +400,27 @@ void onEmbeddedStyleAdd(LinkStyle element) {
onStyleModify();
}
- /*
- * This method should only be called from HeadElement.
- */
- void onEmbeddedStyleRemove(LinkStyle element) {
+ void onLinkStyleRemove(LinkStyle element) {
+ boolean removed;
if (element instanceof LinkElement) {
- linkedStyle.remove(element);
+ removed = linkedStyle.remove(element);
} else if (element instanceof StyleElement) {
- embeddedStyle.remove(element);
- }
- CSSStyleSheet sheet = (CSSStyleSheet) element.getSheet();
- if (sheet != null) {
- String title = sheet.getTitle();
- if (title != null) {
- sheets.remove(title);
- } else {
- sheets.remove(sheet);
+ removed = embeddedStyle.remove(element);
+ } else {
+ removed = false;
+ }
+ if (removed) {
+ CSSStyleSheet sheet = (CSSStyleSheet) element.getSheet();
+ if (sheet != null) {
+ String title = sheet.getTitle();
+ if (title != null) {
+ sheets.remove(title);
+ } else {
+ sheets.remove(sheet);
+ }
}
+ onStyleModify();
}
- onStyleModify();
}
/**
@@ -444,8 +438,8 @@ void onStyleModify() {
/**
* Gets the serial number for the document-wide merged style sheet.
* true
if any of the linked or embedded style sheets has any SAC or rule error
- * or warning, false
otherwise.
+ * @return true
if any of the linked or embedded style sheets has
+ * any SAC or rule error or warning, false
otherwise.
*/
@Override
public boolean hasStyleIssues() {
@@ -590,13 +579,14 @@ public boolean hasStyleIssues() {
}
public void onMetaAdded(String name, String attribute) {
- if ("Default-Style".equalsIgnoreCase(name)) {
+ if ("default-style".equalsIgnoreCase(name)) {
metaDefaultStyleSet = attribute;
+ onStyleModify();
}
}
public void onMetaRemoved(String name, String attribute) {
- if ("Default-Style".equalsIgnoreCase(name)) {
+ if ("default-style".equalsIgnoreCase(name)) {
metaDefaultStyleSet = "";
}
}
@@ -605,25 +595,52 @@ public void onMetaRemoved(String name, String attribute) {
* Gets the base URL of this Document.
* head
element has a base
child
- * element, the base URI is computed using the value of the href attribute
- * of the base
element. It can also be set with the
+ * element, the base URI is computed using the value of the href attribute of
+ * the base
element. It can also be set with the
* setBaseURL
method.
- * getDocumentURI
method cannot be trusted to
- * find the base URL.
+ * true
if is a safe origin, false
otherwise.
*/
@@ -693,8 +765,36 @@ public boolean isSafeOrigin(URL linkedURL) {
}
/**
- * Get the referrer policy obtained through the 'Referrer-Policy' header or a meta
- * element.
+ * Determine whether the retrieval of the given URL is authorized.
+ * true
if the per-element cache is enabled, false
otherwise.
+ * @return true
if the per-element cache is enabled,
+ * false
otherwise.
*/
public boolean isStyleCacheOn() {
return styleCacheOn;
}
/**
- * Can turn on or off the per-Element style caching capability (by default
- * is off).
+ * Can turn on or off the per-Element style caching capability (by default is
+ * off).
* io.sf.carte.doc.style.css
package and subpackages.
* Short example
- *
- Reader re = ... [reader for XHTML document]
- InputSource source = new InputSource(re);
- SAXReader reader = new SAXReader(XHTMLDocumentFactory.getInstance());
- reader.setEntityResolver(new DefaultEntityResolver());
- Document document = reader.read(source);
- *
- *
- * CSSComputedProperties style = ((CSSStylableElement) element).getComputedStyle();
- * String propertyValue = style.getPropertyValue("display");
- *
- *
- * Non-standard interfaces
- * CSSComputedProperties
interface is a convenient extension to W3C's
- * CSSStyleDeclaration
.
- * DeviceFactory
object. You can set the appropriate
- * DeviceFactory
at the XHTMLDocumentFactory
with the
- * setDeviceFactory
method.
- *
- * Color color = AWTHelper.getAWTColor(style.getCSSColor());
- *
*
* Read the documentation of the individual classes for information on
* additional capabilities, like caching or the use of customized style sheets.
diff --git a/src/module-info.java b/src/module-info.java
index 840082f..a1c5663 100644
--- a/src/module-info.java
+++ b/src/module-info.java
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2005-2019, Carlos Amengual.
+ Copyright (c) 2005-2022, Carlos Amengual.
SPDX-License-Identifier: BSD-3-Clause
@@ -13,5 +13,8 @@
exports io.sf.carte.doc.dom4j;
requires transitive io.sf.carte.css4j.agent.net;
- requires transitive dom4j;
+ requires transitive org.dom4j;
+ requires static org.xmlpull.mxp1;
+ requires static org.xmlpull.v1;
+ requires java.xml;
}
diff --git a/src/overview.html b/src/overview.html
index a8b6d13..3036fb3 100644
--- a/src/overview.html
+++ b/src/overview.html
@@ -3,16 +3,79 @@
XHTMLDocumentFactory
as you would use DOMDocumentFactory
,
-and the produced documents (and elements) will be style-aware.io.sf.carte.doc.dom4j
subpackage.Media handling
-setTargetMedium()
method of the document, which in DOM4J can
-be done with the XHTMLDocument.setTargetMedium()
method.XHTMLDocumentFactory
as you would use a
+DOMDocumentFactory
, and the produced documents (and elements) will
+be style-aware.css4j-dom4j
module in addition to the core
+module. The -dom4j module optionally depends on the -agent module, you do not need it unless you plan to use the DOM4J agent.Example with DOM4J
+SAXReader
:
+Reader re = ... [reader for XHTML document]
+InputSource source = new InputSource(re);
+SAXReader reader = new SAXReader(XHTMLDocumentFactory.getInstance());
+reader.setEntityResolver(new DefaultEntityResolver());
+XHTMLDocument document = (XHTMLDocument) reader.read(source);
+
+ViewCSS
+interface:
+CSSComputedProperties style = ((CSSStylableElement) element).getComputedStyle(null);
+String display = style.getPropertyValue("display");
+
+
+XHTMLDocumentFactory factory = XHTMLDocumentFactory.getInstance();
+// Next line is optional: default is TRUE, and is probably what you want
+// factory.setLenientSystemValues(false);
+HtmlDocumentBuilder builder = new HtmlDocumentBuilder(factory);
+// We do not set the EntityResolver, the HtmlDocumentBuilder does not need it
+Reader re = ... [reader for HTML document]
+InputSource source = new InputSource(re);
+XHTMLDocument document = (XHTMLDocument) builder.parse(source);
+
+XMLDocumentBuilder
+instead of dom4j's SAXReader
:
+XHTMLDocumentFactory factory = XHTMLDocumentFactory.getInstance();
+// Next line is optional: default is TRUE, and is probably what you want
+// factory.setLenientSystemValues(false);
+XMLDocumentBuilder builder = new XMLDocumentBuilder(factory);
+builder.setEntityResolver(new DefaultEntityResolver());
+Reader re = ... [reader for XML document]
+InputSource source = new InputSource(re);
+XHTMLDocument document = (XHTMLDocument) builder.parse(source);
+re.close();
+
+SAXParserFactory
:
+SAXParserFactory parserFactory = ...
+XMLDocumentBuilder builder = new XMLDocumentBuilder(factory, parserFactory);
+
+Clearing the User Agent sheet
+
+XHTMLDocumentFactory docFactory = XHTMLDocumentFactory.getInstance();
+
+
+docFactory.getStyleSheetFactory().getUserAgentStyleSheet(CSSDocument.ComplianceMode.STRICT).getCssRules().clear();
+
+STRICT
mode, i.e. that you use a DOCTYPE
,
+otherwise use QUIRKS
.
+