@readium/shared 1.2.0
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.
- package/LICENSE +28 -0
- package/README.MD +27 -0
- package/dist/index.js +2536 -0
- package/dist/index.umd.cjs +2 -0
- package/package.json +73 -0
- package/src/fetcher/Fetcher.ts +37 -0
- package/src/fetcher/HttpFetcher.ts +122 -0
- package/src/fetcher/Resource.ts +37 -0
- package/src/fetcher/index.ts +2 -0
- package/src/index.ts +4 -0
- package/src/opds/Acquisition.ts +54 -0
- package/src/opds/Availability.ts +61 -0
- package/src/opds/Copies.ts +43 -0
- package/src/opds/Holds.ts +43 -0
- package/src/opds/Price.ts +51 -0
- package/src/opds/index.ts +5 -0
- package/src/publication/BelongsTo.ts +51 -0
- package/src/publication/Contributor.ts +129 -0
- package/src/publication/GuidedNavigation.ts +176 -0
- package/src/publication/Link.ts +332 -0
- package/src/publication/LocalizedString.ts +74 -0
- package/src/publication/Locator.ts +225 -0
- package/src/publication/LocatorCollection.ts +133 -0
- package/src/publication/Manifest.ts +256 -0
- package/src/publication/Metadata.ts +329 -0
- package/src/publication/Properties.ts +44 -0
- package/src/publication/Publication.ts +148 -0
- package/src/publication/PublicationCollection.ts +144 -0
- package/src/publication/ReadingProgression.ts +28 -0
- package/src/publication/Subject.ts +115 -0
- package/src/publication/encryption/Encryption.ts +68 -0
- package/src/publication/encryption/Properties.ts +20 -0
- package/src/publication/encryption/index.ts +2 -0
- package/src/publication/epub/EPUBLayout.ts +10 -0
- package/src/publication/epub/Presentation.ts +16 -0
- package/src/publication/epub/Properties.ts +28 -0
- package/src/publication/epub/Publication.ts +51 -0
- package/src/publication/epub/index.ts +4 -0
- package/src/publication/html/DomRange.ts +63 -0
- package/src/publication/html/DomRangePoint.ts +64 -0
- package/src/publication/html/Locations.ts +129 -0
- package/src/publication/html/index.ts +3 -0
- package/src/publication/index.ts +19 -0
- package/src/publication/opds/Properties.ts +84 -0
- package/src/publication/opds/Publication.ts +14 -0
- package/src/publication/opds/index.ts +2 -0
- package/src/publication/presentation/Metadata.ts +19 -0
- package/src/publication/presentation/Presentation.ts +155 -0
- package/src/publication/presentation/Properties.ts +70 -0
- package/src/publication/presentation/index.ts +3 -0
- package/src/publication/services/content/Content.ts +36 -0
- package/src/publication/services/content/ContentTokenizer.ts +62 -0
- package/src/publication/services/content/Iterator.ts +46 -0
- package/src/publication/services/content/element/attributes.ts +44 -0
- package/src/publication/services/content/element/element.ts +118 -0
- package/src/publication/services/content/element/index.ts +3 -0
- package/src/publication/services/content/element/text_role.ts +60 -0
- package/src/publication/services/content/index.ts +5 -0
- package/src/publication/services/content/iterators/HTMLResourceContentIterator.ts +571 -0
- package/src/publication/services/content/iterators/PDFTextContentIterator.ts +184 -0
- package/src/publication/services/content/iterators/PublicationContentIterator.ts +144 -0
- package/src/publication/services/content/iterators/helpers.ts +146 -0
- package/src/publication/services/content/iterators/index.ts +3 -0
- package/src/publication/services/index.ts +1 -0
- package/src/util/JSONParse.ts +31 -0
- package/src/util/Language.ts +5 -0
- package/src/util/URITemplate.ts +83 -0
- package/src/util/index.ts +5 -0
- package/src/util/mediatype/MediaType.ts +563 -0
- package/src/util/mediatype/index.ts +1 -0
- package/src/util/tokenizer/TextTokenizer.ts +126 -0
- package/src/util/tokenizer/Tokenizer.ts +8 -0
- package/src/util/tokenizer/index.ts +2 -0
- package/src/util/tokenizer/tokenize-english/README.md +7 -0
- package/src/util/tokenizer/tokenize-english/abbreviations.js +52 -0
- package/src/util/tokenizer/tokenize-english/index.d.ts +3 -0
- package/src/util/tokenizer/tokenize-english/index.js +243 -0
- package/src/util/tokenizer/tokenize-english/utils.js +16 -0
- package/src/util/tokenizer/tokenize-text/README.MD +6 -0
- package/src/util/tokenizer/tokenize-text/index.d.ts +52 -0
- package/src/util/tokenizer/tokenize-text/index.js +256 -0
- package/src/util/tokenizer/tokenize-text/tokens.js +100 -0
- package/types/src/fetcher/Fetcher.d.ts +27 -0
- package/types/src/fetcher/HttpFetcher.d.ts +28 -0
- package/types/src/fetcher/Resource.d.ts +15 -0
- package/types/src/fetcher/index.d.ts +2 -0
- package/types/src/index.d.ts +4 -0
- package/types/src/opds/Acquisition.d.ts +26 -0
- package/types/src/opds/Availability.d.ts +32 -0
- package/types/src/opds/Copies.d.ts +22 -0
- package/types/src/opds/Holds.d.ts +22 -0
- package/types/src/opds/Price.d.ts +29 -0
- package/types/src/opds/index.d.ts +5 -0
- package/types/src/publication/BelongsTo.d.ts +23 -0
- package/types/src/publication/Contributor.d.ts +60 -0
- package/types/src/publication/GuidedNavigation.d.ts +70 -0
- package/types/src/publication/Link.d.ts +128 -0
- package/types/src/publication/LocalizedString.d.ts +48 -0
- package/types/src/publication/Locator.d.ts +101 -0
- package/types/src/publication/LocatorCollection.d.ts +54 -0
- package/types/src/publication/Manifest.d.ts +59 -0
- package/types/src/publication/Metadata.d.ts +101 -0
- package/types/src/publication/Properties.d.ts +28 -0
- package/types/src/publication/Publication.d.ts +53 -0
- package/types/src/publication/PublicationCollection.d.ts +34 -0
- package/types/src/publication/ReadingProgression.d.ts +9 -0
- package/types/src/publication/Subject.d.ts +56 -0
- package/types/src/publication/encryption/Encryption.d.ts +33 -0
- package/types/src/publication/encryption/Properties.d.ts +10 -0
- package/types/src/publication/encryption/index.d.ts +2 -0
- package/types/src/publication/epub/EPUBLayout.d.ts +5 -0
- package/types/src/publication/epub/Presentation.d.ts +7 -0
- package/types/src/publication/epub/Properties.d.ts +14 -0
- package/types/src/publication/epub/Publication.d.ts +19 -0
- package/types/src/publication/epub/index.d.ts +4 -0
- package/types/src/publication/html/DomRange.d.ts +40 -0
- package/types/src/publication/html/DomRangePoint.d.ts +36 -0
- package/types/src/publication/html/Locations.d.ts +45 -0
- package/types/src/publication/html/index.d.ts +3 -0
- package/types/src/publication/index.d.ts +19 -0
- package/types/src/publication/opds/Properties.d.ts +38 -0
- package/types/src/publication/opds/Publication.d.ts +6 -0
- package/types/src/publication/opds/index.d.ts +2 -0
- package/types/src/publication/presentation/Metadata.d.ts +6 -0
- package/types/src/publication/presentation/Presentation.d.ts +95 -0
- package/types/src/publication/presentation/Properties.d.ts +32 -0
- package/types/src/publication/presentation/index.d.ts +3 -0
- package/types/src/publication/services/content/Content.d.ts +20 -0
- package/types/src/publication/services/content/ContentTokenizer.d.ts +19 -0
- package/types/src/publication/services/content/Iterator.d.ts +38 -0
- package/types/src/publication/services/content/element/attributes.d.ts +31 -0
- package/types/src/publication/services/content/element/element.d.ts +107 -0
- package/types/src/publication/services/content/element/index.d.ts +3 -0
- package/types/src/publication/services/content/element/text_role.d.ts +53 -0
- package/types/src/publication/services/content/index.d.ts +5 -0
- package/types/src/publication/services/content/iterators/HTMLResourceContentIterator.d.ts +30 -0
- package/types/src/publication/services/content/iterators/PDFTextContentIterator.d.ts +60 -0
- package/types/src/publication/services/content/iterators/PublicationContentIterator.d.ts +59 -0
- package/types/src/publication/services/content/iterators/helpers.d.ts +10 -0
- package/types/src/publication/services/content/iterators/index.d.ts +3 -0
- package/types/src/publication/services/index.d.ts +1 -0
- package/types/src/util/JSONParse.d.ts +11 -0
- package/types/src/util/Language.d.ts +1 -0
- package/types/src/util/URITemplate.d.ts +24 -0
- package/types/src/util/index.d.ts +5 -0
- package/types/src/util/mediatype/MediaType.d.ts +148 -0
- package/types/src/util/mediatype/index.d.ts +1 -0
- package/types/src/util/tokenizer/TextTokenizer.d.ts +39 -0
- package/types/src/util/tokenizer/Tokenizer.d.ts +6 -0
- package/types/src/util/tokenizer/index.d.ts +2 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Locator } from "../../../Locator";
|
|
2
|
+
import { Attribute, ContentElement } from "../element";
|
|
3
|
+
import { IllegalStateError, Iterator } from "../Iterator";
|
|
4
|
+
|
|
5
|
+
// Type TextItem represents a text item in a PDF page (extracted from pdf.js TextItem type).
|
|
6
|
+
type TextItem = {
|
|
7
|
+
str: string;
|
|
8
|
+
dir: string;
|
|
9
|
+
locator: Locator;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A pdf element.
|
|
14
|
+
*/
|
|
15
|
+
export class PDFContentElement extends ContentElement {
|
|
16
|
+
constructor(
|
|
17
|
+
locator: Locator,
|
|
18
|
+
/**
|
|
19
|
+
* Readable text.
|
|
20
|
+
*/
|
|
21
|
+
readonly element: PDFReadableElement,
|
|
22
|
+
attributes: Attribute<any>[] = [],
|
|
23
|
+
) { super(locator, attributes); }
|
|
24
|
+
|
|
25
|
+
get text(): string | undefined {
|
|
26
|
+
return this.element.text;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Element in a PDF resource page.
|
|
31
|
+
export type PDFReadableElement = {
|
|
32
|
+
index: number,
|
|
33
|
+
text: string;
|
|
34
|
+
readingDirection?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
enum Direction {
|
|
38
|
+
Forward = 1,
|
|
39
|
+
Backward = -1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// [Element] loaded with [hasPrevious] or [hasNext], associated with the move direction.
|
|
43
|
+
class ElementInDirection {
|
|
44
|
+
constructor(
|
|
45
|
+
readonly element: ContentElement,
|
|
46
|
+
readonly direction: Direction
|
|
47
|
+
) {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* An iterator which iterates through a whole PDF page provided,
|
|
51
|
+
and returns the elements based on a [PDFReadableElement] array. */
|
|
52
|
+
|
|
53
|
+
// TODO extend Iterator class!
|
|
54
|
+
export class PageContentIteratorPDF extends Iterator{
|
|
55
|
+
private pageContentFiltered: Array<PDFContentElement> = [];
|
|
56
|
+
private currentElement: ElementInDirection | null = null;
|
|
57
|
+
private currentProgression: number = 0;
|
|
58
|
+
private currentElementIndex: number = 0;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
/**
|
|
62
|
+
* The pdf page, represented as array of [TextItem] which will be iterated through.
|
|
63
|
+
* @param {Array<TextItem>} pageContent - an array that represents the page content of a PDF page.
|
|
64
|
+
* @param {Locator} startLocator - (optional) the locator of the first element to be read.
|
|
65
|
+
*/
|
|
66
|
+
private pageContent: Array<TextItem>,
|
|
67
|
+
private startLocator?: Locator | null | undefined
|
|
68
|
+
) {
|
|
69
|
+
super();
|
|
70
|
+
const contentFiltered = this.pageContent.filter(
|
|
71
|
+
(item: TextItem) => item?.str !== " " && item?.str !== ""
|
|
72
|
+
) as Array<TextItem>;
|
|
73
|
+
|
|
74
|
+
this.pageContentFiltered = contentFiltered.map((item) => {
|
|
75
|
+
return new PDFContentElement(item.locator, {
|
|
76
|
+
index: contentFiltered.indexOf(item),
|
|
77
|
+
text: item.str,
|
|
78
|
+
readingDirection: item.dir
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (this.startLocator) {
|
|
83
|
+
this.currentProgression = this.startLocator?.locations?.progression || 0;
|
|
84
|
+
this.updateElementIndex();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.pageContentFiltered.length > 0) {
|
|
88
|
+
const index = this.currentElementIndex;
|
|
89
|
+
|
|
90
|
+
if (index || index === 0) {
|
|
91
|
+
this.currentElement = new ElementInDirection(this.pageContentFiltered[index], Direction.Forward);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
// Returns the current element to be read.
|
|
98
|
+
current(): ContentElement | null {
|
|
99
|
+
return this.currentElement ? this.currentElement.element : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async hasPrevious(): Promise<boolean> {
|
|
103
|
+
this.currentElement?.direction === Direction.Backward;
|
|
104
|
+
return this.nextIn(Direction.Backward) !== null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
previous(): ContentElement {
|
|
108
|
+
|
|
109
|
+
if (!this.currentElement) throw new IllegalStateError("Called next() without a successful call to hasNext() first");
|
|
110
|
+
|
|
111
|
+
this.currentElement = this.nextIn(Direction.Backward);
|
|
112
|
+
|
|
113
|
+
if (this.currentElementIndex && this.currentElementIndex > 0) {
|
|
114
|
+
const newIndex = this.currentElementIndex - 1;
|
|
115
|
+
this.currentProgression = newIndex / this.pageContentFiltered.length * 100;
|
|
116
|
+
this.currentElementIndex = newIndex;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return this.currentElement ?
|
|
120
|
+
this.currentElement.element :
|
|
121
|
+
new PDFContentElement(new Locator( { href : "", type: "" }), { index: -1, text: "" });
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async hasNext(): Promise<boolean> {
|
|
126
|
+
this.currentElement?.direction === Direction.Forward;
|
|
127
|
+
return this.nextIn(Direction.Forward) !== null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
next(): ContentElement {
|
|
131
|
+
|
|
132
|
+
this.currentElement = this.nextIn(Direction.Forward);
|
|
133
|
+
const currentIndex = this.currentElementIndex;
|
|
134
|
+
if (currentIndex !== null &&
|
|
135
|
+
currentIndex >= 0 &&
|
|
136
|
+
currentIndex < this.pageContentFiltered.length - 1) {
|
|
137
|
+
const newIndex = currentIndex + 1;
|
|
138
|
+
this.currentProgression = newIndex / (this.pageContentFiltered.length) * 100;
|
|
139
|
+
this.currentElementIndex = newIndex;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return this.currentElement ?
|
|
143
|
+
this.currentElement.element :
|
|
144
|
+
new PDFContentElement(new Locator( { href : "", type: "" }), { index: -1, text: "" });
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private nextIn(direction: Direction): ElementInDirection | null {
|
|
149
|
+
const index = this.currentElementIndex;
|
|
150
|
+
|
|
151
|
+
// If the current progression is out of bounds, return null
|
|
152
|
+
if(index === null) return null;
|
|
153
|
+
|
|
154
|
+
// If the current progression is at the end of the pageContentFiltered array, return null
|
|
155
|
+
if(index === this.pageContentFiltered.length - 1 && direction === Direction.Forward) return null;
|
|
156
|
+
|
|
157
|
+
// If the current progression is at the beginning of the pageContentFiltered array, return null
|
|
158
|
+
if(index === 0 && direction === Direction.Backward) return null;
|
|
159
|
+
|
|
160
|
+
// Get the next element in the pageContentFiltered array
|
|
161
|
+
const content = this.pageContentFiltered[index + direction];
|
|
162
|
+
|
|
163
|
+
if (content) {
|
|
164
|
+
return new ElementInDirection(content, direction);
|
|
165
|
+
} else {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Updates the current [PDFReadableElement] position in the pageContentFiltered array
|
|
172
|
+
* based on the current progression value.
|
|
173
|
+
*/
|
|
174
|
+
private updateElementIndex(): number | null {
|
|
175
|
+
if (this.currentProgression === 0) return 0;
|
|
176
|
+
|
|
177
|
+
// return null if the current progression is out of bounds
|
|
178
|
+
if (this.currentProgression < 0 || this.currentProgression >= 100) return null;
|
|
179
|
+
|
|
180
|
+
// Convert the progression percentage to an index in the pageContentFiltered array
|
|
181
|
+
return Math.floor(this.currentProgression * this.pageContentFiltered.length / 100);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Resource } from "../../../../fetcher/Resource";
|
|
2
|
+
import { Locator } from "../../../Locator";
|
|
3
|
+
import { Publication } from "../../../Publication";
|
|
4
|
+
import { IllegalStateError, Iterator } from "../Iterator";
|
|
5
|
+
import { ContentElement } from "../element";
|
|
6
|
+
|
|
7
|
+
// Creates a [Content.Iterator] instance for the [Resource], starting from the given [Locator].
|
|
8
|
+
// Returns null if the resource media type is not supported.
|
|
9
|
+
export type ResourceContentIteratorFactory = (resource: Resource, locator: Locator) => Iterator | null;
|
|
10
|
+
|
|
11
|
+
enum Direction {
|
|
12
|
+
Forward = 1,
|
|
13
|
+
Backward = -1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// [Iterator] for a resource, associated with its [index] in the reading order.
|
|
17
|
+
class IndexedIterator {
|
|
18
|
+
constructor(
|
|
19
|
+
readonly index: number,
|
|
20
|
+
readonly iterator: Iterator
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
async nextContentIn(direction: Direction): Promise<ContentElement | null> {
|
|
24
|
+
return direction === Direction.Forward ? this.iterator.nextOrNull() : this.iterator.previousOrNull();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// [Element] loaded with [hasPrevious] or [hasNext], associated with the move direction.
|
|
29
|
+
class ElementInDirection {
|
|
30
|
+
constructor(
|
|
31
|
+
readonly element: ContentElement,
|
|
32
|
+
readonly direction: Direction
|
|
33
|
+
) {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type LocatorOrProgression = Locator | number;
|
|
37
|
+
|
|
38
|
+
// A composite [Content.Iterator] which iterates through a whole [publication] and delegates the
|
|
39
|
+
// iteration inside a given resource to media type-specific iterators.
|
|
40
|
+
export class PublicationContentIterator extends Iterator {
|
|
41
|
+
private _currentIterator: IndexedIterator | null = null;
|
|
42
|
+
private currentElement: ElementInDirection | null = null;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
/**
|
|
46
|
+
* The [Publication] which will be iterated through.
|
|
47
|
+
*/
|
|
48
|
+
private publication: Publication,
|
|
49
|
+
/**
|
|
50
|
+
* Starting [Locator] in the publication.
|
|
51
|
+
*/
|
|
52
|
+
private startLocator: Locator | undefined,
|
|
53
|
+
/**
|
|
54
|
+
* List of [ResourceContentIteratorFactory] which will be used to create the
|
|
55
|
+
* iterator for each resource. The factories are tried in order until there's a match.
|
|
56
|
+
*/
|
|
57
|
+
private resourceContentIteratorFactories: ResourceContentIteratorFactory[]
|
|
58
|
+
) { super(); }
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async hasPrevious(): Promise<boolean> {
|
|
62
|
+
this.currentElement = await this.nextIn(Direction.Backward);
|
|
63
|
+
return this.currentElement !== null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
previous(): ContentElement {
|
|
67
|
+
if(this.currentElement?.direction !== Direction.Backward) throw new IllegalStateError("Called previous() without a successful call to hasPrevious() first");
|
|
68
|
+
return this.currentElement.element;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async hasNext(): Promise<boolean> {
|
|
72
|
+
this.currentElement = await this.nextIn(Direction.Forward);
|
|
73
|
+
return this.currentElement !== null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
next(): ContentElement {
|
|
77
|
+
if(this.currentElement?.direction !== Direction.Forward) throw new IllegalStateError("Called next() without a successful call to hasNext() first");
|
|
78
|
+
return this.currentElement.element;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async nextIn(direction: Direction): Promise<ElementInDirection | null> {
|
|
82
|
+
const iterator = this.currentIterator;
|
|
83
|
+
if(!iterator) return null;
|
|
84
|
+
|
|
85
|
+
const content = await iterator.nextContentIn(direction);
|
|
86
|
+
if(!content) {
|
|
87
|
+
const ni = this.nextIteratorIn(direction, iterator.index);
|
|
88
|
+
if(!ni) return null;
|
|
89
|
+
this._currentIterator = ni;
|
|
90
|
+
return this.nextIn(direction);
|
|
91
|
+
}
|
|
92
|
+
return new ElementInDirection(content!, direction);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns the [Iterator] for the current [Resource] in the reading order.
|
|
97
|
+
*/
|
|
98
|
+
private get currentIterator(): IndexedIterator | null {
|
|
99
|
+
if(!this._currentIterator) {
|
|
100
|
+
this._currentIterator = this.initialIterator();
|
|
101
|
+
}
|
|
102
|
+
return this._currentIterator;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns the first iterator starting at [startLocator] or the beginning of the publication.
|
|
107
|
+
*/
|
|
108
|
+
private initialIterator(): IndexedIterator | null {
|
|
109
|
+
const index = this.startLocator ? this.publication.readingOrder.findIndexWithHref(this.startLocator.href) : 0;
|
|
110
|
+
const locations: LocatorOrProgression = this.startLocator || 0;
|
|
111
|
+
return this.loadIteratorAt(index, locations) ?? this.nextIteratorIn(Direction.Forward, index);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns the next resource iterator in the given [direction], starting from [fromIndex].
|
|
116
|
+
*/
|
|
117
|
+
private nextIteratorIn(direction: Direction, fromIndex: number): IndexedIterator | null {
|
|
118
|
+
const index = fromIndex + direction;
|
|
119
|
+
if(index < 0 || index >= this.publication.manifest.readingOrder.items.length) return null;
|
|
120
|
+
const progression = direction === Direction.Forward ? 0 : 1;
|
|
121
|
+
|
|
122
|
+
return this.loadIteratorAt(index, progression) ?? this.nextIteratorIn(direction, index);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Loads the iterator at the given [index] in the reading order.
|
|
127
|
+
* The [location] will be used to compute the starting [Locator] for the iterator.
|
|
128
|
+
*/
|
|
129
|
+
private loadIteratorAt(index: number, location: LocatorOrProgression): IndexedIterator | null {
|
|
130
|
+
const link = this.publication.readingOrder.items[index];
|
|
131
|
+
const locator = typeof location === "number" ? this.publication.manifest.locatorFromLink(link)?.copyWithLocations({
|
|
132
|
+
progression: location
|
|
133
|
+
}) : location;
|
|
134
|
+
if(!locator) return null;
|
|
135
|
+
const resource = this.publication.get(link);
|
|
136
|
+
|
|
137
|
+
for (const factory of this.resourceContentIteratorFactories) {
|
|
138
|
+
const res = factory(resource, locator);
|
|
139
|
+
if(res) new IndexedIterator(index, res);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { LocatorText } from "../../../Locator";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// https://github.com/jhy/jsoup/blob/0b10d516ed8f907f8fb4acb9a0806137a8988d45/src/main/java/org/jsoup/parser/Tag.java#L243
|
|
5
|
+
const inlineTags = [
|
|
6
|
+
"OBJECT",
|
|
7
|
+
"BASE",
|
|
8
|
+
"FONT",
|
|
9
|
+
"TT",
|
|
10
|
+
"I",
|
|
11
|
+
"B",
|
|
12
|
+
"U",
|
|
13
|
+
"BIG",
|
|
14
|
+
"SMALL",
|
|
15
|
+
"EM",
|
|
16
|
+
"STRONG",
|
|
17
|
+
"DFN",
|
|
18
|
+
"CODE",
|
|
19
|
+
"SAMP",
|
|
20
|
+
"KBD",
|
|
21
|
+
"VAR",
|
|
22
|
+
"CITE",
|
|
23
|
+
"ABBR",
|
|
24
|
+
"TIME",
|
|
25
|
+
"ACRONYM",
|
|
26
|
+
"MARK",
|
|
27
|
+
"RUBY",
|
|
28
|
+
"RT",
|
|
29
|
+
"RP",
|
|
30
|
+
"RTC",
|
|
31
|
+
"A",
|
|
32
|
+
"IMG",
|
|
33
|
+
"BR",
|
|
34
|
+
"WBR",
|
|
35
|
+
"MAP",
|
|
36
|
+
"Q",
|
|
37
|
+
"SUB",
|
|
38
|
+
"SUP",
|
|
39
|
+
"BDO",
|
|
40
|
+
"IFRAME",
|
|
41
|
+
"EMBED",
|
|
42
|
+
"SPAN",
|
|
43
|
+
"INPUT",
|
|
44
|
+
"SELECT",
|
|
45
|
+
"TEXTAREA",
|
|
46
|
+
"LABEL",
|
|
47
|
+
"BUTTON",
|
|
48
|
+
"OPTGROUP",
|
|
49
|
+
"OPTION",
|
|
50
|
+
"LEGEND",
|
|
51
|
+
"DATALIST",
|
|
52
|
+
"KEYGEN",
|
|
53
|
+
"OUTPUT",
|
|
54
|
+
"PROGRESS",
|
|
55
|
+
"METER",
|
|
56
|
+
"AREA",
|
|
57
|
+
"PARAM",
|
|
58
|
+
"SOURCE",
|
|
59
|
+
"TRACK",
|
|
60
|
+
"SUMMARY",
|
|
61
|
+
"COMMAND",
|
|
62
|
+
"DEVICE",
|
|
63
|
+
"BASEFONT",
|
|
64
|
+
"BGSOUND",
|
|
65
|
+
"MENUITEM",
|
|
66
|
+
"DATA",
|
|
67
|
+
"BDI",
|
|
68
|
+
"S",
|
|
69
|
+
"STRIKE",
|
|
70
|
+
"NOBR",
|
|
71
|
+
"RB"
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
export function isInlineTag(n: Node) {
|
|
75
|
+
return inlineTags.includes(n.nodeName.toUpperCase());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function srcRelativeToHref(e: Element, base?: string | URL): string | null {
|
|
79
|
+
const src = e.getAttribute("src");
|
|
80
|
+
if (!src?.length) return null;
|
|
81
|
+
try {
|
|
82
|
+
// TODO use readium util Href class
|
|
83
|
+
return new URL(src, base).toString();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return src; // This is a temporary solution until we implement the Href utility
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Inspired by golang's unicode.IsSpace
|
|
90
|
+
const unicodeSpaceRange = `[\\s\\u0085\\u00A0\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]`;
|
|
91
|
+
const allUnicodeSpace = new RegExp(`^${unicodeSpaceRange}+$`, "g");
|
|
92
|
+
const unicodeSpaceTrim = new RegExp(`^${unicodeSpaceRange}+|${unicodeSpaceRange}+$/g`, "g");
|
|
93
|
+
const unicodeSpaceStart = new RegExp(`^${unicodeSpaceRange}+`, "g");
|
|
94
|
+
const unicodeSpaceEnd = new RegExp(`${unicodeSpaceRange}+$`, "g");
|
|
95
|
+
|
|
96
|
+
export const isBlank = (s: string | null) =>
|
|
97
|
+
// Normal spaces, \t, \n, \v, \f, \r, U+0085, U+00A0, U+2000 to U+200A, U+2028, U+2029, U+202F, U+205F, U+3000
|
|
98
|
+
!s?.length ? true : allUnicodeSpace.test(s);
|
|
99
|
+
|
|
100
|
+
// Like Go's strings.TrimSpace
|
|
101
|
+
export const trimUnicodeSpace = (s: string) => s.replace(unicodeSpaceTrim, "");
|
|
102
|
+
export const trimUnicodeSpaceStart = (s: string) => s.replace(unicodeSpaceStart, "");
|
|
103
|
+
export const trimUnicodeSpaceEnd = (s: string) => s.replace(unicodeSpaceEnd, "");
|
|
104
|
+
|
|
105
|
+
export const trimmingTextLocator = (text: string, before: string = ""): LocatorText =>
|
|
106
|
+
new LocatorText({
|
|
107
|
+
before: before + (unicodeSpaceStart.exec(text)?.[0] ?? ""),
|
|
108
|
+
highlight: trimUnicodeSpace(text),
|
|
109
|
+
after: unicodeSpaceEnd.exec(text)?.[0] ?? "",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export function elementLanguage(e: HTMLElement | null) {
|
|
113
|
+
while (e) {
|
|
114
|
+
let lang = e.getAttribute("lang")
|
|
115
|
+
|| e.getAttributeNS("http://www.w3.org/1999/xhtml", "lang")
|
|
116
|
+
|| e.getAttributeNS("http://www.w3.org/XML/1998/namespace", "lang");
|
|
117
|
+
if (lang) return lang;
|
|
118
|
+
e = e.parentElement;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Inspired by JSoup: https://github.com/jhy/jsoup/blob/1762412a28fa7b08ccf71d93fc4c98dc73086e03/src/main/java/org/jsoup/internal/StringUtil.java#L233
|
|
124
|
+
// Slight differing definition of what a whitespace characacter is
|
|
125
|
+
export function appendNormalizedWhitespace(accum: string, text: string): string {
|
|
126
|
+
const stripLeading = accum.length > 0 && accum[accum.length - 1] === " ";
|
|
127
|
+
let lastWasWhite = false, reachedNonWhite = false;
|
|
128
|
+
for (let i = 0; i < text.length; i++) {
|
|
129
|
+
if (isBlank(text[i])) {
|
|
130
|
+
if ((stripLeading && !reachedNonWhite) || lastWasWhite) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
accum += " ";
|
|
134
|
+
lastWasWhite = true;
|
|
135
|
+
} else {
|
|
136
|
+
const cp = text.charCodeAt(i);
|
|
137
|
+
// zero width sp, soft hyphen
|
|
138
|
+
if (cp !== 8203 && cp !== 173) {
|
|
139
|
+
accum += text[i];
|
|
140
|
+
lastWasWhite = false;
|
|
141
|
+
reachedNonWhite = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return accum;
|
|
146
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './content';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Parses the given array (or undefined it it's not an array) */
|
|
2
|
+
export function arrayfromJSON(json: any): Array<any> | undefined {
|
|
3
|
+
return json && json instanceof Array ? json : undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function arrayfromJSONorString(json: any): Array<any> | undefined {
|
|
7
|
+
return json && typeof json === 'string' ? [json] : arrayfromJSON(json);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Parses the given key and returns a Date (or undefined if it’s not a string) */
|
|
11
|
+
export function datefromJSON(json: any): Date | undefined {
|
|
12
|
+
return typeof json === 'string' ? new Date(json) : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Parses a numeric value, but returns undefined if it is not a positive number. */
|
|
16
|
+
export function numberfromJSON(json: any): number | undefined {
|
|
17
|
+
return isNaN(json) ? undefined : json;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Parses a numeric value, but returns undefined if it is not a positive number. */
|
|
21
|
+
export function positiveNumberfromJSON(json: any): number | undefined {
|
|
22
|
+
let num = numberfromJSON(json);
|
|
23
|
+
return num !== undefined && Math.sign(json) >= 0 ? json : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Converts a Set of a string to a string Array object */
|
|
27
|
+
export function setToArray(obj: Set<string>): Array<string> {
|
|
28
|
+
const list = new Array<string>();
|
|
29
|
+
obj.forEach(x => list.push(x));
|
|
30
|
+
return list;
|
|
31
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* Copyright 2021 Readium Foundation. All rights reserved.
|
|
2
|
+
* Use of this source code is governed by a BSD-style license,
|
|
3
|
+
* available in the LICENSE file present in the Github repository of the project.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A lightweight implementation of URI Template (RFC 6570).
|
|
7
|
+
*
|
|
8
|
+
* Only handles simple cases, fitting Readium's use cases.
|
|
9
|
+
* See https://tools.ietf.org/html/rfc6570
|
|
10
|
+
*/
|
|
11
|
+
export class URITemplate {
|
|
12
|
+
public uri: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* List of URI template parameter keys, if the [Link] is templated.
|
|
16
|
+
*/
|
|
17
|
+
public parameters: Set<string>;
|
|
18
|
+
|
|
19
|
+
constructor(uri: string) {
|
|
20
|
+
this.uri = uri;
|
|
21
|
+
|
|
22
|
+
/** List of URI template parameter keys. */
|
|
23
|
+
this.parameters = this.getParameters(uri);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* List of URI template parameter keys, if the [Link] is templated.
|
|
28
|
+
*/
|
|
29
|
+
private getParameters(uri: string): Set<string> {
|
|
30
|
+
const regex = /\{\??([^}]+)\}/g;
|
|
31
|
+
const match = uri.match(regex);
|
|
32
|
+
if (match) {
|
|
33
|
+
return new Set(
|
|
34
|
+
match
|
|
35
|
+
.join(',')
|
|
36
|
+
.replace(regex, '$1')
|
|
37
|
+
.split(',')
|
|
38
|
+
.map(m => m.trim())
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return new Set();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Expands the URI by replacing the template variables by the given parameters.
|
|
45
|
+
* Any extra parameter is appended as query parameters.
|
|
46
|
+
* See RFC 6570 on URI template: https://tools.ietf.org/html/rfc6570
|
|
47
|
+
*/
|
|
48
|
+
public expand(parameters: { [param: string]: string }): string {
|
|
49
|
+
const expandSimpleString = (string: string): string => {
|
|
50
|
+
return string
|
|
51
|
+
.split(',')
|
|
52
|
+
.map(parameter => {
|
|
53
|
+
const parameterValue = parameters[parameter];
|
|
54
|
+
return parameterValue ? encodeURIComponent(parameterValue) : '';
|
|
55
|
+
})
|
|
56
|
+
.join(',');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const expandFormStyle = (string: string): string => {
|
|
60
|
+
return (
|
|
61
|
+
'?' +
|
|
62
|
+
string
|
|
63
|
+
.split(',')
|
|
64
|
+
.map(expression => {
|
|
65
|
+
const parameter = expression.split('=')[0];
|
|
66
|
+
const parameterValue = parameters[parameter];
|
|
67
|
+
if (parameterValue) {
|
|
68
|
+
return `${parameter}=${encodeURIComponent(parameterValue)}`;
|
|
69
|
+
} else {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.join('&')
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return this.uri.replace(/\{(\??)([^}]+)\}/g, (...groups: Array<string>) => {
|
|
78
|
+
return !groups[1]
|
|
79
|
+
? expandSimpleString(groups[2])
|
|
80
|
+
: expandFormStyle(groups[2]);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|