fair_champion_harvester 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/lib/fair_champion_harvester/handoff.md +313 -0
- data/lib/fair_champion_harvester/version.rb +1 -1
- data/lib/harvester.rb +71 -29
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98197689724c7efe3d1d725d74b96d841ef1e9b4da0ab6068a721109192cd901
|
|
4
|
+
data.tar.gz: eab9a398f94b2770d60aa9bde1a81ca035bc21ee69bc22d2b0be0fc46a148e5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac9ca34832e5045adc5761f4579d69eec86e7ee87e54aeeb6ccb6ae87a3d04b31d3c03bcbbcf730d4274ce2066fbdd4b5e7e28afc5528544d8a84078bf4d1d61
|
|
7
|
+
data.tar.gz: 3d1f6868db870bcd874f3797a93160f5e330b7614c7de0aaa514d3d914ee8fc17a5f6cc2c99ecba0386c030419af446052faf6c7428b2969dd9ce06a1ce95507
|
data/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
1
3
|
## [Unreleased]
|
|
2
4
|
|
|
5
|
+
## [0.1.13] - 2026-06-30
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- JSON-LD documents with a remote `@context` (e.g. `"@context": "http://schema.org"`) were not having their context resolved, so properties like `license` that carry `"@type": "@id"` in the schema.org context were being parsed as string literals instead of IRI resources. This caused FAIR license assessment tests to warn "Found the Schema license predicate, but it does not have a Resource as its value." Fixed by routing JSON-LD through `JSON::LD::API.expand` (processing mode 1.1) before conversion to RDF, which applies `@type: @id` coercions. `json/ld/preloaded` (already required) supplies schema.org and other common contexts without network overhead.
|
|
10
|
+
- Array-wrapped JSON-LD (a top-level JSON array, as returned by extruct and some APIs) no longer raises `TypeError: no implicit conversion of String into Integer` during the context debug log.
|
|
11
|
+
- Added graceful fallback: if JSON-LD context expansion raises `JSON::LD::JsonLdError`, the document is re-parsed without expansion and a warning is recorded rather than failing silently.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- RSpec unit tests for `parse_rdf` JSON-LD path: license IRI coercion, successful expansion comment, array-wrapped document handling.
|
|
16
|
+
|
|
3
17
|
## [0.1.12] - 2026-05-27
|
|
4
18
|
|
|
5
19
|
### Fixed
|
|
20
|
+
|
|
6
21
|
- 0.1.11 regression: `FAIRChampionHarvester::Uri` was missing from the gem because `uri_resolver.rb` was not tracked by git and the gemspec uses `git ls-files` to determine packaged files; merged the class back into `lib/uri.rb` so a single tracked file handles both stdlib forwarding and the class definition
|
|
7
22
|
|
|
8
23
|
## [0.1.11] - 2026-05-26
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
26
|
+
|
|
11
27
|
- `lib/uri.rb` was shadowing Ruby's stdlib `require "uri"` because `lib/` is on the load path; moved `FAIRChampionHarvester::Uri` class to `lib/uri_resolver.rb` and turned `lib/uri.rb` into a stdlib-forwarding shim — this was the root cause of all HTTP fetch failures (`uninitialized constant URI`)
|
|
12
28
|
- `parse_link_http_headers`: handle multiple separate `Link:` headers (Array input) in addition to comma-separated single-string headers, using `Array(links).flat_map { |l| l.split(",") }`
|
|
13
29
|
- `parse_link_http_headers`: `rel` regex `\w+` → `[\w-]+` so hyphenated rel types like `cite-as` are captured correctly rather than silently truncated
|
|
@@ -18,13 +34,13 @@
|
|
|
18
34
|
- `Core.fetch`: backtrace now always logged on `StandardError` (removed `if ENV["DEBUG"]` guard)
|
|
19
35
|
|
|
20
36
|
### Added
|
|
37
|
+
|
|
21
38
|
- RSpec test suite with 14 unit tests for `parse_link_http_headers` and 6 live integration tests against `https://fairsharing.org/1547`; live tests clear stale `/tmp/*_error` cache files before each run
|
|
22
39
|
|
|
23
40
|
## [0.1.10] - 2026-05-26
|
|
24
41
|
|
|
25
42
|
- variable url was not defined, but caught by begin block so no complaints
|
|
26
43
|
|
|
27
|
-
|
|
28
44
|
## [0.1.0] - 2026-03-27
|
|
29
45
|
|
|
30
46
|
- Initial release
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# JSON-LD Context Expansion Fix — Handoff for Claude Code Integration
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-30
|
|
4
|
+
**Issue:** JSON-LD license properties being parsed as blank nodes instead of IRI resources
|
|
5
|
+
**Root Cause:** Remote `@context` resolution not enabled in RDF reader instantiation
|
|
6
|
+
**Impact:** FAIR assessment tests failing because `schema.org` license coercion (`@type: @id`) not applied
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem Summary
|
|
11
|
+
|
|
12
|
+
### What's Happening
|
|
13
|
+
When the FAIR Champion Harvester encounters JSON-LD with `"@context": "http://schema.org"` and `"license": "https://creativecommons.org/licenses/by/4.0/legalcode"`:
|
|
14
|
+
|
|
15
|
+
1. **Expected behavior:** JSON-LD processor fetches schema.org context, sees `license` has `"@type": "@id"`, coerces the URL string to an IRI resource
|
|
16
|
+
2. **Actual behavior:** Parser treats license as a bare string literal, or normalizes it into a blank node structure during serialization
|
|
17
|
+
|
|
18
|
+
### Why It Fails
|
|
19
|
+
The current `parse_rdf` method in `FAIRChampionHarvester` uses:
|
|
20
|
+
```ruby
|
|
21
|
+
reader = formattype.reader.new(body)
|
|
22
|
+
meta.merge_rdf(reader.to_a)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This instantiates `JSON::LD::Reader` with **no processing options**, so:
|
|
26
|
+
- Remote `@context` URLs are **not fetched** (security/performance default)
|
|
27
|
+
- JSON-LD expansion rules are **not applied**
|
|
28
|
+
- `@type: @id` coercions don't happen
|
|
29
|
+
- Bare string IRIs stay as literals
|
|
30
|
+
|
|
31
|
+
### Real-World Impact
|
|
32
|
+
ESRF dataset metadata has:
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"@context": "http://schema.org",
|
|
36
|
+
"license": "https://creativecommons.org/licenses/by/4.0/legalcode"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Without context expansion → license becomes a literal string or blank node → FAIR license test fails with warning:
|
|
41
|
+
```
|
|
42
|
+
WARN: Found the Schema license predicate, but it does not have a Resource as its value.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Solution: Enable JSON-LD Expansion with Remote Context Resolution
|
|
48
|
+
|
|
49
|
+
### Key Code Decisions
|
|
50
|
+
|
|
51
|
+
**Decision 1: Format-specific handling**
|
|
52
|
+
- JSON-LD gets special treatment: use `JSON::LD::API.expand()` + `toRdf()` pipeline
|
|
53
|
+
- Other RDF formats (Turtle, N-Triples, RDF/XML) use existing reader pattern
|
|
54
|
+
- Rationale: Only JSON-LD needs context resolution; other formats are already RDF
|
|
55
|
+
|
|
56
|
+
**Decision 2: Processing options**
|
|
57
|
+
```ruby
|
|
58
|
+
processingOptions: {
|
|
59
|
+
processingMode: 'json-ld-1.1', # CRITICAL: 1.0 mode has weaker @type: @id semantics
|
|
60
|
+
documentLoader: JSON::LD::DocumentLoader.new # Explicitly enable remote context fetching
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
- `processingMode: 'json-ld-1.1'` ensures `@type: @id` is properly applied (1.0 had edge cases)
|
|
64
|
+
- `documentLoader.new` explicitly enables fetching of remote `@context` URIs (may be disabled by default)
|
|
65
|
+
|
|
66
|
+
**Decision 3: Graceful fallback**
|
|
67
|
+
- If remote context resolution fails (network, malformed URI), catch `JSON::LD::ProcessingError`
|
|
68
|
+
- Attempt parsing without expansion as fallback
|
|
69
|
+
- Log warning so operator knows context wasn't resolved
|
|
70
|
+
- Rationale: Metadata is still parseable as plain RDF even without context; better to get something than fail completely
|
|
71
|
+
|
|
72
|
+
**Decision 4: Cache interaction**
|
|
73
|
+
- Continue using existing `FAIRChampionHarvester::Cache` for non-JSON-LD formats
|
|
74
|
+
- For JSON-LD, cache the **raw body**, not the reader (reader is stateful, hard to serialize)
|
|
75
|
+
- After expansion, the RDF graph is cached via normal merge process
|
|
76
|
+
- Rationale: Keeps cache simple; re-expansion is cheap
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Implementation Strategy
|
|
81
|
+
|
|
82
|
+
### File to Modify
|
|
83
|
+
`lib/fair_champion_harvester/evaluator.rb` (or wherever `parse_rdf` method lives)
|
|
84
|
+
|
|
85
|
+
### Change Scope
|
|
86
|
+
- **Lines affected:** Reader instantiation block (approximately where `formattype.reader.new(body)` appears)
|
|
87
|
+
- **Methods touched:** `parse_rdf` static method
|
|
88
|
+
- **New requires:** `require 'json/ld'` (add at top of method or class)
|
|
89
|
+
- **No breaking changes:** Falls back to existing behavior if JSON-LD expansion fails
|
|
90
|
+
|
|
91
|
+
### Integration Checklist
|
|
92
|
+
- [ ] Locate `parse_rdf` method definition
|
|
93
|
+
- [ ] Add `require 'json/ld'` at method start or class level
|
|
94
|
+
- [ ] Replace simple reader pattern with format-aware conditional
|
|
95
|
+
- [ ] Test against ESRF metadata (original + expanded forms)
|
|
96
|
+
- [ ] Verify license triple is now IRI resource, not literal
|
|
97
|
+
- [ ] Run FAIR license assessment test — should pass or show nuanced result
|
|
98
|
+
- [ ] Check cache hit/miss behavior for JSON-LD documents
|
|
99
|
+
- [ ] Monitor for JSON::LD::ProcessingError in production logs
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Code Structure: What Gets Added
|
|
104
|
+
|
|
105
|
+
### Before (Current)
|
|
106
|
+
```ruby
|
|
107
|
+
reader = formattype.reader.new(body)
|
|
108
|
+
if reader.size == 0
|
|
109
|
+
# handle empty graph
|
|
110
|
+
end
|
|
111
|
+
reader = formattype.reader.new(body)
|
|
112
|
+
FAIRChampionHarvester::Cache.writeRDFCache(reader, body)
|
|
113
|
+
reader = formattype.reader.new(body)
|
|
114
|
+
meta.merge_rdf(reader.to_a)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### After (Proposed)
|
|
118
|
+
```ruby
|
|
119
|
+
if formattype.to_s.include?('JSON::LD')
|
|
120
|
+
# JSON-LD: expand with remote context resolution
|
|
121
|
+
json_body = JSON.parse(body)
|
|
122
|
+
|
|
123
|
+
expanded = JSON::LD::API.expand(json_body, processingOptions: {
|
|
124
|
+
processingMode: 'json-ld-1.1',
|
|
125
|
+
documentLoader: JSON::LD::DocumentLoader.new
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
graph = RDF::Graph.new
|
|
129
|
+
JSON::LD::API.toRdf(expanded) do |statement|
|
|
130
|
+
graph << statement
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
meta.merge_rdf(graph.to_a)
|
|
134
|
+
FAIRChampionHarvester::Cache.writeRDFCache(graph, body) # cache expanded RDF
|
|
135
|
+
else
|
|
136
|
+
# Turtle, N-Triples, RDF/XML, etc: use standard reader
|
|
137
|
+
reader = formattype.reader.new(body)
|
|
138
|
+
|
|
139
|
+
if reader.size == 0
|
|
140
|
+
meta.comments << "WARN: Though linked data was found, it failed to parse...\n"
|
|
141
|
+
return meta
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
reader = formattype.reader.new(body)
|
|
145
|
+
FAIRChampionHarvester::Cache.writeRDFCache(reader, body)
|
|
146
|
+
reader = formattype.reader.new(body)
|
|
147
|
+
meta.merge_rdf(reader.to_a)
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Error Handling
|
|
152
|
+
Wrap in `rescue JSON::LD::ProcessingError` to catch context resolution failures:
|
|
153
|
+
```ruby
|
|
154
|
+
rescue JSON::LD::ProcessingError => e
|
|
155
|
+
meta.comments << "WARN: JSON-LD context resolution failed: #{e.message}\n"
|
|
156
|
+
# Try fallback: parse without expansion
|
|
157
|
+
begin
|
|
158
|
+
reader = formattype.reader.new(body)
|
|
159
|
+
meta.merge_rdf(reader.to_a)
|
|
160
|
+
meta.comments << "WARN: Parsed JSON-LD without remote context resolution. Results may be incomplete.\n"
|
|
161
|
+
rescue => fallback_error
|
|
162
|
+
meta.comments << "CRITICAL: JSON-LD parsing failed even without context expansion: #{fallback_error.message}\n"
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Validation: How to Test the Fix
|
|
169
|
+
|
|
170
|
+
### Unit Test: ESRF Metadata
|
|
171
|
+
```ruby
|
|
172
|
+
# Test with the original ESRF JSON-LD
|
|
173
|
+
json_body = JSON.parse(File.read('spec/fixtures/esrf_dataset.jsonld'))
|
|
174
|
+
|
|
175
|
+
expanded = JSON::LD::API.expand(json_body, processingOptions: {
|
|
176
|
+
processingMode: 'json-ld-1.1',
|
|
177
|
+
documentLoader: JSON::LD::DocumentLoader.new
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
graph = RDF::Graph.new
|
|
181
|
+
JSON::LD::API.toRdf(expanded) { |stmt| graph << stmt }
|
|
182
|
+
|
|
183
|
+
# Check license triple
|
|
184
|
+
license_triples = graph.query([nil, RDF::URI('http://schema.org/license'), nil])
|
|
185
|
+
license_triples.each do |stmt|
|
|
186
|
+
expect(stmt.object.resource?).to be true
|
|
187
|
+
expect(stmt.object.anonymous?).to be false
|
|
188
|
+
expect(stmt.object.to_s).to eq 'https://creativecommons.org/licenses/by/4.0/legalcode'
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Integration Test: FAIR Assessment
|
|
193
|
+
Run your existing FAIR license test against the fixed harvester:
|
|
194
|
+
```ruby
|
|
195
|
+
result = FAIRChampionHarvester.evaluate('https://doi.org/10.15151/esrf-es-2210534378')
|
|
196
|
+
|
|
197
|
+
# Should now pass (or at least not complain about literal value)
|
|
198
|
+
expect(result.output.score).to eq 'pass'
|
|
199
|
+
expect(result.output.comments).to include('SUCCESS: Found the Schema license predicate with a Resource')
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Diagnostic Output
|
|
203
|
+
Add debug logging to trace expansion:
|
|
204
|
+
```ruby
|
|
205
|
+
warn "JSON-LD Expansion: Input has #{json_body.keys.length} root keys"
|
|
206
|
+
warn "JSON-LD Expansion: Context is #{json_body['@context']}"
|
|
207
|
+
expanded = JSON::LD::API.expand(json_body, processingOptions: {...})
|
|
208
|
+
warn "JSON-LD Expansion: Output has #{expanded.length} objects"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Performance Considerations
|
|
214
|
+
|
|
215
|
+
### Remote Context Fetching
|
|
216
|
+
- **Latency:** First fetch of `http://schema.org` context takes ~200-500ms
|
|
217
|
+
- **Caching:** Ruby's `json-ld` gem caches fetched contexts in memory — subsequent documents are fast
|
|
218
|
+
- **Network:** Single HTTP GET per unique `@context` URL per process lifetime
|
|
219
|
+
- **Recommendation:** OK for production; consider warm-up if processing high volume at startup
|
|
220
|
+
|
|
221
|
+
### JSON-LD Expansion
|
|
222
|
+
- **CPU:** Expansion is O(n) in document size; typically <50ms for datasets
|
|
223
|
+
- **Memory:** Expanded form is usually 1.5-2x original size
|
|
224
|
+
- **Recommendation:** Acceptable; RDF graph construction is already expensive
|
|
225
|
+
|
|
226
|
+
### Cache Strategy
|
|
227
|
+
- Cache the **expanded RDF graph**, not the raw JSON-LD
|
|
228
|
+
- This way, subsequent accesses skip expansion
|
|
229
|
+
- For documents with same `@context`, expansion is cached by `json-ld` gem anyway
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Known Limitations & Edge Cases
|
|
234
|
+
|
|
235
|
+
### 1. Custom or Malformed Contexts
|
|
236
|
+
If a document's `@context` URL returns 404 or invalid JSON, expansion will fail.
|
|
237
|
+
- **Mitigation:** Fallback to parsing without expansion (implemented in error handler)
|
|
238
|
+
- **Log:** Warning message indicates context couldn't be resolved
|
|
239
|
+
|
|
240
|
+
### 2. Blank Nodes vs. Named Resources
|
|
241
|
+
Schema.org allows some properties to be blank nodes. Example: a license as a `CreativeWork` object.
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"license": {
|
|
245
|
+
"@type": "CreativeWork",
|
|
246
|
+
"name": "CC-BY",
|
|
247
|
+
"url": "https://creativecommons.org/licenses/by/4.0/"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
- **Current test logic:** Rejects blank nodes (`!object.anonymous?`)
|
|
252
|
+
- **Better approach:** Accept blank nodes IF they have properties; only fail on bare string literals
|
|
253
|
+
- **Future refinement:** Update license test to differentiate:
|
|
254
|
+
- Named resource IRI (best)
|
|
255
|
+
- Anonymous resource with properties (good)
|
|
256
|
+
- String literal (warn)
|
|
257
|
+
|
|
258
|
+
### 3. Context Conflicts
|
|
259
|
+
If document has both `"@context": "http://schema.org"` and inline context definitions, inline wins.
|
|
260
|
+
- **Expected behavior:** This is JSON-LD spec-compliant
|
|
261
|
+
- **No action needed:** Rare in practice
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Debugging Tips for Claude Code Session
|
|
266
|
+
|
|
267
|
+
### If License Still Appears as Literal
|
|
268
|
+
1. Check if `JSON::LD::API.expand()` is actually being called
|
|
269
|
+
- Add `warn "Expanding JSON-LD with schema.org context"`
|
|
270
|
+
2. Verify documentLoader is enabled:
|
|
271
|
+
- Try adding `documentLoader: JSON::LD::DocumentLoader.new` explicitly
|
|
272
|
+
3. Check if remote context is actually fetching:
|
|
273
|
+
- Try: `context = JSON.parse(Net::HTTP.get(URI('https://schema.org/docs/jsonldcontext.json')))`
|
|
274
|
+
- If that times out, network issue; if it parses, `json-ld` gem should too
|
|
275
|
+
|
|
276
|
+
### If Blank Nodes Still Appear
|
|
277
|
+
- Check if license is nested structure: `{ "@type": "CreativeWork", ... }`
|
|
278
|
+
- If so, that's valid RDF; update FAIR test to accept anonymous resources with properties
|
|
279
|
+
- If it's a bare blank node with no properties, something is still wrong with expansion
|
|
280
|
+
|
|
281
|
+
### If Context Resolution Fails
|
|
282
|
+
- Check gem version: `bundle show json-ld`
|
|
283
|
+
- Ensure `json-ld >= 3.2.0` (older versions had weaker context support)
|
|
284
|
+
- Try manually: `JSON::LD::API.expand(json_body, processingOptions: { documentLoader: JSON::LD::DocumentLoader.new })`
|
|
285
|
+
- Check network: Can you reach `http://schema.org` from the runtime environment?
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Commit Message Template
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
Fix: Enable JSON-LD context expansion for remote @context resolution
|
|
293
|
+
|
|
294
|
+
- Add processingMode: 'json-ld-1.1' to apply @type: @id coercions
|
|
295
|
+
- Enable documentLoader for remote context fetching (schema.org, etc.)
|
|
296
|
+
- Separate JSON-LD handling from other RDF formats
|
|
297
|
+
- Add graceful fallback if context resolution fails
|
|
298
|
+
- Resolves: License properties now correctly expanded as IRI resources
|
|
299
|
+
- Tested against: ESRF dataset metadata with schema.org context
|
|
300
|
+
|
|
301
|
+
Fixes #ISSUE_NUMBER (if applicable)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## References
|
|
307
|
+
|
|
308
|
+
- **JSON-LD 1.1 Spec:** https://www.w3.org/TR/json-ld11/
|
|
309
|
+
- **schema.org context:** https://schema.org/docs/jsonldcontext.json
|
|
310
|
+
- **ruby json-ld gem:** https://github.com/ruby-rdf/json-ld
|
|
311
|
+
- **schema.org license property:** https://schema.org/license
|
|
312
|
+
- **FAIR license assessment:** Your FAIR test queries for `http://schema.org/license` with `@type: @id`
|
|
313
|
+
|
data/lib/harvester.rb
CHANGED
|
@@ -148,38 +148,80 @@ module FAIRChampionHarvester
|
|
|
148
148
|
return meta
|
|
149
149
|
end
|
|
150
150
|
meta.comments << "INFO: The response message body component appears to contain #{formattype}.\n"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
151
|
+
if formattype.to_s.include?('JSON::LD')
|
|
152
|
+
# JSON-LD: expand with remote context resolution to correctly apply @type: @id coercions
|
|
153
|
+
# (e.g. schema.org "license" coerced to IRI resource instead of string literal)
|
|
154
|
+
# json/ld/preloaded provides schema.org and other common contexts without network calls
|
|
155
|
+
begin
|
|
156
|
+
json_body = JSON.parse(body)
|
|
157
|
+
context_hint = json_body.is_a?(Array) ? json_body.first&.[]('@context') : json_body['@context']
|
|
158
|
+
warn "JSON-LD Expansion: Context is #{context_hint}"
|
|
159
|
+
|
|
160
|
+
expanded = JSON::LD::API.expand(json_body, processingMode: 'json-ld-1.1')
|
|
161
|
+
warn "JSON-LD Expansion: Output has #{expanded.length} objects"
|
|
162
|
+
|
|
163
|
+
graph = RDF::Graph.new
|
|
164
|
+
JSON::LD::API.toRdf(expanded) { |statement| graph << statement }
|
|
165
|
+
|
|
166
|
+
if graph.size == 0
|
|
167
|
+
meta.comments << "WARN: Though linked data was found, it failed to parse. This likely indicates some syntax error in the data. As a result, no metadata will be extracted from this message.\n"
|
|
168
|
+
return meta
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
meta.comments << "INFO: JSON-LD context resolved and expanded successfully.\n"
|
|
172
|
+
FAIRChampionHarvester::Cache.writeRDFCache(graph, body)
|
|
173
|
+
meta.merge_rdf(graph.to_a)
|
|
174
|
+
|
|
175
|
+
rescue JSON::LD::JsonLdError => e
|
|
176
|
+
meta.comments << "WARN: JSON-LD context resolution failed: #{e.message}. Attempting parse without expansion.\n"
|
|
177
|
+
warn "JSON-LD JsonLdError: #{e.message}"
|
|
178
|
+
begin
|
|
179
|
+
reader = formattype.reader.new(body)
|
|
180
|
+
meta.merge_rdf(reader.to_a)
|
|
181
|
+
meta.comments << "WARN: Parsed JSON-LD without context expansion. Results may be incomplete.\n"
|
|
182
|
+
rescue => fallback_error
|
|
183
|
+
meta.comments << "CRITICAL: JSON-LD parsing failed even without context expansion: #{fallback_error.message}\n"
|
|
184
|
+
return meta
|
|
185
|
+
end
|
|
186
|
+
rescue StandardError => e
|
|
162
187
|
meta.comments << "WARN: Though linked data was found, it failed to parse. This likely indicates some syntax error in the data. As a result, no metadata will be extracted from this message.\n"
|
|
188
|
+
warn "JSON-LD StandardError: #{e.message}"
|
|
163
189
|
return meta
|
|
164
190
|
end
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
else
|
|
192
|
+
reader = ""
|
|
193
|
+
begin
|
|
194
|
+
reader = formattype.reader.new(body)
|
|
195
|
+
rescue StandardError
|
|
196
|
+
meta.comments << "WARN: Though linked data was found, it failed to parse. This likely indicates some syntax error in the data. As a result, no metadata will be extracted from this message.\n"
|
|
197
|
+
return meta
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
begin
|
|
201
|
+
# $stderr.puts "Reader Class #{reader.class}\n\n #{reader.inspect}"
|
|
202
|
+
if reader.size == 0
|
|
203
|
+
meta.comments << "WARN: Though linked data was found, it failed to parse. This likely indicates some syntax error in the data. As a result, no metadata will be extracted from this message.\n"
|
|
204
|
+
return meta
|
|
205
|
+
end
|
|
206
|
+
# reader.rewind!
|
|
207
|
+
# for some reason, the rewind method isn't working here...??
|
|
208
|
+
reader = formattype.reader.new(body) # have to re-read it here, but now its safe because we have already caught errors
|
|
209
|
+
warn "WRITING TO CACHE"
|
|
210
|
+
FAIRChampionHarvester::Cache.writeRDFCache(reader, body) # write to the special RDF graph cache
|
|
211
|
+
warn "WRITING DONE"
|
|
212
|
+
reader = formattype.reader.new(body)
|
|
213
|
+
warn "RE-READING DONE"
|
|
214
|
+
meta.merge_rdf(reader.to_a)
|
|
215
|
+
warn "MERGE DONE"
|
|
216
|
+
rescue RDF::ReaderError => e
|
|
217
|
+
meta.comments << "CRITICAL: The Linked Data was malformed and caused the parser to crash with error message: #{e.message} || (sample of what was parsed: #{body[0..300].delete("\n")})\n"
|
|
218
|
+
warn "CRITICAL: The Linked Data was malformed and caused the parser to crash with error message: #{e.message} || (sample of what was parsed: #{body[0..300].delete("\n")})\n"
|
|
219
|
+
nil
|
|
220
|
+
rescue Exception => e
|
|
221
|
+
meta.comments << "CRITICAL: An unknown error occurred while parsing the (apparent) Linked Data (sample of what was parsed: #{body[0..300].delete("\n")}). Moving on...\n"
|
|
222
|
+
warn "\n\nCRITICAL: #{e.inspect} An unknown error occurred while parsing the (apparent) Linked Data (full body: #{body}). Moving on...\n\n"
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
183
225
|
end
|
|
184
226
|
end
|
|
185
227
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fair_champion_harvester
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- markwilkinson
|
|
@@ -301,6 +301,7 @@ files:
|
|
|
301
301
|
- lib/doi.rb
|
|
302
302
|
- lib/extruct.rb
|
|
303
303
|
- lib/fair_champion_harvester.rb
|
|
304
|
+
- lib/fair_champion_harvester/handoff.md
|
|
304
305
|
- lib/fair_champion_harvester/version.rb
|
|
305
306
|
- lib/fairsharing.rb
|
|
306
307
|
- lib/handle.rb
|