@readium/shared
Version:
Shared models to be used across other Readium projects and implementations in Typescript
129 lines (111 loc) • 4.16 kB
text/typescript
import { LocatorLocations } from '../Locator';
import { DomRange } from './DomRange';
// HTML extensions for [Locations].
// https://github.com/readium/architecture/blob/master/models/locators/extensions/html.md
declare module '../Locator' {
export interface LocatorLocations {
/**
* A CSS Selector.
*/
getCssSelector(): string | undefined;
/**
* [partialCfi] is an expression conforming to the "right-hand" side of the EPUB CFI syntax, that is
* to say: without the EPUB-specific OPF spine item reference that precedes the first ! exclamation
* mark (which denotes the "step indirection" into a publication document). Note that the wrapping
* epubcfi(***) syntax is not used for the [partialCfi] string, i.e. the "fragment" part of the CFI
* grammar is ignored.
*/
getPartialCfi(): string | undefined;
/**
* An HTML DOM range.
*/
getDomRange(): DomRange | undefined;
/**
* All named parameters found in the fragments, such as `p=5`.
*/
fragmentParameters(): Map<string, string>;
/**
* HTML ID fragment identifier.
*/
htmlId(): string | undefined;
/**
* Page fragment identifier, used for example in PDF.
*/
page(): number | undefined;
/**
* Temporal Dimension media fragment, used for example in audiobooks.
*
* https://www.w3.org/TR/media-frags/
*/
time(): number | undefined;
/**
* Spatial Dimension media fragment, used for example in audiobooks.
*
* https://www.w3.org/TR/media-frags/
*/
space(): [number, number, number, number] | undefined;
}
}
LocatorLocations.prototype.getCssSelector = function(): string | undefined {
return this.otherLocations?.get('cssSelector');
};
LocatorLocations.prototype.getPartialCfi = function(): string | undefined {
return this.otherLocations?.get('partialCfi');
};
LocatorLocations.prototype.getDomRange = function(): DomRange | undefined {
return DomRange.deserialize(this.otherLocations?.get('domRange'));
};
// Below is technically part of the Navigator's Locator extensions, we're putting it here for now
// Reference: https://www.w3.org/TR/fragid-best-practices
LocatorLocations.prototype.fragmentParameters = function(): Map<string, string> {
return new Map(
this.fragments
// Concatenates fragments together, after dropping any #
.map(f => f.startsWith("#") ? f.slice(1) : f)
.join("&")
// Splits parameters
.split("&")
.filter(f => !f.startsWith("#"))
.map(f => f.split("="))
// Only keep named parameters
.filter(f => f.length === 2)
.map(f => [
f[0].trim().toLowerCase(),
f[1].trim()
])
);
};
LocatorLocations.prototype.htmlId = function(): string | undefined {
/*
The HTML 5 specification (used for WebPub) allows any character in an HTML ID, except spaces.
This is an issue to differentiate with named parameters, so we ignore any ID containing `=`.
*/
if(!this.fragments.length) return;
let f = this.fragments.find(f => f.length && !f.includes("="));
if(!f) {
const fp = this.fragmentParameters();
if(fp.has("id")) f = fp.get("id");
else if(fp.has("name")) f = fp.get("name");
}
return f?.startsWith("#") ? f.slice(1) : f;
};
LocatorLocations.prototype.page = function(): number | undefined {
const i = parseInt(this.fragmentParameters().get("page")!);
if(!isNaN(i) && i >= 0) return i;
return undefined;
}
LocatorLocations.prototype.time = function(): number | undefined {
// TODO more sophiticated parsing
const i = parseInt(this.fragmentParameters().get("t")!);
if(!isNaN(i)) return i;
return undefined;
}
LocatorLocations.prototype.space = function(): [number, number, number, number] | undefined {
const fp = this.fragmentParameters();
if(!fp.has("xywh")) return;
// TODO more sophiticated parsing to handle the format
const xywh = fp.get("xywh")!.split(",").map(s => parseInt(s));
if(xywh.length !== 4) return; // Must have four parts
if(xywh.some(isNaN)) return; // All parts must be numbers
return xywh as [number, number, number, number];
}