-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expand VitalSource chapter selections to end of HTML document
Fix an issue where selections in the VitalSource book picker could create assignments with empty page ranges, resulting in a warning being shown about being outside of the assignment regardless of the current page. When determining the endpoint of the CFI range for a selected chapter, find the next CFI which is at a level equal to or above that of the selected entry _and_ refers to a different PDF page or HTML document. Given a TOC structure like this: ``` - Chapter 1 (CFI: /2!/4, Page 2) - Chapter 1 - Section 1 (CFI: /2!/6, Page 3) - Chapter 1 - Section 12 (CFI: /2!/8, Page 4) - Chapter 2 (CFI: /4!/2, Page 5) ``` If the user selects eg. "Chapter 1 - Section 1" the selected CFI range was previously `/2!/6-/2!/8` but is now `/2!/6-/4!/2`. The page range shown in the UI would previously have been `3-4` and is now `3-5`. The reason for doing this is that the Hypothesis client discards the part of the CFIs after the "!" and treats the endpoint of the stripped CFI as exclusive. Therefore `/2!/6-/2!/8` is an empty range whereas `/2!/6-/4!/2` is a range containing the whole of the document identified by `/2`. The reason why the Hypothesis client discards the part after the "!" is because the minimum granularity at which it operates is a single HTML document or PDF page, identified by the part before the "!". Fixes hypothesis/support#153.
- Loading branch information
1 parent
9a12803
commit 8b4c583
Showing
4 changed files
with
253 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* Functions for working with EPUB Canonical Fragment Identifiers. | ||
* | ||
* See https://idpf.org/epub/linking/cfi/. | ||
*/ | ||
|
||
/** | ||
* Strip assertions from a Canonical Fragment Identifier. | ||
* | ||
* Assertions are `[...]` enclosed sections which act as checks on the validity | ||
* of numbers but do not affect the sort order. | ||
* | ||
* @example | ||
* stripCFIAssertions("/6/14[chap05ref]") // returns "/6/14" | ||
*/ | ||
export function stripCFIAssertions(cfi: string): string { | ||
// Fast path for CFIs with no assertions. | ||
if (!cfi.includes('[')) { | ||
return cfi; | ||
} | ||
|
||
let result = ''; | ||
|
||
// Has next char been escaped? | ||
let escaped = false; | ||
|
||
// Are we in a `[...]` assertion section? | ||
let inAssertion = false; | ||
|
||
for (const ch of cfi) { | ||
if (!escaped && ch === '^') { | ||
escaped = true; | ||
continue; | ||
} | ||
|
||
if (!escaped && ch === '[') { | ||
inAssertion = true; | ||
} else if (!escaped && inAssertion && ch === ']') { | ||
inAssertion = false; | ||
} else if (!inAssertion) { | ||
result += ch; | ||
} | ||
|
||
escaped = false; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Return a slice of `cfi` up to the first step indirection [1], with assertions | ||
* removed. | ||
* | ||
* A typical CFI consists of a path within the table of contents to indicate | ||
* a content document, a step indirection ("!"), then the path of an element | ||
* within the content document. For such a CFI, this function will retain only | ||
* the content document path. | ||
* | ||
* [1] https://idpf.org/epub/linking/cfi/#sec-path-indirection | ||
* | ||
* @example | ||
* documentCFI('/6/152[;vnd.vst.idref=ch13_01]!/4/2[ch13_sec_1]') // Returns "/6/152" | ||
*/ | ||
export function documentCFI(cfi: string): string { | ||
const stripped = stripCFIAssertions(cfi); | ||
const sepIndex = stripped.indexOf('!'); | ||
return sepIndex === -1 ? stripped : stripped.slice(0, sepIndex); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { documentCFI, stripCFIAssertions } from '../cfi'; | ||
|
||
describe('cfi', () => { | ||
describe('stripCFIAssertions', () => { | ||
it('returns CFI without assertions unchanged', () => { | ||
assert.equal(stripCFIAssertions('/1/2/3/10'), '/1/2/3/10'); | ||
}); | ||
|
||
it('removes assertions from CFI', () => { | ||
assert.equal(stripCFIAssertions('/1/2[chap4ref]'), '/1/2'); | ||
assert.equal( | ||
stripCFIAssertions('/1[part1ref]/2[chapter2ref]/3[subsectionref]'), | ||
'/1/2/3', | ||
); | ||
}); | ||
|
||
it('ignores escaped characters', () => { | ||
assert.equal(stripCFIAssertions('/1/2[chap4^[ignoreme^]ref]'), '/1/2'); | ||
assert.equal(stripCFIAssertions('/1/2[a^[b^]]/3[c^[d^]]'), '/1/2/3'); | ||
}); | ||
}); | ||
|
||
describe('documentCFI', () => { | ||
it('returns part of CFI before first step indirection', () => { | ||
// Typical CFI with one step indirection. | ||
assert.equal(documentCFI('/2/4/8!/10/12'), '/2/4/8'); | ||
|
||
// Rarer case of CFI with multiple step indirections. | ||
assert.equal(documentCFI('/2/4/8!/10/12!/2/4'), '/2/4/8'); | ||
}); | ||
|
||
it('strips assertions', () => { | ||
assert.equal( | ||
documentCFI('/6/152[;vnd.vst.idref=ch13_01]!/4/2[ch13_sec_1]'), | ||
'/6/152', | ||
); | ||
}); | ||
}); | ||
}); |