From 17ef7db427407d709ae60966e99f605ac01e7322 Mon Sep 17 00:00:00 2001
From: "Kevin S. Clarke"
Date: Sun, 14 Apr 2024 00:16:02 -0400
Subject: [PATCH 01/40] [WIP]
---
.../docs/recipes/code/0007-string-formats.md | 35 ++-
.../presentation/v3/PlaceholderCanvas.java | 4 +-
.../iiif/presentation/v3/ids/UriUtils.java | 2 +-
.../iiif/presentation/v3/properties/I18n.java | 48 ++--
.../v3/properties/I18nProperty.java | 113 +++++----
.../presentation/v3/properties/Label.java | 227 +++++++++++++++---
.../presentation/v3/properties/Property.java | 26 +-
.../presentation/v3/properties/Summary.java | 213 ++++++++++++++--
.../presentation/v3/properties/Value.java | 223 +++++++++++++++--
.../iiif/presentation/v3/utils/I18nUtils.java | 75 ++++--
.../resources/iiif_presentation_messages.xml | 4 +
.../v3/examples/CookbooksTest.java | 78 ++++++
.../v3/properties/I18nPropertyTest.java | 151 +-----------
13 files changed, 882 insertions(+), 317 deletions(-)
diff --git a/docs/content/en/docs/recipes/code/0007-string-formats.md b/docs/content/en/docs/recipes/code/0007-string-formats.md
index 9b6ae104..77d24ad3 100644
--- a/docs/content/en/docs/recipes/code/0007-string-formats.md
+++ b/docs/content/en/docs/recipes/code/0007-string-formats.md
@@ -1,7 +1,32 @@
-+++
-title = "0007-string-formats"
-weight = 0
-+++
+---
+title: "Embedding HTML in descriptive properties"
+linkTitle: "0007-string-formats"
+weight: 0
+---
-Content goes here!
+You want to have more control on how your metadata is displayed by adding links or simple formatting instructions to selected text blocks. For example, scientific names
+might need special formatting and links out to other sites benefit from being activatable. Legacy systems may also include rudimentary formatting in their output.
+| | |
+| :--- | :---: |
+| Recipe: | https://iiif.io/api/cookbook/recipe/0007-string-formats/ |
+| Manifest: | https://iiif.io/api/cookbook/recipe/0007-string-formats/manifest.json |
+
+### Method One
+
+For the first method, we use a Minter to create IDs for the components of the manifest. This allows us to use less code to create the same structures. It will, however,
+create different IDs than are used in the cookbook recipe.
+
+```java
+
+```
+
+### Method Two
+
+The second method will create a manifest with the same IDs that the cookbook recipe uses. It involves setting all the IDs manually, which involves a little more code.
+
+```java
+
+```
+
+{{% sandbox %}}
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/PlaceholderCanvas.java b/src/main/java/info/freelibrary/iiif/presentation/v3/PlaceholderCanvas.java
index 9df01628..87c1b4d6 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/PlaceholderCanvas.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/PlaceholderCanvas.java
@@ -10,6 +10,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import info.freelibrary.util.warnings.Eclipse;
+import info.freelibrary.util.warnings.PMD;
import info.freelibrary.iiif.presentation.v3.annotations.WebAnnotation;
import info.freelibrary.iiif.presentation.v3.ids.Minter;
@@ -37,7 +38,8 @@
* resource. A placeholder canvas is likely to have different dimensions to those of the canvas(es) of the resource that
* has the placeholderCanvas property.
*/
-@SuppressWarnings({ "PMD.TooManyMethods", "PMD.ExcessivePublicCount" })
+@SuppressWarnings({ PMD.TOO_MANY_METHODS, "PMD.TooManyMethods", PMD.EXCESSIVE_PUBLIC_COUNT,
+ "PMD.ExcessivePublicCount" })
public class PlaceholderCanvas extends AbstractCanvas
implements Resource, CanvasResource {
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/ids/UriUtils.java b/src/main/java/info/freelibrary/iiif/presentation/v3/ids/UriUtils.java
index 963cccf5..9439334b 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/ids/UriUtils.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/ids/UriUtils.java
@@ -28,7 +28,7 @@ private UriUtils() {
* @throws InvalidIdentifierException If the supplied identifier doesn't conform to IIIF's rules
*/
@SuppressWarnings({ "PMD.AvoidCatchingGenericException", PMD.AVOID_CATCHING_GENERIC_EXCEPTION,
- "PMD.AvoidCatchingNPE" })
+ "PMD.AvoidCatchingNPE", PMD.AVOID_CATCHING_NPE })
public static String checkID(final String aID, final boolean aHttpsReq) {
final URI id;
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18n.java b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18n.java
index b85cc38d..bcf2667a 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18n.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18n.java
@@ -17,13 +17,10 @@
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonProcessingException;
-import info.freelibrary.util.Logger;
-import info.freelibrary.util.LoggerFactory;
import info.freelibrary.util.warnings.PMD;
import info.freelibrary.iiif.presentation.v3.utils.I18nUtils;
import info.freelibrary.iiif.presentation.v3.utils.JSON;
-import info.freelibrary.iiif.presentation.v3.utils.MessageCodes;
import info.freelibrary.iiif.presentation.v3.utils.json.JsonParsingException;
/**
@@ -38,11 +35,6 @@ public class I18n implements Iterable {
*/
public static final String DEFAULT_LANG = "none";
- /**
- * The logger used by I18n.
- */
- private static final Logger LOGGER = LoggerFactory.getLogger(I18n.class, MessageCodes.BUNDLE);
-
/**
* The standard types of immutable lists in Java; it doesn't include third party libraries like Guava.
*/
@@ -65,6 +57,25 @@ public class I18n implements Iterable {
*/
private Locale myLocale;
+ /**
+ * Creates an internationalization, using the default language tag, from the supplied string value.
+ *
+ * @param aString An internationalized value
+ */
+ public I18n(final String aString) {
+ this(DEFAULT_LANG, aString);
+ }
+
+ /**
+ * Creates an internationalization, using the default language tag, from the supplied string value.
+ *
+ * @param aString An internationalized value
+ * @param aHtmlAllowed Whether the supplied value can contain HTML
+ */
+ public I18n(final String aString, final boolean aHtmlAllowed) {
+ this(DEFAULT_LANG, aString, aHtmlAllowed);
+ }
+
/**
* Creates an internationalization from the supplied language tag and string value.
*
@@ -153,7 +164,7 @@ public I18n(final String aLangTag, final List aStringList, final boolean
* in the list of strings after being disallowed
*/
public I18n(final Locale aLocale, final List aStringList, final boolean aHtmlValueAllowed) {
- myLocale = checkLangTag(aLocale);
+ myLocale = I18nUtils.checkLocale(aLocale);
isAllowingHTML = aHtmlValueAllowed;
if (!isAllowingHTML) {
@@ -194,7 +205,7 @@ public String getLang() {
*/
@JsonIgnore
public I18n setLang(final String aLangTag) {
- myLocale = checkLangTag(Locale.forLanguageTag(aLangTag));
+ myLocale = I18nUtils.checkLocale(Locale.forLanguageTag(aLangTag));
return this;
}
@@ -207,7 +218,7 @@ public I18n setLang(final String aLangTag) {
*/
@JsonIgnore
public I18n setLang(final Locale aLocale) {
- myLocale = checkLangTag(aLocale);
+ myLocale = I18nUtils.checkLocale(aLocale);
return this;
}
@@ -306,19 +317,4 @@ private Map> toMap() { // NOPMD
return Map.of(myLocale.toLanguageTag(), myStrings);
}
- /**
- * Checks the language tag of the supplied Locale. If the language tag is "und" the Locale is undefined and an
- * IllegalArgumentException is thrown.
- *
- * @param aLocale A locale
- * @return The valid locale
- * @throws IllegalArgumentException If the supplied locale is not a pre-defined locale
- */
- private Locale checkLangTag(final Locale aLocale) {
- if ("und".equals(aLocale.toLanguageTag())) {
- throw new IllegalArgumentException(LOGGER.getMessage(MessageCodes.JPA_020, aLocale.getDisplayName()));
- }
-
- return aLocale;
- }
}
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18nProperty.java b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18nProperty.java
index baf9b865..07a096db 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18nProperty.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/I18nProperty.java
@@ -7,18 +7,19 @@
import java.util.Map;
import java.util.Objects;
-import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
import info.freelibrary.util.Logger;
import info.freelibrary.util.LoggerFactory;
import info.freelibrary.util.StringUtils;
+import info.freelibrary.iiif.presentation.v3.utils.I18nUtils;
import info.freelibrary.iiif.presentation.v3.utils.MessageCodes;
/**
* A base class for label, summary, attribution, property, and metadata's label and value fields.
*/
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
class I18nProperty> {
/**
@@ -42,25 +43,28 @@ class I18nProperty> {
}
/**
- * Creates a property from an array of string(s).
+ * Creates a property from a matrix of internationalization values.
*
- * @param aStringArray An array of strings for the property
+ * @param aHtmlAllowed Whether HTML is allowed in the internationalization values
+ * @param aI18nMatrix A matrix of internationalization values
+ * @throws IllegalArgumentException If the supplied matrix isn't valid
*/
- I18nProperty(final String... aStringArray) {
- myI18ns = new ArrayList<>();
- addCheckedStrings(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ I18nProperty(final boolean aHtmlAllowed, final String[][] aI18nMatrix) {
+ myI18ns = List.of(I18nUtils.createI18ns(aHtmlAllowed, null, aI18nMatrix));
}
/**
- * Sets the string value of the property, removing all other previous strings.
+ * Creates a property from a matrix of internationalization values.
*
- * @param aStringArray A string value
- * @return True if the property's value was set
+ * @param aHtmlAllowed Whether HTML is allowed in the internationalization values
+ * @param aDefaultLangTag A default language tag that can be used with the matrix
+ * @param aI18nMatrix A matrix of internationalization values
+ * @throws IllegalArgumentException If the supplied matrix isn't valid
*/
- @JsonIgnore
- protected I18nProperty setStrings(final String... aStringArray) {
- myI18ns.clear();
- return addCheckedStrings(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ I18nProperty(final boolean aHtmlAllowed, final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ myI18ns = List.of(I18nUtils.createI18ns(aHtmlAllowed, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix));
}
/**
@@ -74,6 +78,35 @@ protected I18nProperty setI18ns(final I18n... aI18nArray) {
return addCheckedI18ns(aI18nArray);
}
+ /**
+ * Sets the internationalizations of the property from the supplied matrix of values.
+ *
+ * @param aHtmlAllowed Whether HTML is allowed in internationalized values
+ * @param aI18nMatrix A matrix of values to be turned into internationalizations in the property
+ * @return An I18n property
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ protected I18nProperty setI18ns(final boolean aHtmlAllowed, final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return addCheckedI18ns(I18nUtils.createI18ns(aHtmlAllowed, null, aI18nMatrix));
+ }
+
+ /**
+ * Sets the internationalizations of the property from the supplied matrix of values and a default language tag.
+ *
+ * @param aHtmlAllowed Whether HTML is allowed in internationalized values
+ * @param aDefaultLangTag A default language tag to use if one isn't supplied in the matrix
+ * @param aI18nMatrix A matrix of values to be turned into internationalizations in the property
+ * @return An I18n property
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ protected I18nProperty setI18ns(final boolean aHtmlAllowed, final String aDefaultLangTag,
+ final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return addCheckedI18ns(
+ I18nUtils.createI18ns(aHtmlAllowed, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix));
+ }
+
/**
* Gets a list of the property's internationalizations.
*
@@ -155,23 +188,40 @@ public String toString() {
}
/**
- * Adds a string value to the property.
+ * Adds an internationalization to the property.
*
- * @param aStringArray An array of strings to add to the property
+ * @param aI18nArray A list of internationalizations
* @return The property
*/
- protected I18nProperty addStrings(final String... aStringArray) {
- return addCheckedStrings(aStringArray);
+ protected I18nProperty addI18ns(final I18n... aI18nArray) {
+ return addCheckedI18ns(aI18nArray);
}
/**
- * Adds an internationalization to the property.
+ * Adds an internationalization to the property in the form of a matrix of values.
*
- * @param aI18nArray A list of internationalizations
+ * @param aHtmlAllowed Whether the internationalization values can contain HTML
+ * @param aI18nMatrix A matrix of values to turn into internationalizations
* @return The property
*/
- protected I18nProperty addI18ns(final I18n... aI18nArray) {
- return addCheckedI18ns(aI18nArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ protected I18nProperty addI18ns(final boolean aHtmlAllowed, final String[][] aI18nMatrix) {
+ return addCheckedI18ns(I18nUtils.createI18ns(aHtmlAllowed, null, aI18nMatrix));
+ }
+
+ /**
+ * Adds an internationalization to the property in the form of a matrix of values.
+ *
+ * @param aHtmlAllowed Whether HTML is allowed in the property's value(s)
+ * @param aDefaultLangTag A default language tag to use when the matrix values don't contain one
+ * @param aI18nMatrix A matrix of values to turn into internationalizations
+ * @return The property
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ protected I18nProperty addI18ns(final boolean aHtmlAllowed, final String aDefaultLangTag,
+ final String[][] aI18nMatrix) {
+ return addCheckedI18ns(
+ I18nUtils.createI18ns(aHtmlAllowed, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix));
}
/**
@@ -196,27 +246,6 @@ protected Object toMap() {
return map;
}
- /**
- * Adds strings to the I18nProperty after they've been checked and confirmed to not be null.
- *
- * @param aStringArray An array of strings
- * @return The property
- * @throws UnsupportedOperationException If a supplied string cannot be added as an I18n
- */
- private I18nProperty addCheckedStrings(final String... aStringArray) {
- Objects.requireNonNull(aStringArray, MessageCodes.JPA_001);
-
- for (final String string : aStringArray) {
- Objects.requireNonNull(string, MessageCodes.JPA_001);
-
- if (!myI18ns.add(new I18n(I18n.DEFAULT_LANG, string))) {
- throw new UnsupportedOperationException(LOGGER.getMessage(MessageCodes.JPA_043, string));
- }
- }
-
- return this;
- }
-
/**
* Adds internationalized values to the I18nProperty after they've been checked and confirmed to not be null.
*
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Label.java b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Label.java
index 67ffe93d..055410ab 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Label.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Label.java
@@ -1,8 +1,9 @@
package info.freelibrary.iiif.presentation.v3.properties;
+import java.util.Objects;
+
import com.fasterxml.jackson.annotation.JsonGetter;
-import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import info.freelibrary.iiif.presentation.v3.utils.I18nUtils;
@@ -14,8 +15,28 @@
* between pages or between a choice of images to display.
*/
@JsonDeserialize(using = LabelDeserializer.class)
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class Label extends I18nProperty
+ *
+ * For example:
*
- * @param aStringArray An array of strings for label
- * @throws IllegalArgumentException If the supplied string(s) have HTML markup
- */
- public Label(final String... aStringArray) {
- super(I18nUtils.createI18ns(false, aStringArray));
- }
-
- /**
- * Create a label from a language tag and a string value.
+ *
+ *
+ * Label label = new Label({ { "en", "An English value" }, { "A 'none' value" } });
+ *
+ *
+ *
*
- * @param aLangTag A language tag
- * @param aValue A string value
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- public Label(final String aLangTag, final String aValue) {
- this(new I18n(aLangTag, aValue));
+ @SuppressWarnings("PMD.UseVarargs")
+ public Label(final String[][] aI18nMatrix) {
+ super(I18nUtils.createI18ns(false, null, aI18nMatrix));
}
/**
- * Sets the internationalization value of the label, removing all other previous values.
+ * Creates a label from the supplied string matrix. This gives the constructor more flexibility when all the values
+ * are known up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * Label label = new Label("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
*
- * @param aStringArray An array of strings
- * @return True if the property's string value was set
- * @throws IllegalArgumentException If the supplied strings contain HTML markup
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- @Override
- @JsonIgnore
- public Label setStrings(final String... aStringArray) {
- myI18ns.clear();
- return addI18ns(I18nUtils.createI18ns(false, aStringArray));
+ @SuppressWarnings("PMD.UseVarargs")
+ public Label(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ super(I18nUtils.createI18ns(false, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix));
}
/**
* Sets the internationalizations of the label, removing all other previous internationalizations.
*
* @param aI18nArray An array of internationalizations
- * @return True if the property's internationalizations were set successfully
+ * @return This Label
* @throws IllegalArgumentException If the supplied internationalizations contain HTML markup
*/
@Override
@@ -74,14 +122,68 @@ public Label setI18ns(final I18n... aI18nArray) {
}
/**
- * Adds an array of strings to the label.
+ * Sets a label from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ *
+ * For example:
*
- * @param aStringArray An array of strings
- * @return The label
+ *
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Label
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- @Override
- public Label addStrings(final String... aStringArray) {
- return (Label) super.addI18ns(I18nUtils.createI18ns(false, aStringArray));
+ @SuppressWarnings("PMD.UseVarargs")
+ public Label setI18ns(final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return (Label) super.addI18ns(false, null, aI18nMatrix);
+ }
+
+ /**
+ * Sets a label from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * label.setI18ns("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Label
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Label setI18ns(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return (Label) super.addI18ns(false, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix);
}
/**
@@ -95,6 +197,69 @@ public Label addI18ns(final I18n... aI18nArray) {
return (Label) super.addI18ns(I18nUtils.validateI18ns(false, aI18nArray));
}
+ /**
+ * Adds label values from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Label
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Label addI18ns(final String[][] aI18nMatrix) {
+ return (Label) super.addI18ns(false, aI18nMatrix);
+ }
+
+ /**
+ * Adds label values from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * label.addI18ns("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Label
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Label addI18ns(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ return (Label) super.addI18ns(false, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix);
+ }
+
@Override
@JsonGetter(JsonKeys.LABEL)
protected Object toMap() {
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Property.java b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Property.java
index 5c919f0b..aa2911db 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Property.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Property.java
@@ -3,6 +3,8 @@
import java.util.Objects;
+import info.freelibrary.iiif.presentation.v3.utils.I18nUtils;
+
/**
* A generic property that can be used in navPlace features(s).
*/
@@ -23,13 +25,27 @@ public Property(final String aName, final I18n... aI18nArray) {
}
/**
- * Creates a new property from the supplied name and strings.
+ * Creates a new property from the supplied name and I18n values matrix.
+ *
+ * @param aName A property name
+ * @param aMatrix A matrix of strings that can be composed into a series of I18ns
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Property(final String aName, final String[][] aMatrix) {
+ super(I18nUtils.createI18ns(false, null, aMatrix));
+ myName = Objects.requireNonNull(aName);
+ }
+
+ /**
+ * Creates a new property from the supplied name, default language tag, and I18n values matrix.
*
* @param aName A property name
- * @param aStringArray An array of property values
+ * @param aDefaultLangTag A default language tag to use with the supplied matrix
+ * @param aMatrix A matrix of strings that can be composed into a series of I18ns
*/
- public Property(final String aName, final String... aStringArray) {
- super(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Property(final String aName, final String aDefaultLangTag, final String[][] aMatrix) {
+ super(I18nUtils.createI18ns(false, aDefaultLangTag, aMatrix));
myName = Objects.requireNonNull(aName);
}
@@ -37,7 +53,7 @@ public Property(final String aName, final String... aStringArray) {
* A private constructor just used by Jackson for its deserialization process.
*/
private Property() {
- super(new String[] {});
+ super(new I18n[] {});
}
/**
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Summary.java b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Summary.java
index dfb8f1da..c3df6a02 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Summary.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Summary.java
@@ -1,9 +1,12 @@
package info.freelibrary.iiif.presentation.v3.properties;
+import java.util.Objects;
+
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import info.freelibrary.iiif.presentation.v3.utils.I18nUtils;
import info.freelibrary.iiif.presentation.v3.utils.JsonKeys;
/**
@@ -12,8 +15,28 @@
* as an alternative user interface when the metadata property is not currently being rendered.
*/
@JsonDeserialize(using = SummaryDeserializer.class)
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class Summary extends I18nProperty {
+ /**
+ * Creates a summary using the 'none' language tag.
+ *
+ * @param aValue A value
+ */
+ public Summary(final String aValue) {
+ this(new I18n(I18n.DEFAULT_LANG, aValue, true));
+ }
+
+ /**
+ * Creates a summary using the supplied language tag and value.
+ *
+ * @param aLangTag A language tag
+ * @param aValue A value of the summary
+ */
+ public Summary(final String aLangTag, final String aValue) {
+ this(new I18n(aLangTag, aValue, true));
+ }
+
/**
* Creates a summary from the supplied internationalization(s).
*
@@ -24,23 +47,129 @@ public Summary(final I18n... aI18nArray) {
}
/**
- * Creates a summary from the supplied string(s).
+ * Creates a summary from the supplied string matrix. This gives the constructor more flexibility when all the
+ * values are known up-front, but also provides the opportunity to submit invalid data. When invalid data is
+ * submitted, an IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * Summary summary = new Summary({ { "en", "An English value" }, { "A 'none' value" } });
+ *
+ *
+ *
*
- * @param aStringArray An array of strings for the summary
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- public Summary(final String... aStringArray) {
- super(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Summary(final String[][] aI18nMatrix) {
+ super(I18nUtils.createI18ns(true, null, aI18nMatrix));
}
/**
- * Sets the string value of the property, removing all other previous strings.
+ * Creates a summary from the supplied string matrix. This gives the constructor more flexibility when all the
+ * values are known up-front, but also provides the opportunity to submit invalid data. When invalid data is
+ * submitted, an IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
*
- * @param aStringArray A string value
- * @return True if the property's value was set
+ *
+ *
+ * Summary summary = new Summary("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- @Override
- public Summary setStrings(final String... aStringArray) {
- return (Summary) super.setStrings(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Summary(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ super(I18nUtils.createI18ns(true, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix));
+ }
+
+ /**
+ * Sets a summary from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This summary
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Summary setI18ns(final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return (Summary) super.addI18ns(true, null, aI18nMatrix);
+ }
+
+ /**
+ * Sets a value from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * summary.setI18ns("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This summary
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Summary setI18ns(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return (Summary) super.addI18ns(true, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix);
}
/**
@@ -55,14 +184,66 @@ public Summary setI18ns(final I18n... aI18nArray) {
}
/**
- * Adds a string value to the property.
+ * Adds summary values from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ *
+ * For example:
*
- * @param aStringArray An array of strings to add to the property
- * @return The property
+ *
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This summary
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- @Override
- public Summary addStrings(final String... aStringArray) {
- return (Summary) super.addStrings(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Summary addI18ns(final String[][] aI18nMatrix) {
+ return (Summary) super.addI18ns(true, aI18nMatrix);
+ }
+
+ /**
+ * Adds summary values from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * summary.addI18ns("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This summary
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Summary addI18ns(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ return (Summary) super.addI18ns(true, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix);
}
/**
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Value.java b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Value.java
index 48a692ee..0dcf2bc2 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Value.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/properties/Value.java
@@ -1,17 +1,40 @@
package info.freelibrary.iiif.presentation.v3.properties;
+import java.util.Objects;
+
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import info.freelibrary.iiif.presentation.v3.utils.I18nUtils;
import info.freelibrary.iiif.presentation.v3.utils.JsonKeys;
/**
* A human readable internationalized value.
*/
@JsonDeserialize(using = ValueDeserializer.class)
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class Value extends I18nProperty {
+ /**
+ * Creates a value using the 'none' language tag.
+ *
+ * @param aValue A value
+ */
+ public Value(final String aValue) {
+ this(new I18n(I18n.DEFAULT_LANG, aValue, true));
+ }
+
+ /**
+ * Creates a value using the supplied language tag and value.
+ *
+ * @param aLangTag A language tag
+ * @param aValue A value of the label
+ */
+ public Value(final String aLangTag, final String aValue) {
+ this(new I18n(aLangTag, aValue, true));
+ }
+
/**
* Creates a value from the supplied internationalization(s).
*
@@ -22,30 +45,136 @@ public Value(final I18n... aI18nArray) {
}
/**
- * Creates a value from the supplied string(s).
+ * Creates a value from the supplied string matrix. This gives the constructor more flexibility when all the values
+ * are known up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ *
+ * For example:
*
- * @param aStringArray An array of strings for the value
+ *
+ *
+ * Value value = new Value({ { "en", "An English value" }, { "A 'none' value" } });
+ *
+ *
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- public Value(final String... aStringArray) {
- super(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Value(final String[][] aI18nMatrix) {
+ super(I18nUtils.createI18ns(true, null, aI18nMatrix));
}
/**
- * Sets the string value of the property, removing all other previous strings.
+ * Creates a value from the supplied string matrix. This gives the constructor more flexibility when all the values
+ * are known up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * Value value = new Value("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
*
- * @param aStringArray A string value
- * @return True if the property's value was set
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- @Override
- public Value setStrings(final String... aStringArray) {
- return (Value) super.setStrings(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Value(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ super(I18nUtils.createI18ns(true, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix));
+ }
+
+ /**
+ * Sets a value from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Label
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Value setI18ns(final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return (Value) super.addI18ns(true, null, aI18nMatrix);
+ }
+
+ /**
+ * Sets a value from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * value.setI18ns("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Value
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Value setI18ns(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ myI18ns.clear();
+ return (Value) super.addI18ns(true, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix);
}
/**
- * Sets the internationalization of the property, removing all other previous internationalizations.
+ * Sets the I18n values, removing all other previous internationalizations.
*
* @param aI18nArray An array of I18n(s).
- * @return True if the property's value was set
+ * @return This Value
*/
@Override
public Value setI18ns(final I18n... aI18nArray) {
@@ -53,21 +182,73 @@ public Value setI18ns(final I18n... aI18nArray) {
}
/**
- * Adds a string value to the property.
+ * Adds values from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the 'none' language tag)
+ *
+ *
+ *
+ * For example:
*
- * @param aStringArray An array of strings to add to the property
- * @return The property
+ *
+ *
+ *
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Value
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
*/
- @Override
- public Value addStrings(final String... aStringArray) {
- return (Value) super.addStrings(aStringArray);
+ @SuppressWarnings("PMD.UseVarargs")
+ public Value addI18ns(final String[][] aI18nMatrix) {
+ return (Value) super.addI18ns(true, aI18nMatrix);
+ }
+
+ /**
+ * Adds values from the supplied string matrix. This provides more flexibility when all the values are known
+ * up-front, but also provides the opportunity to submit invalid data. When invalid data is submitted, an
+ * IllegalArgumentException is thrown.
+ *
+ * The submitted matrix should be an array that contains arrays of:
+ *
+ *
1) pairs of language tag and internationalizations
+ *
2) internationalizations without the language tag (which will get the default language tag)
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ *
+ * value.addI18ns("fr", { { "en", "An English value" }, { "A French value" } });
+ *
+ *
+ *
+ *
+ * @param aDefaultLangTag A default language tag to use when one isn't supplied
+ * @param aI18nMatrix An array of string arrays to be composed into internationalizations
+ * @return This Value
+ * @throws IllegalArgumentException If the supplied matrix doesn't contain arrays with either zero, one, or two
+ * elements
+ */
+ @SuppressWarnings("PMD.UseVarargs")
+ public Value addI18ns(final String aDefaultLangTag, final String[][] aI18nMatrix) {
+ return (Value) super.addI18ns(true, Objects.requireNonNull(aDefaultLangTag), aI18nMatrix);
}
/**
- * Adds an internationalization to the property.
+ * Adds a value internationalizations.
*
- * @param aI18nArray A list of internationalizations
- * @return The property
+ * @param aI18nArray A list of internationalization values
+ * @return This Value
*/
@Override
public Value addI18ns(final I18n... aI18nArray) {
diff --git a/src/main/java/info/freelibrary/iiif/presentation/v3/utils/I18nUtils.java b/src/main/java/info/freelibrary/iiif/presentation/v3/utils/I18nUtils.java
index 1a0864af..326e9843 100644
--- a/src/main/java/info/freelibrary/iiif/presentation/v3/utils/I18nUtils.java
+++ b/src/main/java/info/freelibrary/iiif/presentation/v3/utils/I18nUtils.java
@@ -8,6 +8,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.regex.Pattern;
import org.jsoup.Jsoup;
@@ -21,6 +22,7 @@
import info.freelibrary.util.Logger;
import info.freelibrary.util.LoggerFactory;
+import info.freelibrary.util.StringUtils;
import info.freelibrary.util.warnings.PMD;
import info.freelibrary.iiif.presentation.v3.properties.I18n;
@@ -28,7 +30,7 @@
/**
* A utilities class for internationalizations.
*/
-@SuppressWarnings(PMD.TOO_MANY_METHODS)
+@SuppressWarnings({ PMD.GOD_CLASS, "PMD.GodClass" })
public final class I18nUtils {
/** Logger used by the I18nUtils class. */
@@ -68,6 +70,9 @@ public final class I18nUtils {
/** A less than symbol used for closing tag names. */
private static final String LESS_THAN = "<";
+ /** The largest valid array size for I18n string matrices. */
+ private static final int MAX_ARRAY_SIZE = 2;
+
/**
* A constructor for I18nUtils.
*/
@@ -238,41 +243,71 @@ public static I18n[] validateI18ns(final boolean aHtmlAllowed, final I18n... aI1
}
/**
- * Creates an array of I18ns from an array of strings, checking for HTML if it isn't allowed.
+ * Creates an array of I18ns from a string matrix, checking for HTML if it isn't allowed if that flag is passed.
*
- * @param aHtmlAllowed Whether HTML markup is allowed in the I18n
- * @param aStringArray The strings to convert into I18ns
+ * @param aHtmlAllowed Whether HTML mark-up is allowed in the I18ns
+ * @param aLangTag A default language tag to use with the I18n matrix
+ * @param aMatrix A matrix of strings to convert into I18ns
* @return An array of I18ns
- * @throws IllegalArgumentException If HTML is not allowed, but one of the strings contains markup
+ * @throws IllegalArgumentException If HTML is not allowed, but one of the strings contains mark-up
*/
- public static I18n[] createI18ns(final boolean aHtmlAllowed, final String... aStringArray) {
+ @SuppressWarnings({ "PMD.UseVarargs", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity" })
+ public static I18n[] createI18ns(final boolean aHtmlAllowed, final String aLangTag, final String[][] aMatrix) {
+ final String langTag = StringUtils.trimToNull(aLangTag) == null ? I18n.DEFAULT_LANG
+ : checkLocale(Locale.forLanguageTag(aLangTag)).toLanguageTag();
final List i18ns = new ArrayList<>();
- for (final String string : aStringArray) {
- final I18n i18n;
+ for (final String[] values : aMatrix) {
+ if (values.length > MAX_ARRAY_SIZE) {
+ throw new IllegalArgumentException(LOGGER.getMessage(MessageCodes.BUNDLE, MessageCodes.JPA_145));
+ }
- // If our internationalization isn't supposed to have HTML, strip any that's found there
if (aHtmlAllowed) {
- // Since our strings don't have language codes, we'll just use the default of "none"
- i18n = new I18n(I18n.DEFAULT_LANG, cleanHTML(string), aHtmlAllowed);
- } else {
- if (hasHTML(string)) {
- // We don't really need to see this warning unless there might be HTML in the string
- LOGGER.warn(MessageCodes.JPA_033, string);
+ if (values.length == SINGLE_INSTANCE) {
+ i18ns.add(new I18n(langTag, cleanHTML(values[0]), aHtmlAllowed));
+ } else if (values.length == MAX_ARRAY_SIZE) {
+ i18ns.add(new I18n(values[0], cleanHTML(values[1]), aHtmlAllowed));
+ } else {
+ LOGGER.warn(MessageCodes.JPA_146);
+ }
+ } else if (values.length == SINGLE_INSTANCE) {
+ if (hasHTML(values[0])) {
+ LOGGER.warn(MessageCodes.JPA_033, values[0]);
}
- // Since our strings don't have language codes, we'll just use the default of "none"
- i18n = new I18n(I18n.DEFAULT_LANG, stripHTML(string), aHtmlAllowed);
- }
+ i18ns.add(new I18n(langTag, cleanHTML(values[0]), aHtmlAllowed));
+ } else if (values.length == MAX_ARRAY_SIZE) {
+ if (hasHTML(values[1])) {
+ LOGGER.warn(MessageCodes.JPA_033, values[1]);
+ }
- i18ns.add(i18n);
+ i18ns.add(new I18n(values[0], stripHTML(values[1]), aHtmlAllowed));
+ } else {
+ LOGGER.warn(MessageCodes.JPA_146);
+ }
}
return i18ns.toArray(new I18n[0]);
}
/**
- * This is a workaround for Jsoup not properly handling text that has a stand-alone < character. It's definitely
+ * Checks the language tag of the supplied Locale. If the language tag is "und" the Locale is undefined and an
+ * IllegalArgumentException is thrown.
+ *
+ * @param aLocale A locale
+ * @return The valid locale
+ * @throws IllegalArgumentException If the supplied locale is not a pre-defined locale
+ */
+ public static Locale checkLocale(final Locale aLocale) {
+ if ("und".equals(aLocale.toLanguageTag())) {
+ throw new IllegalArgumentException(LOGGER.getMessage(MessageCodes.JPA_020, aLocale.getDisplayName()));
+ }
+
+ return aLocale;
+ }
+
+ /**
+ * This is a workaround for JSoup not properly handling text that has a stand-alone < character. It's definitely
* not perfect (i.e. this obviously isn't a full-fledged HTML parser).
*
* @param aString A string to check for less than characters.
diff --git a/src/main/resources/iiif_presentation_messages.xml b/src/main/resources/iiif_presentation_messages.xml
index d6cf1374..b3a7bce4 100644
--- a/src/main/resources/iiif_presentation_messages.xml
+++ b/src/main/resources/iiif_presentation_messages.xml
@@ -113,4 +113,8 @@
Stylesheet on PaintingAnnotation must have a value or URIThe paintWith() method cannot be used if the canvas doesn't have a minter setThe supplementWith() method cannot be usd if the canvas doesn't have a minter set
+ An I18n matrix must contain a single value or key value pairs, nothing more
+ An empty string matrix was passed to I18nUtils.createI18ns()
+
+
diff --git a/src/test/java/info/freelibrary/iiif/presentation/v3/examples/CookbooksTest.java b/src/test/java/info/freelibrary/iiif/presentation/v3/examples/CookbooksTest.java
index 55c7ab13..12263cbe 100644
--- a/src/test/java/info/freelibrary/iiif/presentation/v3/examples/CookbooksTest.java
+++ b/src/test/java/info/freelibrary/iiif/presentation/v3/examples/CookbooksTest.java
@@ -422,6 +422,84 @@ public final void test0006WithoutMinter() throws IOException {
assertEquals(getExpected("0006-text-language/manifest"), normalizeIDs(manifest.toString()));
}
+ @Test
+ @SuppressWarnings("Checkstyle.LineLengthCheck")
+ public final void test0006WithMinterAndMatrix() throws IOException {
+
+ final var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0006-text-language/manifest.json",
+ new Label(new String[][] { { "en", "Whistler's Mother" }, { "fr", "La Mère de Whistler" } }));
+
+ final var canvas = new Canvas(MinterFactory.getMinter(manifest));
+ final var imageContent = new ImageContent(
+ "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother/full/max/0/default.jpg");
+ final var service = new ImageService3(LEVEL_ONE,
+ "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother");
+
+ final var creator = new Metadata(new Label(new String[][] { { "en", "Creator" }, { "fr", "Auteur" } }),
+ new Value("Whistler, James Abbott McNeill"));
+
+ final var creatir = new Metadata(new Label(new I18n("en", "Creator"), new I18n("fr", "Auteur")),
+ new Value("Whistler, James Abbott McNeill"));
+
+ final var subject = new Metadata(new Label(new String[][] { { "en", "Subject" }, { "fr", "Sujet" } }),
+ new Value(new String[][] { { "en", "McNeill Anna Matilda, mother of Whistler (1804-1881)" },
+ { "fr", "McNeill Anna Matilda, mère de Whistler (1804-1881)" } }));
+
+ final var summaryEN =
+ new I18n("en", "Arrangement in Grey and Black No. 1, also called Portrait of the Artist's Mother.");
+ final var summaryFR =
+ new I18n("fr", "Arrangement en gris et noir n°1, also called Portrait de la mère de l'artiste.");
+
+ final var reqStmtLabel = new Label(new I18n("en", "Held By"), new I18n("fr", "Détenu par"));
+ final var reqStmt = new Value("Musée d'Orsay, Paris, France");
+
+ manifest.setMetadata(creator, subject);
+ manifest.setSummary(new Summary(summaryEN, summaryFR));
+ manifest.setRequiredStatement(new RequiredStatement(reqStmtLabel, reqStmt));
+
+ imageContent.setWidthHeight(1114, 991).setFormat(IMAGE_JPEG).setServices(service);
+ manifest.addCanvases(canvas.setWidthHeight(1114, 991).paintWith(imageContent));
+
+ System.out.println(manifest);
+ }
+
+ /**
+ * Runs the 0007 cookbook example with a minter.
+ */
+ @Test
+ @SuppressWarnings("Checkstyle.LineLengthCheck")
+ public final void test0007WithMinter() throws IOException {
+ final var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0007-string-formats/manifest.json",
+ new Label("en", "Picture of Göttingen taken during the 2019 IIIF Conference"));
+ final var summary = new Summary(new I18n("en",
+ "
",
+ true));
+
+ final var label = new Label("en", "Author");
+ final var value = new Value(
+ new I18n("none", "Glen Robson", true));
+
+ final var metadata = new Metadata(label, value);
+
+ // System.out.println(manifest);
+
+ // Don't include this in the example; it's just a sanity check
+ // assertEquals(getExpected("0006-text-language/manifest"), normalizeIDs(manifest.toString()));
+ }
+
+ /**
+ * Runs the 0007 cookbook example without a minter.
+ */
+ @Test
+ @SuppressWarnings("Checkstyle.LineLengthCheck")
+ public final void test0007WithoutMinter() throws IOException {
+
+ // System.out.println(manifest);
+
+ // Don't include this in the example; it's just a sanity check
+ // assertEquals(getExpected("0006-text-language/manifest"), normalizeIDs(manifest.toString()));
+ }
+
/**
* Gets the expected JSON output with its IDs normalized.
*
diff --git a/src/test/java/info/freelibrary/iiif/presentation/v3/properties/I18nPropertyTest.java b/src/test/java/info/freelibrary/iiif/presentation/v3/properties/I18nPropertyTest.java
index eee76849..7289c6e4 100644
--- a/src/test/java/info/freelibrary/iiif/presentation/v3/properties/I18nPropertyTest.java
+++ b/src/test/java/info/freelibrary/iiif/presentation/v3/properties/I18nPropertyTest.java
@@ -2,11 +2,7 @@
package info.freelibrary.iiif.presentation.v3.properties;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.List;
import org.junit.Test;
@@ -21,12 +17,6 @@ public class I18nPropertyTest {
/** Sample test value. */
private static final String TWO = "two";
- /** Sample test value. */
- private static final String THREE = "three";
-
- /** Sample test value. */
- private static final String FOUR = "four";
-
/** Sample test value. */
private static final String ENG = "eng";
@@ -41,33 +31,6 @@ public void testI18nPropertyValueArray() {
assertEquals(ONE, new I18nProperty(new I18n(ENG, ONE), new I18n(FRE, TWO)).getString());
}
- /**
- * Tests construction of an internationalized property.
- */
- @Test
- public void testI18nPropertyStringArray() {
- assertEquals(ONE, new I18nProperty(ONE, TWO).getString());
- }
-
- /**
- * Tests setting the strings of an internationalized property.
- */
- @Test
- public void testSetStrings() {
- assertEquals(TWO, new I18nProperty(ONE).setStrings(TWO).getString());
- }
-
- /**
- * Tests getting the internationalizations from a property.
- */
- @Test
- public void testGetI18ns() {
- final I18nProperty> i18np = new I18nProperty<>(ONE, TWO);
- final List values = i18np.getI18ns();
-
- assertEquals(2, values.size());
- }
-
/**
* Test of hashCode with double values.
*/
@@ -95,120 +58,10 @@ public void testHashCodeDifferentDoubleValue() {
*/
@Test
public void testHashCode() {
- final I18nProperty> i18np1 = new I18nProperty<>(ONE, TWO);
- final I18nProperty> i18np2 = new I18nProperty<>(ONE, TWO);
-
- assertEquals(i18np1.hashCode(), i18np2.hashCode());
- }
-
- /**
- * Test of hashCode override for multiple strings.
- */
- @Test
- public void testHashCodeMultipleValues() {
- final I18nProperty> i18np1 = new I18nProperty<>(ONE).addStrings(TWO, THREE);
- final I18nProperty> i18np2 = new I18nProperty<>(ONE).addStrings(TWO, THREE);
+ final I18nProperty> i18np1 = new I18nProperty<>(new I18n(ENG, ONE));
+ final I18nProperty> i18np2 = new I18nProperty<>(new I18n(ENG, ONE));
assertEquals(i18np1.hashCode(), i18np2.hashCode());
}
- /**
- * Test of hashCode override for multiple strings.
- */
- @Test
- public void testNotEqualHashCode() {
- final I18nProperty> i18np1 = new I18nProperty<>(ONE);
- final I18nProperty> i18np2 = new I18nProperty<>(TWO);
-
- assertNotEquals(i18np1.hashCode(), i18np2.hashCode());
- }
-
- /**
- * Test of hashCode override for multiple strings.
- */
- @Test
- public void testNotEqualHashCodeMultipleValues() {
- final I18nProperty> i18np1 = new I18nProperty<>(ONE).addStrings(TWO, THREE);
- final I18nProperty> i18np2 = new I18nProperty<>(ONE).addStrings(TWO, FOUR);
-
- assertNotEquals(i18np1.hashCode(), i18np2.hashCode());
- }
-
- /**
- * Tests getting an internationalized property as a string value.
- */
- @Test
- public void testGetString() {
- assertEquals(ONE, new I18nProperty(ONE).getString());
- }
-
- /**
- * Tests getting an internationalized property as a string value.
- */
- @Test
- public void testGetStringNull() {
- final I18nProperty i18np = new I18nProperty<>(ONE);
-
- i18np.getI18ns().remove(0);
-
- assertEquals(null, i18np.getString());
- }
-
- /**
- * Tests adding strings to an internationalized property.
- */
- @Test
- public void testAddStrings() {
- assertEquals(3, new I18nProperty(ONE).addStrings(TWO, THREE).getI18ns().size());
- }
-
- /**
- * Tests adding internationalizations to a property.
- */
- @Test
- public void testAddI18ns() {
- assertEquals(3,
- new I18nProperty(ONE).addI18ns(new I18n(ENG, TWO), new I18n(FRE, THREE)).getI18ns().size());
- }
-
- /**
- * Tests whether an internationalized property has strings.
- */
- @Test
- public void testHasStrings() {
- assertTrue(new I18nProperty(ONE).hasStrings());
- }
-
- /**
- * Tests whether an internationalized property has strings.
- */
- @Test
- public void testHasNoStrings() {
- final I18nProperty> i18np = new I18nProperty<>(ONE);
-
- i18np.getI18ns().remove(0);
-
- assertFalse(i18np.hasStrings());
- }
-
- /**
- * Tests getting a simple value.
- */
- @Test
- public void testGetStringSimple() {
- assertEquals(ONE, new I18nProperty(ONE).getString());
- }
-
- /**
- * Tests getting a null value.
- */
- @Test
- public void testGetI18nsNull() {
- final I18nProperty> i18np = new I18nProperty<>(ONE);
-
- i18np.getI18ns().remove(0);
-
- assertEquals(null, i18np.toMap());
- }
-
}
From a6cfe0aabf1ed4bfac900a24753b670ed896ccb4 Mon Sep 17 00:00:00 2001
From: "Kevin S. Clarke"
Date: Thu, 18 Apr 2024 01:13:21 -0400
Subject: [PATCH 02/40] Update docs, fix parsing bug
---
.../en/docs/recipes/code/0001-mvm-image.md | 2 +
.../en/docs/recipes/code/0002-mvm-audio.md | 2 +
.../en/docs/recipes/code/0003-mvm-video.md | 2 +
.../en/docs/recipes/code/0004-canvas-size.md | 2 +
.../docs/recipes/code/0005-image-service.md | 2 +
.../docs/recipes/code/0006-text-language.md | 62 ++-
.../docs/recipes/code/0007-string-formats.md | 46 ++-
.../en/docs/recipes/code/0008-rights.md | 75 +++-
.../en/docs/recipes/code/0009-book-1.md | 156 +++++++-
docs/content/en/docs/sandbox/_index.md | 6 +-
pom.xml | 1 +
.../iiif/presentation/v3/Manifest.java | 22 ++
.../AbstractI18nStdDeserializer.java | 3 +-
.../v3/properties/I18nProperty.java | 182 +++------
.../presentation/v3/properties/Label.java | 204 +---------
.../presentation/v3/properties/Property.java | 30 +-
.../presentation/v3/properties/Summary.java | 203 +---------
.../presentation/v3/properties/Value.java | 199 +---------
.../iiif/presentation/v3/utils/I18nUtils.java | 80 ++--
.../resources/iiif_presentation_messages.xml | 8 +-
.../v3/examples/CookbooksTest.java | 356 ++++++++++++++++--
21 files changed, 804 insertions(+), 839 deletions(-)
diff --git a/docs/content/en/docs/recipes/code/0001-mvm-image.md b/docs/content/en/docs/recipes/code/0001-mvm-image.md
index 3ae91731..46ba4dc5 100644
--- a/docs/content/en/docs/recipes/code/0001-mvm-image.md
+++ b/docs/content/en/docs/recipes/code/0001-mvm-image.md
@@ -4,6 +4,8 @@ linkTitle: "0001-mvm-image"
weight: 0
---
+### Use Case
+
The simplest viable manifest for image content. If all you have for an object is one image on the web and a label to go along with it, this pattern turns it into a IIIF
Presentation resource.
diff --git a/docs/content/en/docs/recipes/code/0002-mvm-audio.md b/docs/content/en/docs/recipes/code/0002-mvm-audio.md
index c8fffc58..a382ddee 100644
--- a/docs/content/en/docs/recipes/code/0002-mvm-audio.md
+++ b/docs/content/en/docs/recipes/code/0002-mvm-audio.md
@@ -4,6 +4,8 @@ linkTitle: "0002-mvn-audio"
weight: 0
---
+### Use Case
+
The simplest viable manifest for audio content. This pattern presents a single audio file in a IIIF Presentation resource.
| | |
diff --git a/docs/content/en/docs/recipes/code/0003-mvm-video.md b/docs/content/en/docs/recipes/code/0003-mvm-video.md
index 483d3fe9..52be5721 100644
--- a/docs/content/en/docs/recipes/code/0003-mvm-video.md
+++ b/docs/content/en/docs/recipes/code/0003-mvm-video.md
@@ -4,6 +4,8 @@ linkTitle: "0003-mvn-video"
weight: 0
---
+### Use Case
+
The simplest viable manifest for video content. This pattern presents a single video file in a IIIF Presentation resource.
| | |
diff --git a/docs/content/en/docs/recipes/code/0004-canvas-size.md b/docs/content/en/docs/recipes/code/0004-canvas-size.md
index f3b7aff2..7bb1308b 100644
--- a/docs/content/en/docs/recipes/code/0004-canvas-size.md
+++ b/docs/content/en/docs/recipes/code/0004-canvas-size.md
@@ -4,6 +4,8 @@ linkTitle: "0004-canvas-size"
weight: 0
---
+### Use Case
+
You have an image ready for annotating that is expected to be replaced later by a higher resolution image. You would like to provide a sufficiently high-resolution
coordinate space to position the annotations precisely. IIIF Presentation v3.0 allows you to describe a Canvas with the dimensions of the larger image to come, fill it
with the smaller image you have ready now, and capture annotations on the smaller image in confidence that they will be positioned appropriately on the larger image when
diff --git a/docs/content/en/docs/recipes/code/0005-image-service.md b/docs/content/en/docs/recipes/code/0005-image-service.md
index f301ed51..f4f32fc0 100644
--- a/docs/content/en/docs/recipes/code/0005-image-service.md
+++ b/docs/content/en/docs/recipes/code/0005-image-service.md
@@ -4,6 +4,8 @@ linkTitle: "0005-image-service"
weight: 0
---
+### Use Case
+
You have a rare or special object in your collection that you’d like to make available for research to a large audience, including those without the ability to be
present at your institution to examine the object in person. Presenting a medium-resolution flat digital image of the object using IIIF is possible, but if you have
implemented a IIIF Image API service, you have significantly enhanced interaction possibilities for research and engagement. Specifying a IIIF Image API service in your
diff --git a/docs/content/en/docs/recipes/code/0006-text-language.md b/docs/content/en/docs/recipes/code/0006-text-language.md
index 971b7429..7bdb1ffe 100644
--- a/docs/content/en/docs/recipes/code/0006-text-language.md
+++ b/docs/content/en/docs/recipes/code/0006-text-language.md
@@ -4,6 +4,8 @@ linkTitle: "0006-text-language"
weight: 0
---
+### Use Case
+
In some cases, a IIIF resource may have a title or description (label or summary) in more than one language; or, the publisher of the content may want to provide
descriptive metadata field labels in multiple languages for different language audiences, for example supplying a label for a creator field in both English and French
(“Creator”, “Auteur”).
@@ -15,25 +17,26 @@ descriptive metadata field labels in multiple languages for different language a
### Method One
-For the first method, we use a Minter to create IDs for the components of the manifest. This allows us to use less code to create the same structures. It will, however,
-create different IDs than are used in the cookbook recipe.
+For the first method, we use a `Minter` to create IDs for the components of the manifest. This allows us to use less code to create the same structures. It will,
+however, create different IDs than are used in the cookbook recipe. It also uses `I18n` objects to create the internationalizations.
_Note that `LEVEL_ONE` and `IMAGE_JPEG` are static imports whose classes (`ImageService3.Profile` and `MediaType`) are predefined in this site's code sandbox._
```java
var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0006-text-language/manifest.json",
new Label(new I18n("en", "Whistler's Mother"), new I18n("fr", "La Mère de Whistler")));
+
var canvas = new Canvas(MinterFactory.getMinter(manifest));
var imageContent = new ImageContent("https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother/full/max/0/default.jpg");
var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother");
-var creatorLabel = new Label(new I18n("en", "Creator"), new I18n("fr", "Auteur"));
-var creator = new Metadata(creatorLabel, new Value("Whistler, James Abbott McNeill"));
+var creator = new Metadata(new Label(new I18n("en", "Creator"), new I18n("fr", "Auteur")),
+ new Value("Whistler, James Abbott McNeill"));
-var subjectLabel = new Label(new I18n("en", "Subject"), new I18n("fr", "Sujet"));
var subjectEN = new I18n("en", "McNeill Anna Matilda, mother of Whistler (1804-1881)");
var subjectFR = new I18n("fr", "McNeill Anna Matilda, mère de Whistler (1804-1881)");
-var subject = new Metadata(subjectLabel, new Value(subjectEN, subjectFR));
+var subject = new Metadata(new Label(new I18n("en", "Subject"), new I18n("fr", "Sujet")),
+ new Value(subjectEN, subjectFR));
var summaryEN = new I18n("en", "Arrangement in Grey and Black No. 1, also called Portrait of the Artist's Mother.");
var summaryFR = new I18n("fr", "Arrangement en gris et noir n°1, also called Portrait de la mère de l'artiste.");
@@ -53,13 +56,15 @@ System.out.println(manifest);
### Method Two
-The second method will create a manifest with the same IDs that the cookbook recipe uses. It involves setting all the IDs manually, which involves a little more code.
+The second method doesn't use a `Minter` and will create a manifest with the same IDs that the cookbook recipe uses. It sets all the IDs manually, which involves a
+little more code. Like the above, it still uses `I18n`(s) for the internationalizations.
_The same note above about `LEVEL_ONE` and `IMAGE_JPEG` also applies to this example._
```java
var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0006-text-language/manifest.json",
new Label(new I18n("en", "Whistler's Mother"), new I18n("fr", "La Mère de Whistler")));
+
var canvas = new Canvas("https://iiif.io/api/cookbook/recipe/0006-text-language/canvas/p1");
var page = new AnnotationPage("https://iiif.io/api/cookbook/recipe/0006-text-language/page/p1/1");
var annotation = new PaintingAnnotation("https://iiif.io/api/cookbook/recipe/0006-text-language/annotation/p0001-image", canvas);
@@ -69,10 +74,10 @@ var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/exampl
var creatorLabel = new Label(new I18n("en", "Creator"), new I18n("fr", "Auteur"));
var creator = new Metadata(creatorLabel, new Value("Whistler, James Abbott McNeill"));
-var subjectLabel = new Label(new I18n("en", "Subject"), new I18n("fr", "Sujet"));
var subjectEN = new I18n("en", "McNeill Anna Matilda, mother of Whistler (1804-1881)");
var subjectFR = new I18n("fr", "McNeill Anna Matilda, mère de Whistler (1804-1881)");
-var subject = new Metadata(subjectLabel, new Value(subjectEN, subjectFR));
+var subject = new Metadata(new Label(new I18n("en", "Subject"), new I18n("fr", "Sujet")),
+ new Value(subjectEN, subjectFR));
var summaryEN = new I18n("en", "Arrangement in Grey and Black No. 1, also called Portrait of the Artist's Mother.");
var summaryFR = new I18n("fr", "Arrangement en gris et noir n°1, also called Portrait de la mère de l'artiste.");
@@ -91,4 +96,43 @@ manifest.addCanvases(canvas.setWidthHeight(1114, 991).setPaintingPages(page));
System.out.println(manifest);
```
+### Method Three
+
+The third, and last, example uses a `Minter` and creates internationalizations without using `I18n`(s). Instead, internationalizations are passed to their manifest
+properties as arrays. This approach is the most concise method of the three.
+
+_The same note above about `LEVEL_ONE` and `IMAGE_JPEG` also applies to this example._
+
+```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0006-text-language/manifest.json",
+ new Label("en", "Whistler's Mother", "fr", "La Mère de Whistler"));
+
+var canvas = new Canvas(MinterFactory.getMinter(manifest));
+var imageContent = new ImageContent("https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother/full/max/0/default.jpg");
+var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/329817fc8a251a01c393f517d8a17d87-Whistlers_Mother");
+
+var creator = new Metadata(new Label("en", "Creator", "fr", "Auteur"),
+ new Value("Whistler, James Abbott McNeill"));
+
+var subject = new Metadata(new Label("en", "Subject", "fr", "Sujet"),
+ new Value("en", "McNeill Anna Matilda, mother of Whistler (1804-1881)", "fr", "McNeill Anna Matilda, mère de Whistler (1804-1881)"));
+
+var summary = new Summary("en", "Arrangement in Grey and Black No. 1, also called Portrait of the Artist's Mother.",
+ "fr", "Arrangement en gris et noir n°1, also called Portrait de la mère de l'artiste.");
+
+var reqStatement = new RequiredStatement(new Label("en", "Held By", "fr", "Détenu par"),
+ new Value("Musée d'Orsay, Paris, France"));
+
+manifest.setMetadata(creator, subject);
+manifest.setSummary(summary);
+manifest.setRequiredStatement(reqStatement);
+
+imageContent.setWidthHeight(1114, 991).setFormat(IMAGE_JPEG).setServices(service);
+manifest.addCanvases(canvas.setWidthHeight(1114, 991).paintWith(imageContent));
+
+System.out.println(manifest);
+```
+
+All three of the above methods work, and any of them is a valid way to create the cookbook recipe's manifests.
+
{{% sandbox %}}
diff --git a/docs/content/en/docs/recipes/code/0007-string-formats.md b/docs/content/en/docs/recipes/code/0007-string-formats.md
index 77d24ad3..16dbad6b 100644
--- a/docs/content/en/docs/recipes/code/0007-string-formats.md
+++ b/docs/content/en/docs/recipes/code/0007-string-formats.md
@@ -4,6 +4,8 @@ linkTitle: "0007-string-formats"
weight: 0
---
+### Use Case
+
You want to have more control on how your metadata is displayed by adding links or simple formatting instructions to selected text blocks. For example, scientific names
might need special formatting and links out to other sites benefit from being activatable. Legacy systems may also include rudimentary formatting in their output.
@@ -15,18 +17,58 @@ might need special formatting and links out to other sites benefit from being ac
### Method One
For the first method, we use a Minter to create IDs for the components of the manifest. This allows us to use less code to create the same structures. It will, however,
-create different IDs than are used in the cookbook recipe.
+create different IDs than are used in the cookbook recipe. This example also shows passing HTML markup to manifest properties that are allowed to contain it.
+
+_Note that `LEVEL_ONE` and `IMAGE_JPEG` are static imports whose classes (`ImageService3.Profile` and `MediaType`) are predefined in this site’s code sandbox._
```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0007-string-formats/manifest.json",
+ new Label("en", "Picture of Göttingen taken during the 2019 IIIF Conference"));
+var canvas = new Canvas(MinterFactory.getMinter(manifest));
+var imageContent = new ImageContent("https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg");
+var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen");
+
+manifest.setSummary(new Summary("en", "
"));
+manifest.setMetadata(new Metadata(new Label("en", "Author"),
+ new Value("Glen Robson")));
+manifest.setRights("http://creativecommons.org/licenses/by-sa/3.0/");
+manifest.setRequiredStatement(new RequiredStatement(new Label("en", "Attribution"),
+ new Value("en", "Glen Robson, IIIF Technical Coordinator. CC BY-SA 3.0")));
+
+imageContent.setWidthHeight(4032, 3024).setFormat(IMAGE_JPEG).setServices(service);
+manifest.addCanvases(canvas.setWidthHeight(4032, 3024).paintWith(imageContent));
+System.out.println(manifest);
```
### Method Two
-The second method will create a manifest with the same IDs that the cookbook recipe uses. It involves setting all the IDs manually, which involves a little more code.
+The second method will create a manifest with the same IDs that the cookbook recipe uses. It involves setting all the IDs manually, which involves a little more code.
+This example also shows passing HTML markup to manifest properties that are allowed to contain it.
+
+_Note that `LEVEL_ONE` and `IMAGE_JPEG` are static imports whose classes (`ImageService3.Profile` and `MediaType`) are predefined in this site’s code sandbox._
```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0007-string-formats/manifest.json",
+ new Label("en", "Picture of Göttingen taken during the 2019 IIIF Conference"));
+var canvas = new Canvas("https://iiif.io/api/cookbook/recipe/0007-string-formats/canvas/p1");
+var page = new AnnotationPage("https://iiif.io/api/cookbook/recipe/0007-string-formats/page/p1/1");
+var annotation = new PaintingAnnotation("https://iiif.io/api/cookbook/recipe/0007-string-formats/annotation/p0001-image", canvas);
+var imageContent = new ImageContent("https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg");
+var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen");
+
+manifest.setSummary(new Summary("en", "
"));
+manifest.setMetadata(new Metadata(new Label("en", "Author"),
+ new Value("Glen Robson")));
+manifest.setRights("http://creativecommons.org/licenses/by-sa/3.0/");
+manifest.setRequiredStatement(new RequiredStatement(new Label("en", "Attribution"),
+ new Value("en", "Glen Robson, IIIF Technical Coordinator. CC BY-SA 3.0")));
+
+imageContent.setWidthHeight(4032, 3024).setFormat(IMAGE_JPEG).setServices(service);
+page.addAnnotations(annotation.setBody(imageContent).setTarget(new Target(canvas)));
+manifest.addCanvases(canvas.setWidthHeight(4032, 3024).setPaintingPages(page));
+System.out.println(manifest);
```
{{% sandbox %}}
diff --git a/docs/content/en/docs/recipes/code/0008-rights.md b/docs/content/en/docs/recipes/code/0008-rights.md
index 60a7b11a..a745424e 100644
--- a/docs/content/en/docs/recipes/code/0008-rights.md
+++ b/docs/content/en/docs/recipes/code/0008-rights.md
@@ -1,7 +1,72 @@
-+++
-title = "0008-rights"
-weight = 0
-+++
+---
+title: "Rights statement"
+linkTitle: "0008-rights"
+weight: 0
+---
-Content goes here!
+### Use Case
+You are able to implement the inclusion of a license, rights, or other important statement about a resource (e.g. a Manifest’s JSON or an image’s pixels) by using the
+`rights`, `requiredStatement`, and `metadata` properties.
+
+This recipe will focus on the first two; see the [Presentation 3.0 specification](https://iiif.io/api/presentation/3.0/#metadata) on `metadata` for more information
+about that property.
+
+| | |
+| :--- | :---: |
+| Recipe: | https://iiif.io/api/cookbook/recipe/0008-rights/ |
+| Manifest: | https://iiif.io/api/cookbook/recipe/0008-rights/manifest.json |
+
+### Method One
+
+For the first method, we use a Minter to create IDs for the components of the manifest. This allows us to use less code to create the same structures. It will, however,
+create different IDs than are used in the cookbook recipe.
+
+_Note that `LEVEL_ONE` and `IMAGE_JPEG` are static imports whose classes (`ImageService3.Profile` and `MediaType`) are predefined in this site’s code sandbox._
+
+```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0008-rights/manifest.json",
+ new Label("en", "Picture of Göttingen taken during the 2019 IIIF Conference"));
+var canvas = new Canvas(MinterFactory.getMinter(manifest));
+var imageContent = new ImageContent("https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg");
+var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen");
+
+manifest.setSummary(new Summary("en", "
"));
+manifest.setRights("http://creativecommons.org/licenses/by-sa/3.0/");
+manifest.setRequiredStatement(new RequiredStatement(new Label("en", "Attribution"),
+ new Value("en", "Glen Robson, IIIF Technical Coordinator. CC BY-SA 3.0")));
+
+imageContent.setWidthHeight(4032, 3024).setFormat(IMAGE_JPEG).setServices(service);
+manifest.addCanvases(canvas.setWidthHeight(4032, 3024).paintWith(imageContent));
+
+System.out.println(manifest);
+```
+
+### Method Two
+
+The second method will create a manifest with the same IDs that the cookbook recipe uses. It involves setting all the IDs manually, which involves a little more code.
+
+_Note that `LEVEL_ONE` and `IMAGE_JPEG` are static imports whose classes (`ImageService3.Profile` and `MediaType`) are predefined in this site’s code sandbox._
+
+```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0008-rights/manifest.json",
+ new Label("en", "Picture of Göttingen taken during the 2019 IIIF Conference"));
+var canvas = new Canvas("https://iiif.io/api/cookbook/recipe/0008-rights/canvas/p1");
+var page = new AnnotationPage("https://iiif.io/api/cookbook/recipe/0008-rights/page/p1/1");
+var annotation = new PaintingAnnotation("https://iiif.io/api/cookbook/recipe/0008-rights/annotation/p0001-image", canvas);
+var imageContent = new ImageContent("https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg");
+var service = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen");
+
+manifest.setSummary(new Summary("en", "
"));
+manifest.setRights("http://creativecommons.org/licenses/by-sa/3.0/");
+manifest.setRequiredStatement(new RequiredStatement(new Label("en", "Attribution"),
+ new Value("en", "Glen Robson, IIIF Technical Coordinator. CC BY-SA 3.0")));
+
+imageContent.setWidthHeight(4032, 3024).setFormat(IMAGE_JPEG).setServices(service);
+page.addAnnotations(annotation.setBody(imageContent).setTarget(new Target(canvas)));
+manifest.addCanvases(canvas.setWidthHeight(4032, 3024).setPaintingPages(page));
+
+System.out.println(manifest);
+```
+
+{{% sandbox %}}
diff --git a/docs/content/en/docs/recipes/code/0009-book-1.md b/docs/content/en/docs/recipes/code/0009-book-1.md
index b8d4ca1a..2ed70490 100644
--- a/docs/content/en/docs/recipes/code/0009-book-1.md
+++ b/docs/content/en/docs/recipes/code/0009-book-1.md
@@ -1,7 +1,153 @@
-+++
-title = "0009-book-1"
-weight = 0
-+++
+---
+title: "Simple Manifest - Book"
+linkTitle: "0009-book-1"
+weight: 0
+---
-Content goes here!
+### Use Case
+You have a digitized book consisting of a sequence of multiple images and want to model it as a IIIF Manifest. This recipe deals with a book (codex) object, but in
+practice it is applicable to any kind of compound object that may comprise a series of pages, surfaces or views (pages of a modern printed book like in the example
+below, the two sides of a postcard or the four cardinal views of a statue).
+
+| | |
+| :--- | :---: |
+| Recipe: | https://iiif.io/api/cookbook/recipe/0009-book-1/ |
+| Manifest: | https://iiif.io/api/cookbook/recipe/0009-book-1/manifest.json |
+
+### Method One
+
+For the first method, we use a Minter to create IDs for the components of the manifest. This allows us to use less code to create the same structures. It will, however,
+create different IDs than are used in the cookbook recipe. This first example is also sort of a "brute-force" illustration, because it lays it all out without any sort
+of looping.
+
+_Note that `PAGED`, `LEVEL_ONE`, and `IMAGE_JPEG` are static imports whose classes (`ManifestBehavior`, `ImageService3.Profile`, and `MediaType`) are predefined in this
+site’s code sandbox._
+
+```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0009-book-1/manifest.json",
+ new Label("en", "Simple Manifest - Book"));
+var minter = MinterFactory.getMinter(manifest);
+
+var canvas1 = new Canvas(minter, new Label("en", "Blank page"));
+var imageContent1 = new ImageContent("https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f18/full/max/0/default.jpg");
+var service1 = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f18");
+
+var canvas2 = new Canvas(minter, new Label("en", "Frontispiece"));
+var imageContent2 = new ImageContent("https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f19/full/max/0/default.jpg");
+var service2 = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f19");
+
+var canvas3 = new Canvas(minter, new Label("en", "Title page"));
+var imageContent3 = new ImageContent("https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f20/full/max/0/default.jpg");
+var service3 = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f20");
+
+var canvas4 = new Canvas(minter, new Label("en", "Blank page"));
+var imageContent4 = new ImageContent("https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f21/full/max/0/default.jpg");
+var service4 = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f21");
+
+var canvas5 = new Canvas(minter, new Label("en", "Bookplate"));
+var imageContent5 = new ImageContent("https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f22/full/max/0/default.jpg");
+var service5 = new ImageService3(LEVEL_ONE, "https://iiif.io/api/image/3.0/example/reference/59d09e6773341f28ea166e9f3c1e674f-gallica_ark_12148_bpt6k1526005v_f22");
+
+manifest.setBehaviors(PAGED);
+
+imageContent1.setWidthHeight(3204, 4613).setFormat(IMAGE_JPEG).setServices(service1);
+canvas1.setWidthHeight(3204, 4613).paintWith(imageContent1);
+
+imageContent2.setWidthHeight(3186, 4612).setFormat(IMAGE_JPEG).setServices(service2);
+canvas2.setWidthHeight(3186, 4612).paintWith(imageContent2);
+
+imageContent3.setWidthHeight(3204, 4613).setFormat(IMAGE_JPEG).setServices(service3);
+canvas3.setWidthHeight(3204, 4613).paintWith(imageContent3);
+
+imageContent4.setWidthHeight(3174, 4578).setFormat(IMAGE_JPEG).setServices(service4);
+canvas4.setWidthHeight(3174, 4578).paintWith(imageContent4);
+
+imageContent5.setWidthHeight(3198, 4632).setFormat(IMAGE_JPEG).setServices(service5);
+canvas5.setWidthHeight(3198, 4632).paintWith(imageContent5);
+
+manifest.addCanvases(canvas1, canvas2, canvas3, canvas4, canvas5);
+
+System.out.println(manifest);
+```
+
+### Method Two
+
+The second method will also create a manifest, but assumes the manifest data is coming from a spreadsheet data structure instead of being all hard-coded. It uses a loop
+to construct the manifest's resources. It still uses a minter to generate some of the manifest's IDs.
+
+_Note that `PAGED`, `LEVEL_ONE`, and `IMAGE_JPEG` are static imports whose classes (`ManifestBehavior`, `ImageService3.Profile`, and `MediaType`) are predefined in this
+site’s code sandbox._
+
+```java
+var manifest = new Manifest("https://iiif.io/api/cookbook/recipe/0009-book-1/manifest.json",
+ new Label("en", "Simple Manifest - Book"));
+var minter = MinterFactory.getMinter(manifest);
+var canvases = new ArrayList