From 31425fa5621bd0aeb8c35df7fc66f6f83c91853b Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 Jan 2025 21:26:49 -0800 Subject: [PATCH] implement EPUB/WebPub exemptions --- pkg/manifest/a11y.go | 23 +++++++++++++++++++ pkg/manifest/a11y_test.go | 14 +++++++++-- pkg/parser/epub/metadata.go | 10 ++++++++ pkg/parser/epub/metadata_test.go | 2 ++ .../testdata/package/accessibility-epub2.opf | 3 +++ .../testdata/package/accessibility-epub3.opf | 3 +++ 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pkg/manifest/a11y.go b/pkg/manifest/a11y.go index cf443064..d28a55d3 100644 --- a/pkg/manifest/a11y.go +++ b/pkg/manifest/a11y.go @@ -19,6 +19,7 @@ type A11y struct { AccessModesSufficient [][]A11yPrimaryAccessMode `json:"accessModeSufficient,omitempty"` // A list of single or combined accessModes that are sufficient to understand all the intellectual content of a resource. Features []A11yFeature `json:"feature,omitempty"` // Content features of the resource, such as accessible media, alternatives and supported enhancements for accessibility. Hazards []A11yHazard `json:"hazard,omitempty"` // A characteristic of the described resource that is physiologically dangerous to some users. + Exemptions []A11yExemption `json:"exemption,omitempty"` // Justifications for non-conformance based on exemptions in a given jurisdiction. } // NewA11y creates a new empty A11y. @@ -29,6 +30,7 @@ func NewA11y() A11y { AccessModesSufficient: [][]A11yPrimaryAccessMode{}, Features: []A11yFeature{}, Hazards: []A11yHazard{}, + Exemptions: []A11yExemption{}, } } @@ -130,6 +132,12 @@ func A11yFromJSON(rawJSON map[string]interface{}) (*A11y, error) { } a.Hazards = A11yHazardsFromStrings(hazards) + examptions, err := parseSliceOrString(rawJSON["exemption"], true) + if err != nil { + return nil, errors.Wrap(err, "failed unmarshalling 'exemption'") + } + a.Exemptions = A11yExemptionsFromStrings(examptions) + return a, nil } @@ -433,6 +441,21 @@ func A11yHazardsFromStrings(strings []string) []A11yHazard { }) } +// A11yExemption is a justification for non-conformance based on an exemption in a given jurisdiction. +type A11yExemption string + +const ( + A11yExemptionEAASisproportionateBurden A11yExemption = "eaa-disproportionate-burden" + A11yExemptionEAAFundamentalAlteration A11yExemption = "eaa-fundamental-alteration" + A11yExemptionEAAMicroenterprise A11yExemption = "eaa-microenterprise" +) + +func A11yExemptionsFromStrings(strings []string) []A11yExemption { + return fromStrings(strings, func(str string) A11yExemption { + return A11yExemption(str) + }) +} + func fromStrings[T any](strings []string, transform func(string) T) []T { res := make([]T, 0, len(strings)) for _, s := range strings { diff --git a/pkg/manifest/a11y_test.go b/pkg/manifest/a11y_test.go index cb979329..c569cad6 100644 --- a/pkg/manifest/a11y_test.go +++ b/pkg/manifest/a11y_test.go @@ -26,7 +26,8 @@ func TestA11yUnmarshalFullJSON(t *testing.T) { "accessMode": ["auditory", "chartOnVisual"], "accessModeSufficient": [["visual", "tactile"]], "feature": ["readingOrder", "alternativeText"], - "hazard": ["flashing", "motionSimulation"] + "hazard": ["flashing", "motionSimulation"], + "exemption": ["eaa-fundamental-alteration", "eaa-microenterprise"] }`), &m)) assert.Equal(t, A11y{ ConformsTo: []A11yProfile{ @@ -57,6 +58,10 @@ func TestA11yUnmarshalFullJSON(t *testing.T) { A11yHazardFlashing, A11yHazardMotionSimulation, }, + Exemptions: []A11yExemption{ + A11yExemptionEAAFundamentalAlteration, + A11yExemptionEAAMicroenterprise, + }, }, m, "unmarshalled JSON object should be equal to A11y object") } @@ -101,6 +106,7 @@ func TestA11yMarshalMinimalJSON(t *testing.T) { AccessModesSufficient: [][]A11yPrimaryAccessMode{}, Features: []A11yFeature{}, Hazards: []A11yHazard{}, + Exemptions: []A11yExemption{}, } data, err := json.Marshal(m) assert.NoError(t, err) @@ -139,12 +145,16 @@ func TestA11yMarshalFullJSON(t *testing.T) { A11yHazardFlashing, A11yHazardMotionSimulation, }, + Exemptions: []A11yExemption{ + A11yExemptionEAAFundamentalAlteration, + A11yExemptionEAAMicroenterprise, + }, } data, err := json.Marshal(m) assert.NoError(t, err) assert.Equal( t, data, - []byte(`{"conformsTo":["http://www.idpf.org/epub/a11y/accessibility-20170105.html#wcag-a","https://profile2"],"certification":{"certifiedBy":"company1","credential":"credential1","report":"https://report1"},"summary":"Summary","accessMode":["auditory","chartOnVisual"],"accessModeSufficient":[["auditory"],["visual","tactile"],["visual"]],"feature":["readingOrder","alternativeText"],"hazard":["flashing","motionSimulation"]}`), + []byte(`{"conformsTo":["http://www.idpf.org/epub/a11y/accessibility-20170105.html#wcag-a","https://profile2"],"certification":{"certifiedBy":"company1","credential":"credential1","report":"https://report1"},"summary":"Summary","accessMode":["auditory","chartOnVisual"],"accessModeSufficient":[["auditory"],["visual","tactile"],["visual"]],"feature":["readingOrder","alternativeText"],"hazard":["flashing","motionSimulation"],"exemption":["eaa-fundamental-alteration","eaa-microenterprise"]}`), ) } diff --git a/pkg/parser/epub/metadata.go b/pkg/parser/epub/metadata.go index 85ffc7d2..6a2c82d6 100644 --- a/pkg/parser/epub/metadata.go +++ b/pkg/parser/epub/metadata.go @@ -646,6 +646,7 @@ func (m PubMetadataAdapter) Accessibility() *manifest.A11y { a11y.AccessModesSufficient = m.a11yAccessModesSufficient() a11y.Features = m.a11yFeatures() a11y.Hazards = m.a11yHazards() + a11y.Exemptions = m.a11yExemptions() if a11y.IsEmpty() { return nil @@ -785,6 +786,15 @@ func (m PubMetadataAdapter) a11yHazards() []manifest.A11yHazard { return hazards } +func (m PubMetadataAdapter) a11yExemptions() []manifest.A11yExemption { + values := m.Values(VocabularyA11Y + "exemption") + hazards := make([]manifest.A11yExemption, len(values)) + for i, v := range values { + hazards[i] = manifest.A11yExemption(v) + } + return hazards +} + func (m *PubMetadataAdapter) seedBelongsToData() { if m._belongsToSeeded { return diff --git a/pkg/parser/epub/metadata_test.go b/pkg/parser/epub/metadata_test.go index d04de26f..d7c91ab9 100644 --- a/pkg/parser/epub/metadata_test.go +++ b/pkg/parser/epub/metadata_test.go @@ -323,6 +323,7 @@ func TestMetadataEPUB2Accessibility(t *testing.T) { } e.Features = []manifest.A11yFeature{manifest.A11yFeatureStructuralNavigation, manifest.A11yFeatureAlternativeText} e.Hazards = []manifest.A11yHazard{manifest.A11yHazardMotionSimulation, manifest.A11yHazardNoSoundHazard} + e.Exemptions = []manifest.A11yExemption{manifest.A11yExemptionEAAMicroenterprise, manifest.A11yExemptionEAAFundamentalAlteration} assert.Equal(t, &e, m.Accessibility) assert.Nil(t, m.OtherMetadata["accessibility"]) } @@ -345,6 +346,7 @@ func TestMetadataEPUB3Accessibility(t *testing.T) { } e.Features = []manifest.A11yFeature{manifest.A11yFeatureStructuralNavigation, manifest.A11yFeatureAlternativeText} e.Hazards = []manifest.A11yHazard{manifest.A11yHazardMotionSimulation, manifest.A11yHazardNoSoundHazard} + e.Exemptions = []manifest.A11yExemption{manifest.A11yExemptionEAAMicroenterprise, manifest.A11yExemptionEAAFundamentalAlteration} assert.Equal(t, &e, m.Accessibility) assert.Nil(t, m.OtherMetadata["accessibility"]) } diff --git a/pkg/parser/epub/testdata/package/accessibility-epub2.opf b/pkg/parser/epub/testdata/package/accessibility-epub2.opf index 89264313..61ed8f3c 100644 --- a/pkg/parser/epub/testdata/package/accessibility-epub2.opf +++ b/pkg/parser/epub/testdata/package/accessibility-epub2.opf @@ -20,6 +20,9 @@ + + eaa-microenterprise + eaa-fundamental-alteration diff --git a/pkg/parser/epub/testdata/package/accessibility-epub3.opf b/pkg/parser/epub/testdata/package/accessibility-epub3.opf index 17d4db60..588fce84 100644 --- a/pkg/parser/epub/testdata/package/accessibility-epub3.opf +++ b/pkg/parser/epub/testdata/package/accessibility-epub3.opf @@ -24,6 +24,9 @@ + eaa-microenterprise + eaa-fundamental-alteration +