diff --git a/.github/workflows/check-jsonschema.yml b/.github/workflows/check-jsonschema.yml new file mode 100644 index 0000000..4c1cb75 --- /dev/null +++ b/.github/workflows/check-jsonschema.yml @@ -0,0 +1,35 @@ +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@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + # If you already have a requirements file, replace this step with pip install -r ... + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Run $ref checker + run: | + python schema/v4/check_refs.py \ No newline at end of file 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/AnnoTarget.json b/schema/v4/AnnoTarget.json new file mode 100644 index 0000000..3f132b0 --- /dev/null +++ b/schema/v4/AnnoTarget.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnoTarget.json", + "title": "AnnotationTarget", + "if": { + "type": "string" + }, + "then": { + "type": "string", + "format": "uri", + "pattern": "^http.*$" + }, + "else": { + "type": "object", + "$ref": "SpecificResource.json" + } +} \ No newline at end of file diff --git a/schema/v4/Annotation.json b/schema/v4/Annotation.json new file mode 100644 index 0000000..b7bab63 --- /dev/null +++ b/schema/v4/Annotation.json @@ -0,0 +1,151 @@ +{ + "$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" + }, + "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": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "Resource.json" + }, + { + "$ref": "Choice.json" + } + ] + } + }, + "target": { + "type": "array", + "items": { + "$ref": "AnnoTarget.json" + } + } + }, + "required": [ + "id", + "target", + "type" + ] +} \ No newline at end of file diff --git a/schema/v4/AnnotationBody.json b/schema/v4/AnnotationBody.json new file mode 100644 index 0000000..21a0cb9 --- /dev/null +++ b/schema/v4/AnnotationBody.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnotationBody.json", + "type": "object", + "title": "AnnotationBody", + "description": "Annotation bodies MUST have an id and type property.", + "properties": { + "id": { + "$ref": "properties.json#/$defs/id" + }, + "type": { + "type": "string", + "not": { + "enum": [ + "TextualBody", + "SpecificResource", + "Choice" + ] + } + }, + "height": { + "$ref": "properties.json#/$defs/dimension" + }, + "width": { + "$ref": "properties.json#/$defs/dimension" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + }, + "language": { + "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/AnnotationCollection.json b/schema/v4/AnnotationCollection.json new file mode 100644 index 0000000..636a2e5 --- /dev/null +++ b/schema/v4/AnnotationCollection.json @@ -0,0 +1,58 @@ +{ + "$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" + } + }, + "items": { + "type": "array", + "items": { + "$ref": "Annotation.json" + } + } + }, + "required": [ + "id", + "type", + "items" + ] +} \ No newline at end of file diff --git a/schema/v4/AnnotationCollectionRef.json b/schema/v4/AnnotationCollectionRef.json new file mode 100644 index 0000000..53cc69f --- /dev/null +++ b/schema/v4/AnnotationCollectionRef.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/AnnotationCollectionRef.json", + "title": "AnnotationCollectionRef", + "if": { + "type": "string" + }, + "then": { + "type": "string", + "format": "uri", + "pattern": "^http.*$" + }, + "else": { + "title": "AnnotationCollectionRefExtended", + "allOf": [ + { + "$ref": "Reference.json" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "pattern": "^AnnotationCollection$" + } + } + } + ] + } +} \ 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/Canvas.json b/schema/v4/Canvas.json new file mode 100644 index 0000000..e7ce22b --- /dev/null +++ b/schema/v4/Canvas.json @@ -0,0 +1,40 @@ +{ + "$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": { + "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" + } + }, + "required": [ + "id", + "type", + "items", + "height", + "width" + ] + } + ] +} \ No newline at end of file diff --git a/schema/v4/Choice.json b/schema/v4/Choice.json new file mode 100644 index 0000000..ed6f6a4 --- /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": "Resource.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..f1fa2cd --- /dev/null +++ b/schema/v4/Collection.json @@ -0,0 +1,107 @@ +{ + "$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": { + "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": [ + "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..b340f7b --- /dev/null +++ b/schema/v4/Container.json @@ -0,0 +1,93 @@ +{ + "$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" + ] + }, + "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": { + "oneOf": [ + { + "$ref": "AnnotationPage.json" + }, + { + "$ref": "AnnotationPageRef.json" + } + ] + } + } + }, + "required": [ + "id", + "type" + ] +} \ 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/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/Manifest.json b/schema/v4/Manifest.json new file mode 100644 index 0000000..f02502e --- /dev/null +++ b/schema/v4/Manifest.json @@ -0,0 +1,92 @@ +{ + "$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": [ + "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/PartOf.json b/schema/v4/PartOf.json new file mode 100644 index 0000000..fb8e275 --- /dev/null +++ b/schema/v4/PartOf.json @@ -0,0 +1,31 @@ +{ + "$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" + ] + } + }, + "required": [ + "id", + "type" + ], + "not": { + "required": [ + "items" + ] + } + } +} \ 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/PointSelector.json b/schema/v4/PointSelector.json new file mode 100644 index 0000000..8214437 --- /dev/null +++ b/schema/v4/PointSelector.json @@ -0,0 +1,25 @@ +{ + "$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": { + "$ref": "properties.json#/$defs/dimension" + }, + "y": { + "$ref": "properties.json#/$defs/dimension" + } + }, + "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..81fb8a7 --- /dev/null +++ b/schema/v4/Quantity.json @@ -0,0 +1,19 @@ +{ + "$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": "^Scene$", + "default": "Scene" + }, + "quantityValue": { + "type": "number" + }, + "unit": { + "type": "string" + } + } +} \ 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/Resource.json b/schema/v4/Resource.json new file mode 100644 index 0000000..ac47db1 --- /dev/null +++ b/schema/v4/Resource.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://iiif.io/api/presentation/4.0/schema/Resource.json", + "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": { + "$ref": "AnnotationBody.json" + } + } + } +} \ 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..6acb030 --- /dev/null +++ b/schema/v4/Scene.json @@ -0,0 +1,32 @@ +{ + "$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": { + "type": { + "type": "string", + "pattern": "^Scene$", + "default": "Scene" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + }, + "spatialScale": { + "$ref": "Quantity.json" + } + }, + "required": [ + "id", + "type", + "items" + ] + } + ] +} \ 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..b1ee5f2 --- /dev/null +++ b/schema/v4/Selector.json @@ -0,0 +1,137 @@ +{ + "$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" + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schema/v4/SpecificResource.json b/schema/v4/SpecificResource.json new file mode 100644 index 0000000..9773626 --- /dev/null +++ b/schema/v4/SpecificResource.json @@ -0,0 +1,103 @@ +{ + "$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": { + "oneOf": [ + { + "$ref": "properties.json#/$defs/id" + }, + { + "$ref": "Resource.json" + } + ] + }, + "scope": { + "$ref": "properties.json#/$defs/id" + }, + "selector": { + "oneOf": [ + { + "$ref": "Selector.json" + }, + { + "type": "array", + "items": { + "$ref": "Selector.json" + } + } + ] + }, + "state": { + "oneOf": [ + { + "$ref": "State.json" + }, + { + "type": "array", + "items": { + "$ref": "State.json" + } + } + ] + }, + "styleClass": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "renderedVia": { + "oneOf": [ + { + "$ref": "Agent.json" + }, + { + "type": "array", + "items": { + "$ref": "Agent.json" + } + } + ] + }, + "purpose": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "source" + ] +} \ 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/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..4f5da4a --- /dev/null +++ b/schema/v4/TextualBody.json @@ -0,0 +1,65 @@ +{ + "$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": "string" + }, + "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..c26dee4 --- /dev/null +++ b/schema/v4/Timeline.json @@ -0,0 +1,30 @@ +{ + "$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": { + "type": { + "type": "string", + "pattern": "^Timeline$", + "default": "Timeline" + }, + "duration": { + "$ref": "properties.json#/$defs/duration" + } + }, + "required": [ + "id", + "type", + "items", + "duration" + ] + } + ] +} \ 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/check_refs.py b/schema/v4/check_refs.py new file mode 100644 index 0000000..cfdbabf --- /dev/null +++ b/schema/v4/check_refs.py @@ -0,0 +1,94 @@ +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]] = {} + + for path in schema_dir.rglob("*.json"): + 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 + + 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) + 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/schema/v4/external.json b/schema/v4/external.json new file mode 100644 index 0000000..9c34727 --- /dev/null +++ b/schema/v4/external.json @@ -0,0 +1,23 @@ +{ + "$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" + }, + "profile": { + "type": "string" + } + }, + "required": ["id", "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..cd4da15 --- /dev/null +++ b/schema/v4/properties.json @@ -0,0 +1,151 @@ +{ + "$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" + } + } +} \ No newline at end of file diff --git a/schema/v4/reference.json b/schema/v4/reference.json new file mode 100644 index 0000000..0dce326 --- /dev/null +++ b/schema/v4/reference.json @@ -0,0 +1,41 @@ +{ + "$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" + ] + }, + "thumbnail": { + "type": "array", + "items": { + "$ref": "Resource.json" + } + } + }, + "required": [ + "id", + "type" + ], + "not": { + "required": [ + "items" + ] + } +} \ No newline at end of file diff --git a/schema/v4/service.json b/schema/v4/service.json new file mode 100644 index 0000000..f1d7451 --- /dev/null +++ b/schema/v4/service.json @@ -0,0 +1,52 @@ +{ + "$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": { + "oneOf": [ + { + "type": "object", + "title": "serviceV3", + "properties": { + "id": { "$ref": "properties.json#/$defs/id" }, + "type": { + "type": "string", + "pattern": "^Service$", + "default": "Service" + }, + "profile": { + "type": "string" + }, + "service": { + "$ref": "Service.json" + } + }, + "required": [ + "id", + "type" + ] + }, + { + "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/schema/validation4.py b/schema/validation4.py new file mode 100644 index 0000000..5a17b3f --- /dev/null +++ b/schema/validation4.py @@ -0,0 +1,68 @@ +from ast import main +from pathlib import Path +import json +import sys + +from jsonschema import Draft202012Validator +from referencing import Registry, Resource +from referencing.jsonschema import DRAFT202012 + +SCHEMA_DIR = Path("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) + + validator.validate(instance) + print ("Validation successful") + +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