From fb1e1f0a1feb9d00bb8cd3697247c1ba4a20b7fe Mon Sep 17 00:00:00 2001
From: Henry <chocolatkey@gmail.com>
Date: Tue, 18 Mar 2025 12:12:30 -0700
Subject: [PATCH 1/4] fixes

---
 pkg/parser/epub/metadata.go | 102 +++++++++++++++++++++++++-----------
 1 file changed, 71 insertions(+), 31 deletions(-)

diff --git a/pkg/parser/epub/metadata.go b/pkg/parser/epub/metadata.go
index ec32da2..bc7b221 100644
--- a/pkg/parser/epub/metadata.go
+++ b/pkg/parser/epub/metadata.go
@@ -403,13 +403,18 @@ func (m metadataAdapter) FirstValue(property string) string {
 	return item.value
 }
 
+func itemsValues(items []MetadataItem) []string {
+	values := make([]string, len(items))
+	for i, item := range items {
+		values[i] = item.value
+	}
+	return values
+}
+
 func (m metadataAdapter) Values(property string) []string {
 	var values []string
 	if items, ok := m.items[property]; ok {
-		values = make([]string, len(items))
-		for i, item := range items {
-			values[i] = item.value
-		}
+		values = itemsValues(items)
 	}
 	return values
 }
@@ -640,18 +645,38 @@ func (m PubMetadataAdapter) LocalizedSortAs() *manifest.LocalizedString {
 
 func (m PubMetadataAdapter) Accessibility() *manifest.A11y {
 	a11y := manifest.NewA11y()
-	a11y.ConformsTo = m.a11yConformsTo()
-	a11y.Certification = m.a11yCertification()
-	a11y.Summary = m.a11ySummary()
-	a11y.AccessModes = m.a11yAccessModes()
-	a11y.AccessModesSufficient = m.a11yAccessModesSufficient()
-	a11y.Features = m.a11yFeatures()
-	a11y.Hazards = m.a11yHazards()
-	a11y.Exemptions = m.a11yExemptions()
+	ct, refinedA11y := m.a11yConformsTo()
+	a11y.ConformsTo = ct
+
+	certifierItem, _ := m.First(VocabularyA11Y + "certifiedBy")
+	a11y.Certification = m.a11yCertification(certifierItem)
+
+	a11y.Summary = m.FirstValue(VocabularySchema + "accessibilitySummary")
+
+	modeValues := m.Values(VocabularySchema + "accessMode")
+	a11y.AccessModes = valuesToAccessModes(modeValues)
+
+	sufficientValues := m.Values(VocabularySchema + "accessModeSufficient")
+	a11y.AccessModesSufficient = valuesToAccessModesSufficient(sufficientValues)
+
+	featureValues := m.Values(VocabularySchema + "accessibilityFeature")
+	a11y.Features = valuesToA11yFeatures(featureValues)
+
+	hazardValues := m.Values(VocabularySchema + "accessibilityHazard")
+	a11y.Hazards = valuesToA11yHazards(hazardValues)
+
+	exemptionValues := m.Values(VocabularyA11Y + "exemption")
+	a11y.Exemptions = valuesToA11yExemptions(exemptionValues)
 
 	if a11y.IsEmpty() {
 		return nil
 	}
+
+	// ConformsTo refinements merge with the main a11y data
+	if !refinedA11y.IsEmpty() {
+		a11y.Merge(&refinedA11y)
+	}
+
 	return &a11y
 }
 
@@ -674,13 +699,32 @@ func (m PubMetadataAdapter) TDM() *manifest.TDM {
 	return tdm
 }
 
-func (m PubMetadataAdapter) a11yConformsTo() []manifest.A11yProfile {
+func (m PubMetadataAdapter) a11yConformsTo() ([]manifest.A11yProfile, manifest.A11y) {
 	profiles := manifest.A11yProfileList{}
+	a11y := manifest.NewA11y()
 
-	if items, ok := m.items[VocabularyDCTerms+"conformsto"]; ok {
+	if items, ok := m.items[VocabularyDCTerms+"conformsTo"]; ok {
 		for _, item := range items {
 			if profile := a11yProfile(item.value); profile != "" {
 				profiles = append(profiles, profile)
+				for k, v := range item.children {
+					switch k {
+					case VocabularyA11Y + "certifiedBy":
+						a11y.Certification = m.a11yCertification(v[0])
+					case VocabularySchema + "accessibilitySummary":
+						a11y.Summary = v[0].value
+					case VocabularySchema + "accessMode":
+						a11y.AccessModes = valuesToAccessModes(itemsValues(v))
+					case VocabularySchema + "accessModeSufficient":
+						a11y.AccessModesSufficient = valuesToAccessModesSufficient(itemsValues(v))
+					case VocabularySchema + "accessibilityFeature":
+						a11y.Features = valuesToA11yFeatures(itemsValues(v))
+					case VocabularySchema + "accessibilityHazard":
+						a11y.Hazards = valuesToA11yHazards(itemsValues(v))
+					case VocabularyA11Y + "exemption":
+						a11y.Exemptions = valuesToA11yExemptions(itemsValues(v))
+					}
+				}
 			}
 		}
 	}
@@ -692,7 +736,7 @@ func (m PubMetadataAdapter) a11yConformsTo() []manifest.A11yProfile {
 	}
 
 	profiles.Sort()
-	return profiles
+	return profiles, a11y
 }
 
 func a11yProfile(value string) manifest.A11yProfile {
@@ -726,13 +770,18 @@ func a11yProfile(value string) manifest.A11yProfile {
 		return manifest.EPUBA11y11WCAG21AA
 	case "EPUB Accessibility 1.1 - WCAG 2.1 Level AAA":
 		return manifest.EPUBA11y11WCAG21AAA
+	case "EPUB Accessibility 1.1 - WCAG 2.2 Level A":
+		return manifest.EPUBA11y11WCAG22A
+	case "EPUB Accessibility 1.1 - WCAG 2.2 Level AA":
+		return manifest.EPUBA11y11WCAG22AA
+	case "EPUB Accessibility 1.1 - WCAG 2.2 Level AAA":
+		return manifest.EPUBA11y11WCAG22AAA
 	default:
 		return ""
 	}
 }
 
-func (m PubMetadataAdapter) a11yCertification() *manifest.A11yCertification {
-	certifierItem, _ := m.First(VocabularyA11Y + "certifiedBy")
+func (m PubMetadataAdapter) a11yCertification(certifierItem MetadataItem) *manifest.A11yCertification {
 	c := manifest.A11yCertification{
 		CertifiedBy: certifierItem.value,
 	}
@@ -760,12 +809,7 @@ func (m PubMetadataAdapter) a11yCertification() *manifest.A11yCertification {
 	return &c
 }
 
-func (m PubMetadataAdapter) a11ySummary() string {
-	return m.FirstValue(VocabularySchema + "accessibilitySummary")
-}
-
-func (m PubMetadataAdapter) a11yAccessModes() []manifest.A11yAccessMode {
-	values := m.Values(VocabularySchema + "accessMode")
+func valuesToAccessModes(values []string) []manifest.A11yAccessMode {
 	am := make([]manifest.A11yAccessMode, len(values))
 	for i, v := range values {
 		am[i] = manifest.A11yAccessMode(v)
@@ -773,8 +817,7 @@ func (m PubMetadataAdapter) a11yAccessModes() []manifest.A11yAccessMode {
 	return am
 }
 
-func (m PubMetadataAdapter) a11yAccessModesSufficient() [][]manifest.A11yPrimaryAccessMode {
-	values := m.Values(VocabularySchema + "accessModeSufficient")
+func valuesToAccessModesSufficient(values []string) [][]manifest.A11yPrimaryAccessMode {
 	ams := make([][]manifest.A11yPrimaryAccessMode, 0, len(values))
 	for _, v := range values {
 		c := a11yAccessModesSufficient(v)
@@ -797,8 +840,7 @@ func a11yAccessModesSufficient(value string) []manifest.A11yPrimaryAccessMode {
 	return ams
 }
 
-func (m PubMetadataAdapter) a11yFeatures() []manifest.A11yFeature {
-	values := m.Values(VocabularySchema + "accessibilityFeature")
+func valuesToA11yFeatures(values []string) []manifest.A11yFeature {
 	features := make([]manifest.A11yFeature, len(values))
 	for i, v := range values {
 		features[i] = manifest.A11yFeature(v)
@@ -806,8 +848,7 @@ func (m PubMetadataAdapter) a11yFeatures() []manifest.A11yFeature {
 	return features
 }
 
-func (m PubMetadataAdapter) a11yHazards() []manifest.A11yHazard {
-	values := m.Values(VocabularySchema + "accessibilityHazard")
+func valuesToA11yHazards(values []string) []manifest.A11yHazard {
 	hazards := make([]manifest.A11yHazard, len(values))
 	for i, v := range values {
 		hazards[i] = manifest.A11yHazard(v)
@@ -815,8 +856,7 @@ func (m PubMetadataAdapter) a11yHazards() []manifest.A11yHazard {
 	return hazards
 }
 
-func (m PubMetadataAdapter) a11yExemptions() []manifest.A11yExemption {
-	values := m.Values(VocabularyA11Y + "exemption")
+func valuesToA11yExemptions(values []string) []manifest.A11yExemption {
 	exemptions := make([]manifest.A11yExemption, len(values))
 	for i, v := range values {
 		exemptions[i] = manifest.A11yExemption(v)

From 917784a8d6c51e78a518d3cc81cf59e2c65ca03b Mon Sep 17 00:00:00 2001
From: Henry <chocolatkey@gmail.com>
Date: Tue, 18 Mar 2025 14:40:32 -0700
Subject: [PATCH 2/4] fix incorrect link

---
 pkg/manifest/contributor.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/manifest/contributor.go b/pkg/manifest/contributor.go
index 9296835..d53c399 100644
--- a/pkg/manifest/contributor.go
+++ b/pkg/manifest/contributor.go
@@ -9,7 +9,7 @@ import (
 
 // Contributor
 // https://github.com/readium/webpub-manifest/tree/master/contexts/default#contributors
-// https://github.com/readium/webpub-manifest/schema/contributor-object.schema.json
+// https://github.com/readium/webpub-manifest/blob/master/schema/contributor-object.schema.json
 type Contributor struct {
 	LocalizedName   LocalizedString  `json:"name" validate:"required"` // The name of the contributor.
 	LocalizedSortAs *LocalizedString `json:"sortAs,omitempty"`         // The string used to sort the name of the contributor.

From d3a7463d6a863e5e29334977dd27a2512c376492 Mon Sep 17 00:00:00 2001
From: Henry <chocolatkey@gmail.com>
Date: Tue, 18 Mar 2025 14:42:18 -0700
Subject: [PATCH 3/4] fix bug in casing of DC element data

---
 pkg/parser/epub/metadata.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pkg/parser/epub/metadata.go b/pkg/parser/epub/metadata.go
index bc7b221..e25c60d 100644
--- a/pkg/parser/epub/metadata.go
+++ b/pkg/parser/epub/metadata.go
@@ -205,7 +205,7 @@ func (m MetadataParser) parseMetaElement(element *xmlquery.Node) *MetadataItem {
 			id:       element.SelectAttr("id"),
 		}
 	} else {
-		propName := strings.TrimSpace(element.SelectAttr("property"))
+		propName := strings.TrimSpace(property)
 		if propName == "" {
 			return nil
 		}
@@ -238,7 +238,7 @@ func (m MetadataParser) parseDcElement(element *xmlquery.Node) *MetadataItem {
 	}
 
 	data := strings.ToLower(element.Data)
-	propName := VocabularyDCTerms + data
+	propName := VocabularyDCTerms + element.Data
 	switch data {
 	case "creator":
 		fallthrough

From 79abb2675421c1c7dab029f02f92c2ec8455dea4 Mon Sep 17 00:00:00 2001
From: Henry <chocolatkey@gmail.com>
Date: Wed, 7 May 2025 23:31:21 -0700
Subject: [PATCH 4/4] added test nested a11y data

---
 pkg/parser/epub/metadata_test.go              | 20 ++++++++++++++
 .../package/accessibility-refines.opf         | 27 +++++++++++++++++++
 2 files changed, 47 insertions(+)
 create mode 100644 pkg/parser/epub/testdata/package/accessibility-refines.opf

diff --git a/pkg/parser/epub/metadata_test.go b/pkg/parser/epub/metadata_test.go
index 4765cf7..8bb7121 100644
--- a/pkg/parser/epub/metadata_test.go
+++ b/pkg/parser/epub/metadata_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/readium/go-toolkit/pkg/mediatype"
 	"github.com/readium/go-toolkit/pkg/util/url"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func loadMetadata(ctx context.Context, name string) (*manifest.Metadata, error) {
@@ -361,6 +362,25 @@ func TestMetadataEPUB3Accessibility(t *testing.T) {
 	assert.Nil(t, m.OtherMetadata["accessibility"])
 }
 
+func TestMetadataEPUB3AccessibilityRefines(t *testing.T) {
+	m, err := loadMetadata(t.Context(), "accessibility-refines")
+	require.NoError(t, err)
+	e := manifest.NewA11y()
+	e.Summary = "This publication conforms to WCAG 2.2 Level AA."
+	e.ConformsTo = []manifest.A11yProfile{manifest.EPUBA11y11WCAG22AA}
+	e.Certification = &manifest.A11yCertification{
+		CertifiedBy: "Standard Ebooks",
+	}
+	e.AccessModes = manifest.A11yAccessModesFromStrings([]string{"textual"})
+	e.AccessModesSufficient = [][]manifest.A11yPrimaryAccessMode{
+		a11yAccessModesSufficient("textual"),
+	}
+	e.Features = valuesToA11yFeatures([]string{"readingOrder", "structuralNavigation", "tableOfContents", "unlocked"})
+	e.Hazards = valuesToA11yHazards([]string{"none"})
+	assert.Equal(t, &e, m.Accessibility)
+	assert.Nil(t, m.OtherMetadata["accessibility"])
+}
+
 func TestMetadataEPUB3TDM(t *testing.T) {
 	m, err := loadMetadata(t.Context(), "tdm-epub3")
 	assert.NoError(t, err)
diff --git a/pkg/parser/epub/testdata/package/accessibility-refines.opf b/pkg/parser/epub/testdata/package/accessibility-refines.opf
new file mode 100644
index 0000000..c468613
--- /dev/null
+++ b/pkg/parser/epub/testdata/package/accessibility-refines.opf
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<package xmlns="http://www.idpf.org/2007/opf" dir="ltr" prefix="se: https://standardebooks.org/vocab/1.0" unique-identifier="uid" version="3.0" xml:lang="en-US">
+	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
+		<dc:identifier id="uid">url:https://standardebooks.org/ebooks/nathaniel-hawthorne/the-house-of-the-seven-gables</dc:identifier>
+		<meta property="dcterms:modified">2024-10-02T02:28:18Z</meta>
+		<dc:publisher id="publisher">Standard Ebooks</dc:publisher>
+		<meta id="conformance-statement" property="dcterms:conformsTo">EPUB Accessibility 1.1 - WCAG 2.2 Level AA</meta>
+		<meta property="a11y:certifiedBy" refines="#conformance-statement">Standard Ebooks</meta>
+		<meta property="schema:accessMode">textual</meta>
+		<meta property="schema:accessModeSufficient">textual</meta>
+		<meta property="schema:accessibilityFeature">readingOrder</meta>
+		<meta property="schema:accessibilityFeature">structuralNavigation</meta>
+		<meta property="schema:accessibilityFeature">tableOfContents</meta>
+		<meta property="schema:accessibilityFeature">unlocked</meta>
+		<meta property="schema:accessibilityHazard">none</meta>
+		<meta property="schema:accessibilitySummary">This publication conforms to WCAG 2.2 Level AA.</meta>
+		<dc:title id="title">The House of the Seven Gables</dc:title>
+		<!--Deleted-->
+		<dc:language>en-US</dc:language>
+	</metadata>
+	<manifest>
+		<item href="text/titlepage.xhtml" id="titlepage.xhtml" media-type="application/xhtml+xml" />
+	</manifest>
+	<spine>
+		<itemref idref="titlepage.xhtml"/>
+	</spine>
+</package>
\ No newline at end of file