diff --git a/.github/workflows/check-jsonschema.yml b/.github/workflows/check-jsonschema.yml new file mode 100644 index 0000000..c3419a3 --- /dev/null +++ b/.github/workflows/check-jsonschema.yml @@ -0,0 +1,37 @@ +name: Check JSON Schema $ref integrity + +on: + pull_request: + types: [opened, synchronize, reopened] + # Optional: only run when schemas or the script change + paths: + - "schema/**" + - ".github/workflows/check-jsonschema.yml" + +permissions: + contents: read + +jobs: + check-refs: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: 3.12 + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Sync dependencies + run: uv sync --all-extras --dev + + - name: Run $ref checker + run: | + uv run python -m presentation_validator.v4.check_refs \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 795a355..73dadb5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/README.md b/README.md index 0555ad2..897fa9d 100644 --- a/README.md +++ b/README.md @@ -86,3 +86,21 @@ This should start up a local server, running at . To test it, tr } ``` You may also use `--hostname` to specify a hostname or IP address to which to bind and `--port` for a port to which to bind. + + +## Github action + +It is also possible to run the validator against JSON documents which are in a Github repository using a github action. An example is below: + +``` + - name: Run IIIF validator + uses: IIIF/presentation-validator@main + with: + directory: path/to/json + version: 3 + extension: .json +``` + + * `directory` is required. The validator will start at this directory and then go through any sub-directories looking for json files to validate. + * `version` is optional. If supplied any json files will be validated against this version. If its not supplied the validator will look inside the json to see which version is specified in the @context + * `extension` is optional and defaults to .json. - name: Run IIIF validator \ No newline at end of file diff --git a/fixtures/4/bad/dup_id.json b/fixtures/4/bad/dup_id.json new file mode 100644 index 0000000..b3ba158 --- /dev/null +++ b/fixtures/4/bad/dup_id.json @@ -0,0 +1,42 @@ +{ + "@context": "http://iiif.io/api/presentation/4/context.json", + "id": "https://iiif.io/api/presentation/4.0/example/03_canvas.json", + "type": "Manifest", + "label": { + "en": [ + "Canvas and first annotation page have same id" + ] + }, + "items": [ + { + "id": "https://iiif.io/api/presentation/4.0/example/03_canvas/canvas/p1", + "type": "Canvas", + "height": 1800, + "width": 1200, + "items": [ + { + "id": "https://iiif.io/api/presentation/4.0/example/03_canvas/canvas/p1", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/presentation/4.0/example/03_canvas/annotation/p0001-image", + "type": "Annotation", + "motivation": [ "painting" ], + "body": { + "id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", + "type": "Image", + "format": "image/png", + "height": 1800, + "width": 1200 + }, + "target": { + "id": "https://iiif.io/api/presentation/4.0/example/03_canvas/canvas/p1", + "type": "Canvas" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/fixtures/4/ok/02_timeline.json b/fixtures/4/ok/02_timeline.json new file mode 100644 index 0000000..01d9db0 --- /dev/null +++ b/fixtures/4/ok/02_timeline.json @@ -0,0 +1,40 @@ +{ + "@context": "http://iiif.io/api/presentation/4/context.json", + "id": "https://iiif.io/api/presentation/4.0/example/02_timeline.json", + "type": "Manifest", + "label": { + "en": [ + "Simplest Audio Example (IIIF Presentation v4)" + ] + }, + "items": [ + { + "id": "https://iiif.io/api/presentation/4.0/example/02", + "type": "Timeline", + "duration": 1985.024, + "items": [ + { + "id": "https://iiif.io/api/presentation/4.0/example/02/page", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/presentation/4.0/example/02/page/anno", + "type": "Annotation", + "motivation": ["painting"], + "body": { + "id": "https://fixtures.iiif.io/audio/indiana/mahler-symphony-3/CD1/medium/128Kbps.mp4", + "type": "Audio", + "format": "audio/mp4", + "duration": 1985.024 + }, + "target": { + "id": "https://iiif.io/api/presentation/4.0/example/02", + "type": "Timeline" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/presentation_validator/v3/schemavalidator.py b/presentation_validator/v3/schemavalidator.py index 35a344d..8fe0a21 100755 --- a/presentation_validator/v3/schemavalidator.py +++ b/presentation_validator/v3/schemavalidator.py @@ -24,6 +24,39 @@ def printPath(pathObj, fields): path += '/[{}]'.format(fields) return path +def create_snippet(data): + # Take possibly a large JSON document and only show the fields at the current level + for key in data: + if isinstance(data[key], list): + data[key] = '[ ... ]' + elif isinstance(data[key], dict): + data[key] = '{ ... }' + + return data + +def convertValidationError(err, errorCount, total): + detail = '' + if 'title' in err.schema: + detail = err.schema['title'] + description = '' + if 'description' in err.schema: + detail += ' ' + err.schema['description'] + context = err.instance + if isinstance(context, dict): + for key in context: + if isinstance(context[key], list): + context[key] = '[ ... ]' + elif isinstance(context[key], dict): + context[key] = '{ ... }' + + return ErrorDetail( + f"Error {errorCount} of {total}.\n Message: {err.message}", + detail, + description, + printPath(err.path, err.message), + context, + err) + def validate(data, version, url): if version == IIIFVersion.V3_0: with open(f'{SCHEMA_DIR}/iiif_3_0.json') as json_file: @@ -90,31 +123,12 @@ def validate(data, version, url): if errorPath not in seen_titles: errors.append(errorDup) seen_titles.add(errorPath) + errorCount = 1 # Now create some useful messsages to pass on for err in errors: - detail = '' - if 'title' in err.schema: - detail = err.schema['title'] - description = '' - if 'description' in err.schema: - detail += ' ' + err.schema['description'] - context = err.instance - if isinstance(context, dict): - for key in context: - if isinstance(context[key], list): - context[key] = '[ ... ]' - elif isinstance(context[key], dict): - context[key] = '{ ... }' - - result.errorList.append(ErrorDetail( - 'Error {} of {}.\n Message: {}'.format(errorCount, len(errors), err.message), - detail, - description, - printPath(err.path, err.message), - context, - err)) - #print (json.dumps(err.instance, indent=4)) + result.errorList.append(convertValidationError(err, errorCount, len(errors))) + errorCount += 1 # Return: diff --git a/presentation_validator/v4/check_refs.py b/presentation_validator/v4/check_refs.py new file mode 100644 index 0000000..9744cb6 --- /dev/null +++ b/presentation_validator/v4/check_refs.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, Dict, Iterable, List, Tuple +from urllib.parse import urljoin, urlparse +from pathlib import PurePosixPath + +from referencing import Registry, Resource +from referencing.exceptions import Unresolvable + +def iter_refs(node: Any, path: str = "#") -> Iterable[Tuple[str, str]]: + """ + Yield ($ref_value, json_pointer_path_in_schema) for every $ref in a schema tree. + """ + #print (f"Travesring {node}") + if isinstance(node, dict): + if "$ref" in node and isinstance(node["$ref"], str): + yield node["$ref"], path + "/$ref" + for k, v in node.items(): + yield from iter_refs(v, f"{path}/{k}") + elif isinstance(node, list): + for i, v in enumerate(node): + yield from iter_refs(v, f"{path}/{i}") + + +def build_registry_from_dir(schema_dir: str | Path) -> Tuple[Registry, Dict[str, Dict[str, Any]]]: + """ + Load all *.json schemas under schema_dir into a referencing.Registry. + + Each resource is keyed by: + - its $id, if present, else + - a file:// URI for its absolute path. + """ + schema_dir = Path(schema_dir) + registry = Registry() + by_uri: Dict[str, Dict[str, Any]] = {} + + found=False + for path in schema_dir.rglob("*.json"): + found = True # Found at least one JSON file + with path.open("r", encoding="utf-8") as f: + schema = json.load(f) + + print (f"Loading {path.name}") + uri = schema.get("$id") + if not uri: + uri = path.resolve().as_uri() # file:///.../schema.json + + resource = Resource.from_contents(schema) + registry = registry.with_resource(uri, resource) + if uri in by_uri: + raise Exception(f"Duplicate schema ID {uri} found in {path.name}") + + by_uri[uri] = schema + + if not found: + raise FileNotFoundError(f"No JSON files found in {schema_dir}") + + return registry, by_uri + + +def find_missing_refs_in_dir(schema_dir: str | Path) -> List[Dict[str, str]]: + """ + Returns a list of unresolved $refs across all schemas in schema_dir. + """ + registry, schemas = build_registry_from_dir(schema_dir) + missing: List[Dict[str, str]] = [] + for base_uri, schema in schemas.items(): + resolver = registry.resolver(base_uri=base_uri) + + for ref, where in iter_refs(schema): + # Make relative refs absolute against the schema's base URI + target = urljoin(base_uri, ref) + + try: + resolver.lookup(target) + except Unresolvable: + missing.append( + { + "schema": base_uri, + "ref": ref, + "where": where, + "resolved_target": target, + } + ) + + return missing + + +if __name__ == "__main__": + problems = find_missing_refs_in_dir(Path(__file__).resolve().parent.parent.parent / "schema" / "v4") + if problems: + print("\nMissing/unresolvable $refs:") + for p in problems: + print(f"- In {p['schema'].split('/')[-1]}:\n at {p['where']}: {p['ref']} (→ {p['resolved_target']})\n") + raise SystemExit(2) + else: + print("All $refs resolved.") \ No newline at end of file diff --git a/presentation_validator/v4/unique_ids.py b/presentation_validator/v4/unique_ids.py new file mode 100644 index 0000000..08b527e --- /dev/null +++ b/presentation_validator/v4/unique_ids.py @@ -0,0 +1,61 @@ +import sys +import json +from presentation_validator.model import ErrorDetail +from presentation_validator.v3.schemavalidator import create_snippet + +ignore = ["target", "lookAt", "range","structures","first","last","start","source"] +# create a method where you pass in a manifest and it checks to see if the id is unique +# if it is not unique, then it should raise a validation error +def check(manifest): + + duplicates = [] + ids = [] + checkNode(manifest, ids, duplicates) + + if len(duplicates) > 0: + return duplicates + else: + return None + +def checkNode(node, ids=[], duplicates=[], path = ""): + if type(node) != dict: + return + + for key, value in node.items(): + if key == 'id': + if type(value) != str: + raise ValueError(f"Id must be a string: {value}") + if value in ids: + duplicates.append(ErrorDetail( + f"Duplicate id found", + "The id field must be unique", + f"Duplicate id: {value}", + path + "/" + key, + create_snippet(node), + None + )) + ids.append(value) + else: + # Don't look further in fields that point to other resources + if key in ignore: + continue + + if type(value) == list: + count = 0 + for item in value: + checkNode(item, ids, duplicates, path + "/" + key + "[" + str(count) + "]") + count += 1 + + elif type(value) != str: + checkNode(value, ids, duplicates, path + "/" + key) + +def main(): + # pass in manifest by command line argument + # load json from file + with open(sys.argv[1], 'r') as f: + manifest = json.load(f) + + check(manifest) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/presentation_validator/v4/validation4.py b/presentation_validator/v4/validation4.py new file mode 100644 index 0000000..9426895 --- /dev/null +++ b/presentation_validator/v4/validation4.py @@ -0,0 +1,94 @@ +from ast import main +from pathlib import Path +import json +import sys +from presentation_validator.model import ValidationResult +from presentation_validator.v3.schemavalidator import convertValidationError +from presentation_validator.v4.unique_ids import check + +from jsonschema import Draft202012Validator +from jsonschema.exceptions import relevance +from referencing import Registry, Resource +from referencing.jsonschema import DRAFT202012 + +SCHEMA_DIR = Path(__file__).resolve().parent.parent.parent / "schema" / "v4" +BASE_URI = "https://iiif.io/api/presentation/4.0/schema" + +def load_schema(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + +def validate(instance): + # Read all JSON documents in the SCHEMA_DIR and store them in schemas + schemas = {} + + # Iterate through all JSON files in the schema directory + for json_file in SCHEMA_DIR.glob("*.json"): + try: + schema_content = load_schema(json_file) + # Use the filename as a URI-like key for consistency + uri = f"{BASE_URI}/{json_file.name}" + schemas[uri] = schema_content + #print(f"Loaded schema: {json_file.name}") + except (json.JSONDecodeError, IOError) as e: + print(f"Failed to load schema from {json_file}: {e}") + + registry = Registry().with_resources( + (uri, Resource.from_contents(schema, default_specification=DRAFT202012)) + for uri, schema in schemas.items() + ) + + main = schemas[f"{BASE_URI}/main.json"] + validator = Draft202012Validator(main, registry=registry) + + result = ValidationResult() + + results = validator.iter_errors(instance) + errors = sorted(results, key=relevance) + + if errors: + result.passed = False + errorCount = 1 + # Now create some useful messsages to pass on + for err in errors: + result.errorList.append(convertValidationError(err, errorCount, len(errors))) + + errorCount += 1 + else: + result.passed = True + + duplicate_ids = check(instance) + if duplicate_ids: + result.passed = False + + # Add all of the examples of duplicated ids + result.errorList.extend(duplicate_ids) + + return result + +def main(): + # Check if command line argument is provided + if len(sys.argv) < 2: + print("Usage: python validation4.py ") + sys.exit(1) + + # Get filename from first command line argument + filename = sys.argv[1] + + try: + # Read the JSON file and store in instance + with open(filename, 'r', encoding='utf-8') as f: + instance = json.load(f) + print(f"Checking: {filename}") + + # Validate the instance + validate(instance) + + except FileNotFoundError: + print(f"Error: File '{filename}' not found") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in file '{filename}': {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/presentation_validator/validator.py b/presentation_validator/validator.py index a7b0043..62d456c 100644 --- a/presentation_validator/validator.py +++ b/presentation_validator/validator.py @@ -2,6 +2,7 @@ from jsonschema.exceptions import ValidationError from presentation_validator.model import ValidationResult, ErrorDetail from presentation_validator.v3 import schemavalidator +from presentation_validator.v4 import validation4 from presentation_validator.enum import IIIFVersion import requests @@ -67,6 +68,9 @@ def check_manifest( traceback.print_exc() result.passed = False result.error = f'Presentation Validator bug: "{e}". Please create a Validator Issue, including a link to the manifest.' + + elif version == IIIFVersion.V4_0: + result = validation4.validate(manifest) else: if isinstance(data, dict): data = json.dumps(data, indent=3) diff --git a/schema/v4/AccompanyingCanvas.json b/schema/v4/AccompanyingCanvas.json new file mode 100644 index 0000000..0429bc8 --- /dev/null +++ b/schema/v4/AccompanyingCanvas.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AccompanyingCanvas.json", + "title":"AccompanyingCanvas", + "type": "object", + "allOf": [ + { + "$ref": "Canvas.json" + }, + { + "type": "object", + "not": { + "required": [ + "placeholderCanvas", + "accompanyingCanvas" + ] + } + } + ] +} \ No newline at end of file diff --git a/schema/v4/AmbientAudio.json b/schema/v4/AmbientAudio.json new file mode 100644 index 0000000..d44f352 --- /dev/null +++ b/schema/v4/AmbientAudio.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AmbientAudio.json", + "title": "AmbientAudio", + "type": "object", + "allOf": [ + { + "$ref": "Audio.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^AmbientAudio$", + "default": "AmbientAudio" + } + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/AmbientLight.json b/schema/v4/AmbientLight.json new file mode 100644 index 0000000..2f87897 --- /dev/null +++ b/schema/v4/AmbientLight.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AmbientLight.json", + "type": "object", + "allOf": [ + { + "$ref": "Light.json" + }, + { + "type": "object", + "properties": { + "type": { + "const": "AmbientLight" + }, + "color": { + "$ref": "properties.json#/$defs/backgroundColor" + } + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/Annotation.json b/schema/v4/Annotation.json new file mode 100644 index 0000000..2b08e27 --- /dev/null +++ b/schema/v4/Annotation.json @@ -0,0 +1,139 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Annotation.json", + "type": "object", + "title": "Annotation", + "properties": { + "@context": { + "$ref": "Context.json" + }, + "id": { "$ref": "properties.json#/$defs/id" }, + "created": { + "type": "string" + }, + "modified": { + "type": "string" + }, + "generated": { + "type": "string" + }, + "creator": { + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "Agent.json" + }, + { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "Agent.json" + } + ] + } + } + ] + }, + "generator": { + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "Agent.json" + }, + { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "Agent.json" + } + ] + } + } + ] + }, + "audience": { + "oneOf": [ + { + "$ref": "Audiance.json" + }, + { + "type": "array", + "items": { + "$ref": "Audiance.json" + } + } + ] + }, + "bodyValue": { + "type": "string" + }, + "canonical": { + "type": "string", + "format": "uri" + }, + "via": { + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + } + ] + }, + "stylesheet": { + "$ref": "Stylesheet.json" + }, + "type": { + "type": "string", + "pattern": "^Annotation$", + "default": "Annotation" + }, + "service": { + "$ref": "Service.json" + }, + "rendering": { + "$ref": "External.json" + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + }, + "motivation": { + "type": "array", + "items": { + "type": "string" + } + }, + "body": { + "$ref": "Body.json" + }, + "target": { + "$ref": "Target.json" + } + }, + "required": [ + "id", + "target", + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/AnnotationCollection.json b/schema/v4/AnnotationCollection.json new file mode 100644 index 0000000..5ffda30 --- /dev/null +++ b/schema/v4/AnnotationCollection.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnotationCollection.json", + "title": "AnnotationCollection", + "type": "object", + "properties": { + "@context": { + "$ref": "Context.json" + }, + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^AnnotationCollection$", + "default": "AnnotationCollection" + }, + "rendering": { + "$ref": "External.json" + }, + "partOf": { + "$ref": "PartOf.json" + }, + "next": { + "$ref": "AnnotationPageRef.json" + }, + "first": { + "$ref": "AnnotationPageRef.json" + }, + "last": { + "$ref": "AnnotationPageRef.json" + }, + "service": { + "$ref": "Service.json" + }, + "total": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + } + }, + "required": [ + "id", + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/AnnotationPage.json b/schema/v4/AnnotationPage.json new file mode 100644 index 0000000..c51eb5e --- /dev/null +++ b/schema/v4/AnnotationPage.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnotationPage.json", + "type": "object", + "title": "AnnotationPage", + "description": "id, type and items required", + "properties": { + "@context": { + "$ref": "Context.json" + }, + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^AnnotationPage$", + "default": "AnnotationPage" + }, + "rendering": { + "$ref": "External.json" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "service": { + "$ref": "Service.json" + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + }, + "items": { + "type": "array", + "items": { + "$ref": "Annotation.json" + } + }, + "partOf": { + "$ref": "PartOf.json" + }, + "next": { + "$ref": "AnnotationPageRef.json" + }, + "prev": { + "$ref": "AnnotationPageRef.json" + }, + "first": { + "$ref": "AnnotationPageRef.json" + }, + "last": { + "$ref": "AnnotationPageRef.json" + } + }, + "required": [ + "id", + "type", + "items" + ] +} \ No newline at end of file diff --git a/schema/v4/AnnotationPageRef.json b/schema/v4/AnnotationPageRef.json new file mode 100644 index 0000000..4ec68bc --- /dev/null +++ b/schema/v4/AnnotationPageRef.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnotationPageRef.json", + "title": "AnnotationPageRef", + "if": { + "type": "string" + }, + "then": { + "type": "string" + }, + "else": { + "title": "AnnotationPageRefExtended", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^AnnotationPage$" + } + } + } + ] + } +} \ No newline at end of file diff --git a/schema/v4/AnnotationRef.json b/schema/v4/AnnotationRef.json new file mode 100644 index 0000000..ad582e2 --- /dev/null +++ b/schema/v4/AnnotationRef.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnotationRef.json", + "title": "AnnotationRef", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^Annotation$" + } + } + } + ] +} \ No newline at end of file diff --git a/schema/v4/Audio.json b/schema/v4/Audio.json new file mode 100644 index 0000000..0e014ea --- /dev/null +++ b/schema/v4/Audio.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Audio.json", + "title": "Audio", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "source": { + "$ref": "Body.json" + }, + "volume": { + "$ref": "Quantity.json" + } + }, + "required": [ + "type", + "source" + ] +} \ No newline at end of file diff --git a/schema/v4/AudioTypes.json b/schema/v4/AudioTypes.json new file mode 100644 index 0000000..e733889 --- /dev/null +++ b/schema/v4/AudioTypes.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AudioTypes.json", + "type": "object", + "if": { + "properties": { + "type": { + "const": "AmbientAudio" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "AmbientAudio.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "PointAudio" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "PointAudio.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "SpotAudio" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "SpotAudio.json" + } + } + } +} \ No newline at end of file diff --git a/schema/v4/Body.json b/schema/v4/Body.json new file mode 100644 index 0000000..ed8bfbb --- /dev/null +++ b/schema/v4/Body.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Body.json", + "title": "Body", + "type": "object", + "if": { + "properties": { + "type": { + "const": "TextualBody" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "TextualBody.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "SpecificResource" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "SpecificResource.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Choice" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "Choice.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Scene" + } + }, + "required": [ + "type" + ] + }, + "then": { + "if": { + "properties": { + "items": { + "type": "array" + } + }, + "required": [ + "items" + ] + }, + "then": { + "$ref": "Scene.json" + }, + "else": { + "$ref": "SceneRef.json" + } + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Canvas" + } + }, + "required": [ + "type" + ] + }, + "then": { + "if": { + "properties": { + "items": { + "type": "array" + } + }, + "required": [ + "items" + ] + }, + "then": { + "$ref": "Canvas.json" + }, + "else": { + "$ref": "CanvasRef.json" + } + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Timeline" + } + }, + "required": [ + "type" + ] + }, + "then": { + "if": { + "properties": { + "items": { + "type": "array" + } + }, + "required": [ + "items" + ] + }, + "then": { + "$ref": "Timeline.json" + }, + "else": { + "$ref": "TimelineRef.json" + } + }, + "else": { + "if": { + "properties": { + "type": { + "type": "string", + "pattern": "Camera" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "CameraTypes.json" + }, + "else": { + "if": { + "properties": { + "type": { + "type": "string", + "pattern": "Light" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "LightTypes.json" + }, + "else": { + "if": { + "properties": { + "type": { + "type": "string", + "pattern": "Audio" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "AudioTypes.json" + }, + "else": { + "$ref": "Resource.json" + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schema/v4/Camera.json b/schema/v4/Camera.json new file mode 100644 index 0000000..b2a1d33 --- /dev/null +++ b/schema/v4/Camera.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Camera.json", + "title": "Camera", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "lookAt": { + "$ref": "LookAt.json" + }, + "near": { + "type": "number" + }, + "far": { + "type": "number" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/CameraTypes.json b/schema/v4/CameraTypes.json new file mode 100644 index 0000000..51faa0b --- /dev/null +++ b/schema/v4/CameraTypes.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/CameraTypes.json", + "type": "object", + "if": { + "properties": { + "type": { + "const": "PerspectiveCamera" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "PerspectiveCamera.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "OrthographicCamera" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "OrthographicCamera.json" + } + } +} \ No newline at end of file diff --git a/schema/v4/Canvas.json b/schema/v4/Canvas.json new file mode 100644 index 0000000..165353e --- /dev/null +++ b/schema/v4/Canvas.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Canvas.json", + "title": "Canvas", + "type": "object", + "allOf": [ + { + "$ref": "Container.json" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Canvas$", + "default": "Canvas" + }, + "height": { + "$ref": "properties.json#/$defs/dimension" + }, + "width": { + "$ref": "properties.json#/$defs/dimension" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + }, + "spatialScale": { + "$ref": "Quantity.json" + }, + "backgroundColor": { + "$ref": "properties.json#/$defs/backgroundColor" + } + }, + "required": [ + "id", + "type", + "height", + "width" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/CanvasRef.json b/schema/v4/CanvasRef.json new file mode 100644 index 0000000..89dac26 --- /dev/null +++ b/schema/v4/CanvasRef.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/CanvasRef.json", + "title": "CanvasRef", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Canvas$" + } + }, + "required": [ + "id", + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/Choice.json b/schema/v4/Choice.json new file mode 100644 index 0000000..1aecc0a --- /dev/null +++ b/schema/v4/Choice.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Choice.json", + "title": "Choice", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "Choice", + "default": "Choice" + }, + "items": { + "type": "array", + "items": { + "$ref": "Body.json" + } + } + }, + "required": [ + "type", + "items" + ] +} \ No newline at end of file diff --git a/schema/v4/Collection.json b/schema/v4/Collection.json new file mode 100644 index 0000000..b88ff7e --- /dev/null +++ b/schema/v4/Collection.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Collection.json", + "title": "Collection", + "type": "object", + "properties": { + "@context": { + "$ref": "Context.json" + }, + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Collection", + "default": "Collection", + "title": "Collection", + "description": "If you are validating a manifest, you may get this error if there are errors in the manifest. The validator first validates it as a manifest and if that fails it will try and validate it using the other types." + }, + "metadata": { + "$ref": "Metadata.json" + }, + "summary": { + "$ref": "properties.json#/$defs/lngString" + }, + "requiredStatement": { + "$ref": "properties.json#/$defs/keyValueString" + }, + "rendering": { + "$ref": "External.json" + }, + "rights": { + "$ref": "properties.json#/$defs/rights" + }, + "navDate": { + "$ref": "properties.json#/$defs/navDate" + }, + "navPlace": { + "$ref": "NavPlace.json" + }, + "provider": { + "$ref": "Provider.json" + }, + "seeAlso": { + "$ref": "SeeAlso.json" + }, + "services": { + "$ref": "Service.json" + }, + "service": { + "$ref": "Service.json" + }, + "placeholderCanvas": { + "$ref": "PlaceholderCanvas.json" + }, + "accompanyingCanvas": { + "$ref": "AccompanyingCanvas.json" + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + }, + "homepage": { + "$ref": "Homepage.json" + }, + "behavior": { + "$ref": "properties.json#/$defs/behavior" + }, + "partOf": { + "$ref": "PartOf.json" + }, + "items": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "ManifestRef.json" + }, + { + "$ref": "CollectionRef.json" + }, + { + "$ref": "Collection.json" + } + ] + } + }, + "annotations": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "AnnotationPage.json" + }, + { + "$ref": "AnnotationPageRef.json" + } + ] + } + } + }, + "required": [ + "@context", + "id", + "type", + "label", + "items" + ] +} \ No newline at end of file diff --git a/schema/v4/CollectionRef.json b/schema/v4/CollectionRef.json new file mode 100644 index 0000000..4271cd7 --- /dev/null +++ b/schema/v4/CollectionRef.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/CollectionRef.json", + "title": "CollectionRef", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^Collection$" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + } + }, + "required": [ + "label" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/Container.json b/schema/v4/Container.json new file mode 100644 index 0000000..a661ba6 --- /dev/null +++ b/schema/v4/Container.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Container.json", + "title": "Container", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "enum": [ + "Canvas", + "Timeline", + "Scene" + ] + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "metadata": { + "$ref": "Metadata.json" + }, + "summary": { + "$ref": "properties.json#/$defs/lngString" + }, + "requiredStatement": { + "$ref": "properties.json#/$defs/keyValueString" + }, + "rendering": { + "$ref": "External.json" + }, + "rights": { + "$ref": "properties.json#/$defs/rights" + }, + "navDate": { + "$ref": "properties.json#/$defs/navDate" + }, + "navPlace": { + "$ref": "NavPlace.json" + }, + "provider": { + "$ref": "Provider.json" + }, + "seeAlso": { + "$ref": "SeeAlso.json" + }, + "service": { + "$ref": "Service.json" + }, + "placeholderCanvas": { + "$ref": "PlaceholderCanvas.json" + }, + "accompanyingCanvas": { + "$ref": "AccompanyingCanvas.json" + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + }, + "homepage": { + "$ref": "Homepage.json" + }, + "behavior": { + "$ref": "properties.json#/$defs/behavior" + }, + "partOf": { + "$ref": "PartOf.json" + }, + "items": { + "type": "array", + "items": { + "$ref": "AnnotationPage.json" + } + }, + "annotations": { + "type": "array", + "items": { + "if": { + "type": "object", + "required": [ + "items" + ] + }, + "then": { + "$ref": "AnnotationPage.json" + }, + "else": { + "$ref": "AnnotationPageRef.json" + } + } + } + }, + "required": [ + "id", + "type", + "items" + ] +} \ No newline at end of file diff --git a/schema/v4/Context.json b/schema/v4/Context.json new file mode 100644 index 0000000..f60a130 --- /dev/null +++ b/schema/v4/Context.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Context.json", + "title": "Context", + "if": { + "type": "array" + }, + "then": { + "$comment": "Array form of @context, with at least one entry that is the IIIF context. The IIIF context MUST be the last item but we can't test that with JSON Schema", + "type": "array", + "items": { + "type": "string", + "format": "uri", + "pattern": "^http.*$" + }, + "minItems": 1, + "contains": { + "$ref": "properties.json#/$defs/context" + }, + "minContains": 1, + "maxContains": 1 + }, + "else": { + "$ref": "properties.json#/$defs/context" + } +} \ No newline at end of file diff --git a/schema/v4/CssSelector.json b/schema/v4/CssSelector.json new file mode 100644 index 0000000..4c52f48 --- /dev/null +++ b/schema/v4/CssSelector.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/CssSelector.json", + "title": "CssSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^CssSelector$", + "default": "CssSelector" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ] +} \ No newline at end of file diff --git a/schema/v4/DataPositionSelector.json b/schema/v4/DataPositionSelector.json new file mode 100644 index 0000000..4af6160 --- /dev/null +++ b/schema/v4/DataPositionSelector.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/DataPositionSelector.json", + "title": "DataPositionSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^DataPositionSelector$", + "default": "DataPositionSelector" + }, + "start": { + "type": "integer" + }, + "end": { + "type": "integer" + } + }, + "required": [ + "type", + "start", + "end" + ] +} \ No newline at end of file diff --git a/schema/v4/DirectionalLight.json b/schema/v4/DirectionalLight.json new file mode 100644 index 0000000..c42a2be --- /dev/null +++ b/schema/v4/DirectionalLight.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/DirectionalLight.json", + "type": "object", + "allOf": [ + { + "$ref": "Light.json" + }, + { + "type": "object", + "properties": { + "type": { + "const": "DirectionalLight" + }, + "color": { + "$ref": "properties.json#/$defs/backgroundColor" + }, + "lookAt": { + "$ref": "LookAt.json" + } + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/External.json b/schema/v4/External.json new file mode 100644 index 0000000..3dfac34 --- /dev/null +++ b/schema/v4/External.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/External.json", + "title": "LinkedResources", + "type": "array", + "items": { + "title": "linkedResource", + "type": "object", + "properties": { + "id": { "$ref": "properties.json#/$defs/id" }, + "type": { + "type": "string" + }, + "format": { + "$ref": "properties.json#/$defs/format" + }, + "language": { + "type": "array", + "items": { + "$ref": "properties.json#/$defs/BCP47" + } + }, + "profile": { + "type": "string" + } + }, + "required": ["id", "type"] + } +} \ No newline at end of file diff --git a/schema/v4/FragmentSelector.json b/schema/v4/FragmentSelector.json new file mode 100644 index 0000000..33428ff --- /dev/null +++ b/schema/v4/FragmentSelector.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/FragmentSelector.json", + "title": "FragmentSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^FragmentSelector$", + "default": "FragmentSelector" + }, + "conformsTo": { + "type": "string", + "format": "uri", + "pattern": "^http.*$", + "default": "http://www.w3.org/TR/media-frags/" + }, + "value": { + "type:": "string" + } + }, + "required": [ + "type", + "value" + ] +} \ No newline at end of file diff --git a/schema/v4/Homepage.json b/schema/v4/Homepage.json new file mode 100644 index 0000000..e1efd5b --- /dev/null +++ b/schema/v4/Homepage.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Homepage.json", + "title": "homepages", + "type": "array", + "items": { + "title": "homepage", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Text$", + "default": "Text" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "format": { + "$ref": "properties.json#/$defs/format" + }, + "language": { + "type": "array", + "items": { + "$ref": "properties.json#/$defs/BCP47" + } + } + }, + "required": [ + "id", + "type", + "label" + ] + } +} \ No newline at end of file diff --git a/schema/v4/HttpRequestState.json b/schema/v4/HttpRequestState.json new file mode 100644 index 0000000..d916d23 --- /dev/null +++ b/schema/v4/HttpRequestState.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/HttpRequestState.json", + "title": "HttpRequestState", + "description": "W3C Web Annotation HttpRequestState for HTTP header requirements", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^HttpRequestState$", + "default": "HttpRequestState" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ] +} \ No newline at end of file diff --git a/schema/v4/ImageAPISelector.json b/schema/v4/ImageAPISelector.json new file mode 100644 index 0000000..c571536 --- /dev/null +++ b/schema/v4/ImageAPISelector.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/ImageAPISelector.json", + "title": "ImageAPISelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^ImageApiSelector$", + "default": "ImageApiSelector" + }, + "region": { + "type:": "string" + }, + "size": { + "type:": "string" + }, + "rotation": { + "type:": "string" + }, + "quality": { + "type:": "string" + }, + "format": { + "type:": "string" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/ImageBasedLight.json b/schema/v4/ImageBasedLight.json new file mode 100644 index 0000000..59412c0 --- /dev/null +++ b/schema/v4/ImageBasedLight.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/ImageBasedLight.json", + "title": "ImageBasedLight", + "type": "object", + "allOf": [ + { + "$ref": "Light.json" + }, + { + "type": "object", + "properties": { + "type": { + "const": "ImageBasedLight" + }, + "environmentMap": { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "const": "Image" + }, + "format": { + "$ref": "properties.json#/$defs/format" + }, + "profile": { + "type": "string", + "enum": [ + "equirectangular", + "cubic" + ] + } + }, + "required": [ + "id", + "type", + "profile" + ] + } + }, + "required": [ + "type", + "environmentMap" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/Intensity.json b/schema/v4/Intensity.json new file mode 100644 index 0000000..54cb7a9 --- /dev/null +++ b/schema/v4/Intensity.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Intensity.json", + "type": "object", + "properties": { + "id": { "$ref": "properties.json#/$defs/id" }, + "type": { + "const": "Quantity" + }, + "quantityValue": { + "type": "number", + "minimum": 0.0, + "maximum": 1.0 + }, + "unit": { + "const": "relative" + } + }, + "required": [ + "type", + "quantityValue", + "unit" + ] +} \ No newline at end of file diff --git a/schema/v4/Light.json b/schema/v4/Light.json new file mode 100644 index 0000000..e7b897d --- /dev/null +++ b/schema/v4/Light.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Light.json", + "title": "Light", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "intensity": { "$ref": "Intensity.json" } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/LightTypes.json b/schema/v4/LightTypes.json new file mode 100644 index 0000000..437d0e6 --- /dev/null +++ b/schema/v4/LightTypes.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/LightTypes.json", + "type": "object", + "if": { + "properties": { + "type": { + "const": "ImageBasedLight" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "ImageBasedLight.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "SpotLight" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "SpotLight.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "AmbientLight" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "AmbientLight.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "DirectionalLight" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "DirectionalLight.json" + } + } + } + } +} \ No newline at end of file diff --git a/schema/v4/LookAt.json b/schema/v4/LookAt.json new file mode 100644 index 0000000..271b5da --- /dev/null +++ b/schema/v4/LookAt.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/LookAt.json", + "type": "object", + "if": { + "properties": { + "type": { + "const": "PointSelector" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "PointSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "WktSelector" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "WktSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "SpecificResource" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "SpecificResource.json" + }, + "else": { + "$ref": "AnnotationRef.json" + } + } + } +} \ No newline at end of file diff --git a/schema/v4/Manifest.json b/schema/v4/Manifest.json new file mode 100644 index 0000000..3631be3 --- /dev/null +++ b/schema/v4/Manifest.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Manifest.json", + "title": "Manifest", + "type": "object", + "properties": { + "@context": { + "$ref": "Context.json" + }, + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Manifest$", + "default": "Manifest" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "metadata": { + "$ref": "Metadata.json" + }, + "summary": { + "$ref": "properties.json#/$defs/lngString" + }, + "requiredStatement": { + "$ref": "properties.json#/$defs/keyValueString" + }, + "rendering": { + "$ref": "External.json" + }, + "service": { + "$ref": "Service.json" + }, + "services": { + "$ref": "Service.json" + }, + "viewingDirection": { + "$ref": "properties.json#/$defs/viewingDirection" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "type" + ], + "if": { + "properties": { + "type": { + "const": "Canvas" + } + } + }, + "then": { + "$ref": "Canvas.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Timeline" + } + } + }, + "then": { + "$ref": "Timeline.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Scene" + } + } + }, + "then": { + "$ref": "Scene.json" + } + } + } + } + } + }, + "required": [ + "@context", + "id", + "type", + "label", + "items" + ] +} \ No newline at end of file diff --git a/schema/v4/ManifestRef.json b/schema/v4/ManifestRef.json new file mode 100644 index 0000000..23260b6 --- /dev/null +++ b/schema/v4/ManifestRef.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/ManifestRef.json", + "title": "ManifestRef", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^Manifest$", + "default": "Manifest" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + } + }, + "required": [ + "label" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/NavPlace.json b/schema/v4/NavPlace.json new file mode 100644 index 0000000..a1b9ece --- /dev/null +++ b/schema/v4/NavPlace.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/NavPlace.json", + "title": "NavPlace", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "default": "FeatureCollection" + }, + "features": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "id", + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/OrthographicCamera.json b/schema/v4/OrthographicCamera.json new file mode 100644 index 0000000..22ae276 --- /dev/null +++ b/schema/v4/OrthographicCamera.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/OrthographicCamera.json", + "title": "OrthographicCamera", + "type": "object", + "allOf": [ + { + "$ref": "Camera.json" + }, + { + "type": "object", + "properties": { + "type": { + "const": "OrthographicCamera" + }, + "viewHeight": { + "type": "number" + } + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/PartOf.json b/schema/v4/PartOf.json new file mode 100644 index 0000000..f871946 --- /dev/null +++ b/schema/v4/PartOf.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/PartOf.json", + "title": "PartOf", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "enum": [ + "AnnotationCollection", + "Collection", + "Manifest" + ] + }, + "language": { + "type": "array", + "items": { + "$ref": "properties.json#/$defs/BCP47" + } + } + }, + "required": [ + "id", + "type" + ], + "not": { + "required": [ + "items" + ] + } + } +} \ No newline at end of file diff --git a/schema/v4/PerspectiveCamera.json b/schema/v4/PerspectiveCamera.json new file mode 100644 index 0000000..421e85f --- /dev/null +++ b/schema/v4/PerspectiveCamera.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/PerspectiveCamera.json", + "title": "PerspectiveCamera", + "type": "object", + "allOf": [ + { + "$ref": "Camera.json" + }, + { + "type": "object", + "properties": { + "type": { + "const": "PerspectiveCamera" + }, + "fieldOfView": { + "type": "number" + } + + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/PlaceholderCanvas.json b/schema/v4/PlaceholderCanvas.json new file mode 100644 index 0000000..1194b47 --- /dev/null +++ b/schema/v4/PlaceholderCanvas.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/PlaceholderCanvas.json", + "title": "PlaceholderCanvas", + "type": "object", + "allOf": [ + { + "$ref": "Canvas.json" + }, + { + "type": "object", + "not": { + "required": [ + "placeholderCanvas", + "accompanyingCanvas" + ] + } + } + ] +} \ No newline at end of file diff --git a/schema/v4/PointAudio.json b/schema/v4/PointAudio.json new file mode 100644 index 0000000..bac72a1 --- /dev/null +++ b/schema/v4/PointAudio.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/PointAudio.json", + "title": "PointAudio", + "type": "object", + "allOf": [ + { + "$ref": "Audio.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^PointAudio$", + "default": "PointAudio" + } + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/PointSelector.json b/schema/v4/PointSelector.json new file mode 100644 index 0000000..48978dc --- /dev/null +++ b/schema/v4/PointSelector.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/PointSelector.json", + "title": "PointSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^PointSelector$", + "default": "PointSelector" + }, + "t": { + "$ref": "properties.json#/$defs/duration" + }, + "x": { + "title": "x", + "type": "number" + }, + "y": { + "title": "y", + "type": "number" + }, + "z": { + "title": "z", + "type": "number" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/Provider.json b/schema/v4/Provider.json new file mode 100644 index 0000000..f27a98e --- /dev/null +++ b/schema/v4/Provider.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Provider.json", + "title": "providers", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "Agent.json" + }, + { + "title": "provider", + "type": "object", + "properties": { + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "homepage": { + "$ref": "Homepage.json" + }, + "logo": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + }, + "seeAlso": { + "$ref": "SeeAlso.json" + } + }, + "required": [ + "label" + ] + } + ] + } +} \ No newline at end of file diff --git a/schema/v4/Quantity.json b/schema/v4/Quantity.json new file mode 100644 index 0000000..7d0360b --- /dev/null +++ b/schema/v4/Quantity.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Quantity.json", + "title": "Quantity", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^Quantity$", + "default": "Quantity" + }, + "quantityValue": { + "type": "number" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "type", + "quantityValue", + "unit" + ] +} \ No newline at end of file diff --git a/schema/v4/RangeSelector.json b/schema/v4/RangeSelector.json new file mode 100644 index 0000000..d1dda33 --- /dev/null +++ b/schema/v4/RangeSelector.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/RangeSelector.json", + "title": "RangeSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^RangeSelector$", + "default": "RangeSelector" + }, + "startSelector": { + "$ref": "Selector.json" + }, + "endSelector": { + "$ref": "Selector.json" + }, + "refinedBy": { + "$ref": "Selector.json" + } + }, + "required": [ + "type", + "startSelector", + "endSelector" + ] +} \ No newline at end of file diff --git a/schema/v4/Reference.json b/schema/v4/Reference.json new file mode 100644 index 0000000..e2a8312 --- /dev/null +++ b/schema/v4/Reference.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Reference.json", + "type": "object", + "title": "Reference", + "description": "Id. type, label but not items are required", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "type": { + "type": "string", + "enum": [ + "Manifest", + "Collection", + "AnnotationCollection", + "AnnotationPage", + "Canvas", + "Range", + "Scene", + "Timeline", + "Annotation" + ] + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + } + }, + "required": [ + "id", + "type" + ], + "not": { + "required": [ + "items" + ] + } +} \ No newline at end of file diff --git a/schema/v4/Resource.json b/schema/v4/Resource.json new file mode 100644 index 0000000..c878831 --- /dev/null +++ b/schema/v4/Resource.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Resource.json", + "type": "object", + "title": "Resource", + "description": "Annotation bodies MUST have an id and type property.", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "enum": [ + "Image", + "Audio", + "Video", + "Model", + "Dataset", + "Text" + ] + }, + "height": { + "$ref": "properties.json#/$defs/dimension" + }, + "width": { + "$ref": "properties.json#/$defs/dimension" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + }, + "language": { + "type": "array", + "items": { + "type": "string" + } + }, + "rendering": { + "$ref": "External.json" + }, + "service": { + "$ref": "Service.json" + }, + "format": { + "$ref": "properties.json#/$defs/format" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + }, + "annotations": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "AnnotationPage.json" + }, + { + "$ref": "AnnotationPageRef.json" + } + ] + } + } + }, + "required": [ + "id", + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/SVGSelector.json b/schema/v4/SVGSelector.json new file mode 100644 index 0000000..83eb7ed --- /dev/null +++ b/schema/v4/SVGSelector.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/SVGSelector.json", + "title": "SVGSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^SvgSelector$", + "default": "SvgSelector" + }, + "value": { + "type:": "string" + } + }, + "required": [ + "type", + "value" + ] +} \ No newline at end of file diff --git a/schema/v4/Scene.json b/schema/v4/Scene.json new file mode 100644 index 0000000..a7c7cb8 --- /dev/null +++ b/schema/v4/Scene.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Scene.json", + "title": "Scene", + "type": "object", + "allOf": [ + { + "$ref": "Container.json" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Scene$", + "default": "Scene" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + }, + "spatialScale": { + "$ref": "Quantity.json" + }, + "backgroundColor": { + "$ref": "properties.json#/$defs/backgroundColor" + }, + "interactionMode": { + "$ref": "properties.json#/$defs/interactionMode" + } + }, + "required": [ + "id", + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/SceneRef.json b/schema/v4/SceneRef.json new file mode 100644 index 0000000..1542305 --- /dev/null +++ b/schema/v4/SceneRef.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/SceneRef.json", + "title": "SceneRef", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Scene$" + } + }, + "required": [ + "id", + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/SeeAlso.json b/schema/v4/SeeAlso.json new file mode 100644 index 0000000..804ed17 --- /dev/null +++ b/schema/v4/SeeAlso.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/SeeAlso.json", + "$ref": "External.json" +} \ No newline at end of file diff --git a/schema/v4/Selector.json b/schema/v4/Selector.json new file mode 100644 index 0000000..52a59d2 --- /dev/null +++ b/schema/v4/Selector.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Selector.json", + "title": "Selector", + "if": { + "type": "string" + }, + "then": { + "type": "string", + "format": "uri", + "pattern": "^http.*$" + }, + "else": { + "type": "object", + "required": [ + "type" + ], + "if": { + "properties": { + "type": { + "const": "PointSelector" + } + } + }, + "then": { + "$ref": "PointSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "FragmentSelector" + } + } + }, + "then": { + "$ref": "FragmentSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "SvgSelector" + } + } + }, + "then": { + "$ref": "SVGSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "ImageApiSelector" + } + } + }, + "then": { + "$ref": "ImageAPISelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "XPathSelector" + } + } + }, + "then": { + "$ref": "XPathSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "CssSelector" + } + } + }, + "then": { + "$ref": "CssSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "TextQuoteSelector" + } + } + }, + "then": { + "$ref": "TextQuoteSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "TextPositionSelector" + } + } + }, + "then": { + "$ref": "TextPositionSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "DataPositionSelector" + } + } + }, + "then": { + "$ref": "DataPositionSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "RangeSelector" + } + } + }, + "then": { + "$ref": "RangeSelector.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "WktSelector" + } + } + }, + "then": { + "$ref": "WktSelector.json" + } + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schema/v4/SpecificResource.json b/schema/v4/SpecificResource.json new file mode 100644 index 0000000..56e3cdc --- /dev/null +++ b/schema/v4/SpecificResource.json @@ -0,0 +1,112 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/SpecificResource.json", + "title": "SpecificResource", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^SpecificResource$", + "default": "SpecificResource" + }, + "format": { + "$ref": "properties.json#/$defs/format" + }, + "accessibility": { + "type": "string" + }, + "source": { + "if": { + "type": "object" + }, + "then": { + "$ref": "Body.json" + }, + "else": { + "$ref": "properties.json#/$defs/id" + } + }, + "scope": { + "$ref": "properties.json#/$defs/id" + }, + "selector": { + "if": { + "type": "array" + }, + "then": { + "items": { + "$ref": "Selector.json" + } + }, + "else": { + "$ref": "Selector.json" + } + }, + "transform": { + "type": "array", + "items": { + "$ref": "Transform.json" + } + }, + "state": { + "if": { + "type": "array" + }, + "then": { + "items": { + "$ref": "State.json" + } + }, + "else": { + "$ref": "State.json" + } + }, + "styleClass": { + "if": { + "type": "array" + }, + "then": { + "items": { + "type": "string" + } + }, + "else": { + "type": "string" + } + }, + "renderedVia": { + "if": { + "type": "array" + }, + "then": { + "items": { + "$ref": "Agent.json" + } + }, + "else": { + "$ref": "Agent.json" + } + }, + "purpose": { + "if": { + "type": "array" + }, + "then": { + "items": { + "type": "string" + } + }, + "else": { + "type": "string" + } + } + }, + "required": [ + "id", + "type", + "source" + ] +} \ No newline at end of file diff --git a/schema/v4/SpotAudio.json b/schema/v4/SpotAudio.json new file mode 100644 index 0000000..b958d30 --- /dev/null +++ b/schema/v4/SpotAudio.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/SpotAudio.json", + "title": "SpotAudio", + "type": "object", + "allOf": [ + { + "$ref": "Audio.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^SpotAudio$", + "default": "SpotAudio" + }, + "angle": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 90 + }, + "lookAt": { + "$ref": "LookAt.json" + } + }, + "required": [ + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/SpotLight.json b/schema/v4/SpotLight.json new file mode 100644 index 0000000..40269b7 --- /dev/null +++ b/schema/v4/SpotLight.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/SpotLight.json", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "const": "SpotLight" + }, + "label": { + "$ref": "properties.json#/$defs/lngString" + }, + "angle": { + "type": "number", + "exclusiveMinimum": 0, + "maximum": 90 + }, + "color": { + "$ref": "properties.json#/$defs/backgroundColor" + }, + "intensity": { + "$ref": "Intensity.json" + }, + "lookAt": { + "$ref": "LookAt.json" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/State.json b/schema/v4/State.json new file mode 100644 index 0000000..4ae6da4 --- /dev/null +++ b/schema/v4/State.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/State.json", + "title": "State", + "type": "object", + "description": "W3C Web Annotation State (TimeState or HttpRequestState)", + "if": { + "properties": { + "type": { + "const": "TimeState" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "TimeState.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "HttpRequestState" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "HttpRequestState.json" + } + } +} \ No newline at end of file diff --git a/schema/v4/Stylesheet.json b/schema/v4/Stylesheet.json new file mode 100644 index 0000000..9d7ab97 --- /dev/null +++ b/schema/v4/Stylesheet.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Stylesheet.json", + "title": "Stylesheet", + "description": "W3C Web Annotation Stylesheet for rendering", + "if": { + "type": "string" + }, + "then": { + "type": "string", + "format": "uri" + }, + "else": { + "type": "object", + "title": "CssStylesheet", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "type": { + "type": "string", + "pattern": "^CssStylesheet$", + "default": "CssStylesheet" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type" + ] + } +} \ No newline at end of file diff --git a/schema/v4/Target.json b/schema/v4/Target.json new file mode 100644 index 0000000..2db9da1 --- /dev/null +++ b/schema/v4/Target.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Target.json", + "title": "Target", + "type": "object", + "if": { + "required": [ + "type" + ], + "properties": { + "type": { + "const": "SpecificResource" + } + } + }, + "then": { + "$ref": "SpecificResource.json" + }, + "else": { + "if": { + "required": [ + "type" + ], + "properties": { + "type": { + "const": "Canvas" + } + } + }, + "then": { + "$ref": "CanvasRef.json" + }, + "else": { + "if": { + "required": [ + "type" + ], + "properties": { + "type": { + "const": "Timeline" + } + } + }, + "then": { + "$ref": "TimelineRef.json" + }, + "else": { + "$ref": "SceneRef.json" + } + } + } +} \ No newline at end of file diff --git a/schema/v4/TextPositionSelector.json b/schema/v4/TextPositionSelector.json new file mode 100644 index 0000000..8dca69c --- /dev/null +++ b/schema/v4/TextPositionSelector.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/TextPositionSelector.json", + "title": "TextPositionSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^TextPositionSelector$", + "default": "TextPositionSelector" + }, + "start": { + "type": "integer" + }, + "end": { + "type": "integer" + } + }, + "required": [ + "type", + "start", + "end" + ] +} \ No newline at end of file diff --git a/schema/v4/TextQuoteSelector.json b/schema/v4/TextQuoteSelector.json new file mode 100644 index 0000000..35e281e --- /dev/null +++ b/schema/v4/TextQuoteSelector.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/TextQuoteSelector.json", + "title": "TextQuoteSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^TextQuoteSelector$", + "default": "TextQuoteSelector" + }, + "exact": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "suffix": { + "type": "string" + } + }, + "required": [ + "type", + "exact" + ] +} \ No newline at end of file diff --git a/schema/v4/TextualBody.json b/schema/v4/TextualBody.json new file mode 100644 index 0000000..802d509 --- /dev/null +++ b/schema/v4/TextualBody.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/TextualBody.json", + "title": "TextualBody", + "description": "Annotation bodies which are TextualBody MUST have an type and value property.", + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^TextualBody$", + "default": "TextualBody" + }, + "value": { + "type": "string" + }, + "format": { + "$ref": "properties.json#/$defs/format" + }, + "language": { + "type": "array", + "items": { + "$ref": "properties.json#/$defs/BCP47" + } + }, + "processingLanguage": { + "type": "string" + }, + "textDirection": { + "$ref": "properties.json#/$defs/textDirection" + }, + "purpose": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "creator": { + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "Agent.json" + } + ] + }, + "created": { + "type": "string" + }, + "modified": { + "type": "string" + } + }, + "required": [ + "value", + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/TimeState.json b/schema/v4/TimeState.json new file mode 100644 index 0000000..49cd1ce --- /dev/null +++ b/schema/v4/TimeState.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/TimeState.json", + "title": "TimeState", + "description": "W3C Web Annotation TimeState for temporal resource versions", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^TimeState$", + "default": "TimeState" + }, + "sourceDate": { + "type": "string" + }, + "sourceDateStart": { + "type": "string" + }, + "sourceDateEnd": { + "type": "string" + }, + "cached": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/Timeline.json b/schema/v4/Timeline.json new file mode 100644 index 0000000..97df280 --- /dev/null +++ b/schema/v4/Timeline.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Timeline.json", + "title": "Timeline", + "type": "object", + "allOf": [ + { + "$ref": "Container.json" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Timeline$", + "default": "Timeline" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + } + }, + "required": [ + "id", + "type", + "duration" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/TimelineRef.json b/schema/v4/TimelineRef.json new file mode 100644 index 0000000..c3f3072 --- /dev/null +++ b/schema/v4/TimelineRef.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/TimelineRef.json", + "title": "TimelineRef", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "pattern": "^Timeline$" + } + }, + "required": [ + "id", + "type" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/Transform.json b/schema/v4/Transform.json new file mode 100644 index 0000000..e161069 --- /dev/null +++ b/schema/v4/Transform.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Transform.json", + "title": "Transform", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "RotateTransform", + "TranslateTransform", + "ScaleTransform" + ] + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/WktSelector.json b/schema/v4/WktSelector.json new file mode 100644 index 0000000..68a1744 --- /dev/null +++ b/schema/v4/WktSelector.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/WktSelector.json", + "type": "object", + "properties": { + "id": { "$ref": "properties.json#/$defs/id" }, + "type": { + "const": "WktSelector" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ] +} \ No newline at end of file diff --git a/schema/v4/XPathSelector.json b/schema/v4/XPathSelector.json new file mode 100644 index 0000000..9f1b004 --- /dev/null +++ b/schema/v4/XPathSelector.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/XPathSelector.json", + "title": "XPathSelector", + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^XPathSelector$", + "default": "XPathSelector" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ] +} \ No newline at end of file diff --git a/schema/v4/agent.json b/schema/v4/agent.json new file mode 100644 index 0000000..f0741a9 --- /dev/null +++ b/schema/v4/agent.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Agent.json", + "title": "Agent", + "description": "W3C Web Annotation Agent (creator, generator)", + "type": "object", + "properties": { + "id": { "$ref": "properties.json#/$defs/id" }, + "type": { + "type": "string", + "pattern": "^Agent$", + "default": "Agent" + }, + "name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_sha1": { + "type": "string" + }, + "homepage": { + "type": "string", + "format": "uri" + } + }, + "required": ["id", "type"] +} diff --git a/schema/v4/audiance.json b/schema/v4/audiance.json new file mode 100644 index 0000000..7fd5491 --- /dev/null +++ b/schema/v4/audiance.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Audiance.json", + "title": "Audience", + "description": "W3C Web Annotation Audience", + "type": "object", + "properties": { + "id": { "$ref": "properties.json#/$defs/id" }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/main.json b/schema/v4/main.json new file mode 100644 index 0000000..74a7468 --- /dev/null +++ b/schema/v4/main.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/main.json", + "title": "AbstractIIIFResource", + "type": "object", + "required": [ + "type" + ], + "if": { + "properties": { + "type": { + "const": "Manifest" + } + } + }, + "then": { + "$ref": "Manifest.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Collection" + } + } + }, + "then": { + "$ref": "Collection.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "AnnotationCollection" + } + } + }, + "then": { + "$ref": "AnnotationCollection.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "AnnotationPage" + } + } + }, + "then": { + "$ref": "AnnotationPage.json" + }, + "else": { + "if": { + "properties": { + "type": { + "const": "Annotation" + } + } + }, + "then": { + "$ref": "Annotation.json" + }, + "else": { + "not": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/schema/v4/metadata.json b/schema/v4/metadata.json new file mode 100644 index 0000000..b24dc4c --- /dev/null +++ b/schema/v4/metadata.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Metadata.json", + "type": "array", + "title": "Metadata", + "items": { + "$ref": "properties.json#/$defs/keyValueString" + } +} \ No newline at end of file diff --git a/schema/v4/properties.json b/schema/v4/properties.json new file mode 100644 index 0000000..9ab2795 --- /dev/null +++ b/schema/v4/properties.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/properties.json", + "$defs": { + "context": { + "title": "context", + "type": "string", + "const": "http://iiif.io/api/presentation/4/context.json" + }, + "id": { + "title": "id", + "description": "Id must be present and must be a URI", + "type": "string", + "format": "uri", + "pattern": "^http.*$" + }, + "lngString": { + "title": "LngString", + "description": "Language string, must have a language and value must be an array.", + "type": "object", + "patternProperties": { + "^[a-zA-Z-][a-zA-Z-]*$": { + "type": "array", + "items": { + "type": "string" + } + }, + "^none$": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "dimension": { + "title": "dimension", + "type": "integer", + "exclusiveMinimum": 0 + }, + "keyValueString": { + "title": "KeyValueString", + "type": "object", + "properties": { + "label": { + "$ref": "#/$defs/lngString" + }, + "value": { + "$ref": "#/$defs/lngString" + } + }, + "required": [ + "label", + "value" + ] + }, + "BCP47": { + "title": "BCP47", + "anyOf": [ + { + "type": "string", + "pattern": "^[a-zA-Z-][a-zA-Z-]*$" + }, + { + "type": "string", + "pattern": "^none$" + } + ] + }, + "format": { + "title": "format", + "type": "string", + "pattern": "^[a-z][a-z]*/.*$" + }, + "duration": { + "title": "duration", + "type": "number", + "exclusiveMinimum": 0 + }, + "textDirection": { + "title": "textDirection", + "description": "Text direction for processing from W3C Web Annotation Model", + "type": "string", + "enum": [ + "ltr", + "rtl", + "auto" + ] + }, + "viewingDirection": { + "title": "viewingDirection", + "type": "string", + "enum": [ + "left-to-right", + "right-to-left", + "top-to-bottom", + "bottom-to-top" + ] + }, + "behavior": { + "title": "behavior", + "type": "array", + "items": { + "type": "string", + "enum": [ + "auto-advance", + "no-auto-advance", + "repeat", + "no-repeat", + "unordered", + "individuals", + "continuous", + "paged", + "facing-pages", + "non-paged", + "multi-part", + "together", + "sequence", + "thumbnail-nav", + "no-nav", + "hidden" + ] + } + }, + "rights": { + "title": "rights", + "description": "Rights URI isn't from either Creative Commons or RightsStatements.org. Both require http links.", + "oneOf": [ + { + "type": "string", + "format": "uri", + "pattern": "http://creativecommons.org/licenses/.*" + }, + { + "type": "string", + "format": "uri", + "pattern": "http://creativecommons.org/publicdomain/.*" + }, + { + "type": "string", + "format": "uri", + "pattern": "http://rightsstatements.org/vocab/.*" + } + ] + }, + "navDate": { + "type": "string", + "format": "date-time" + }, + "backgroundColor": { + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$", + "description": "RGB color expressed as a 6-digit hex string beginning with '#'." + }, + "interactionMode": { + "title": "interactionMode", + "type": "array", + "items": { + "type": "string", + "enum": [ + "locked", + "orbit", + "hemisphere-orbit", + "free", + "free-direction" + ] + } + } + } +} \ No newline at end of file diff --git a/schema/v4/service.json b/schema/v4/service.json new file mode 100644 index 0000000..654406a --- /dev/null +++ b/schema/v4/service.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Service.json", + "title": "Services", + "type": "array", + "items": { + "if": { + "type": "object", + "required": [ + "id" + ] + }, + "then": { + "type": "object", + "title": "serviceV3", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string" + }, + "profile": { + "type": "string" + }, + "service": { + "$ref": "Service.json" + } + }, + "required": [ + "id", + "type" + ] + }, + "else": { + "type": "object", + "title": "serviceV2", + "properties": { + "@id": { + "$ref": "properties.json#/$defs/id" + }, + "@type": { + "type": "string" + }, + "profile": { + "type": "string" + }, + "service": { + "$ref": "Service.json" + } + }, + "required": [ + "@id", + "@type" + ] + } + } +} \ No newline at end of file diff --git a/tests/test_validator.py b/tests/test_validator.py index 3e36cc3..4700d9c 100644 --- a/tests/test_validator.py +++ b/tests/test_validator.py @@ -2,6 +2,7 @@ import unittest from unittest.mock import Mock import importlib +from pathlib import Path from bottle import Response, request, LocalRequest @@ -108,6 +109,40 @@ def test_bad_manifests_v3(self): self.assertFalse(result.passed) + def test_good_manifests_v4(self): + base = Path("fixtures/4/ok") + data = [] + for path in base.rglob("*.json"): + with path.open("r", encoding="utf-8") as f: + print ('Testing: {}'.format(path)) + data = json.load(f) + + result = check_manifest(data, '4.0') + if not result.passed: + if 'errorList' in result.errorList: + self.printValidationerror(path, result.errorList) + else: + print ('Failed to find errors but manifest {} failed validation'.format(path)) + print (json.dumps(result.json(), indent=2)) + + self.assertTrue(result.passed, 'Expected manifest {} to pass validation but it failed'.format(path)) + + def test_bad_manifests_v4(self): + base = Path("fixtures/4/bad") + data = [] + for path in base.rglob("*.json"): + with path.open("r", encoding="utf-8") as f: + print ('Testing: {}'.format(path)) + data = json.load(f) + + result = check_manifest(data, '3.0') + + if result.passed: + print(f"Expected {path} to fail validation but it passed....") + + self.assertFalse(result.passed) + + def printValidationerror(self, filename, errors): print('Failed to validate: {}'.format(filename))