forked from ScerIO/epubx.dart
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2651c54
commit 59d3358
Showing
17 changed files
with
4,371 additions
and
46 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import 'package:dio/dio.dart'; | ||
import 'package:epubx/example/token_interceptor.dart'; | ||
import 'package:pretty_dio_logger/pretty_dio_logger.dart'; | ||
|
||
class Datasource { | ||
late final Dio dio; | ||
void init() { | ||
dio = Dio(BaseOptions( | ||
baseUrl: 'https://api.kuka.tech', | ||
connectTimeout: const Duration(seconds: 15), | ||
receiveTimeout: const Duration(seconds: 15), | ||
)); | ||
dio.options.headers = { | ||
'content-type': 'application/json', | ||
'Accept': 'application/json', | ||
'Access-Control-Allow-Origin': '*', | ||
'Access-Control-Allow-Credentials': 'true', | ||
'Access-Control-Allow-Headers': | ||
'Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale', | ||
'Access-Control-Allow-Methods': 'POST, OPTIONS' | ||
// "App-Timezone": currentTimeZone | ||
}; | ||
dio.interceptors.add(PrettyDioLogger( | ||
requestBody: true, | ||
requestHeader: true, | ||
responseHeader: true, | ||
)); | ||
dio.interceptors.addAll([ | ||
TokenInterceptor(), | ||
]); | ||
} | ||
|
||
Future<Book> getBook(int bookId) async { | ||
try { | ||
final response = await dio.get( | ||
'/api/books/$bookId', | ||
); | ||
|
||
final apiResponse = ApiResponse.fromJson(response.data); | ||
|
||
final book = Book.fromJson(apiResponse.data); | ||
|
||
return book; | ||
} catch (e) { | ||
return Book(id: -1, fileUrl: ''); | ||
} | ||
} | ||
} | ||
|
||
class Book { | ||
final int id; | ||
final String fileUrl; | ||
|
||
Book({required this.id, required this.fileUrl}); | ||
|
||
factory Book.fromJson(Map<String, dynamic> json) => | ||
Book(id: json['id'], fileUrl: json['file']); | ||
} | ||
|
||
class ApiResponse { | ||
final bool success; | ||
final dynamic data; | ||
|
||
ApiResponse.fromJson(dynamic json) | ||
: success = json['success'], | ||
data = json['data']; | ||
} |
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,139 @@ | ||
import 'package:epubx/epubx.dart'; | ||
import 'package:html/dom.dart'; | ||
|
||
class EpubCfiGenerator { | ||
const EpubCfiGenerator(); | ||
|
||
String generateCompleteCFI(List<String?> entries) => | ||
'epubcfi(${entries.join()})'; | ||
|
||
String generatePackageDocumentCFIComponent( | ||
EpubChapter chapter, EpubPackage? packageDocument) { | ||
validatePackageDocument(packageDocument, chapter.Anchor); | ||
|
||
final index = getIdRefIndex(chapter, packageDocument!); | ||
final pos = getIdRefPosition(index); | ||
final spineIdRef = index >= 0 | ||
? packageDocument.Spine!.Items![index].IdRef | ||
: chapter.Anchor; | ||
|
||
// Append an !; this assumes that a CFI content document CFI component | ||
// will be appended at some point | ||
// `/6` - is position of the Spine element in Package | ||
return '/6/$pos[$spineIdRef]!'; | ||
} | ||
|
||
String generateElementCFIComponent(Node? startElement) { | ||
validateStartElement(startElement); | ||
|
||
// Call the recursive method to create all the steps up to the head element | ||
// of the content document (the "html" element) | ||
final contentDocCFI = | ||
createCFIElementSteps(startElement as Element, 'html'); | ||
|
||
// Remove the ! | ||
return contentDocCFI.substring(1, contentDocCFI.length); | ||
} | ||
|
||
String createCFIElementSteps(Element currentNode, String topLevelElement) { | ||
int currentNodePosition = 0; | ||
String elementStep = ''; | ||
|
||
// Find position of current node in parent list | ||
int index = 0; | ||
for (var node in currentNode.parent!.children) { | ||
if (node == currentNode) { | ||
currentNodePosition = index; | ||
} | ||
index++; | ||
} | ||
|
||
// Convert position to the CFI even-integer representation | ||
final int cfiPosition = (currentNodePosition + 1) * 2; | ||
|
||
// Create CFI step with id assertion, if the element has an id | ||
if (currentNode.attributes.containsKey('id')) { | ||
elementStep = '/$cfiPosition[${currentNode.attributes['id']!}]'; | ||
} else { | ||
elementStep = '/$cfiPosition'; | ||
} | ||
|
||
// If a parent is an html element return the (last) step for this content | ||
// document, otherwise, continue. | ||
// Also need to check if the current node is the top-level element. | ||
// This can occur if the start node is also the | ||
// top level element. | ||
final parentNode = currentNode.parent!; | ||
if (parentNode.localName == topLevelElement || | ||
currentNode.localName == topLevelElement) { | ||
// If the top level node is a type from which an indirection step, add an | ||
// indirection step character (!) | ||
// REFACTORING CANDIDATE: It is possible that this should be changed to: | ||
// if (topLevelElement = 'package') do | ||
// not return an indirection character. Every other type of top-level | ||
// element may require an indirection | ||
// step to navigate to, thus requiring that ! is always prepend. | ||
if (topLevelElement == 'html') { | ||
return '!$elementStep'; | ||
} else { | ||
return elementStep; | ||
} | ||
} else { | ||
return createCFIElementSteps(parentNode, topLevelElement) + elementStep; | ||
} | ||
} | ||
|
||
int getIdRefIndex(EpubChapter chapter, EpubPackage packageDocument) { | ||
final items = packageDocument.Spine!.Items!; | ||
int index = -1; | ||
int partIndex = -1; | ||
String? edRef = chapter.Anchor; | ||
|
||
if (chapter.Anchor == null) { | ||
// filename w/o extension | ||
edRef = _fileNameAsChapterName(chapter.ContentFileName!); | ||
} | ||
|
||
for (var i = 0; i < items.length; i++) { | ||
if (edRef == items[i].IdRef) { | ||
index = i; | ||
break; | ||
} | ||
if (edRef!.contains(items[i].IdRef!) || items[i].IdRef!.contains(edRef)) { | ||
partIndex = i; | ||
} | ||
} | ||
|
||
return index >= 0 ? index : partIndex; | ||
} | ||
|
||
int getIdRefPosition(int idRefIndex) => (idRefIndex + 1) * 2; | ||
|
||
void validatePackageDocument(EpubPackage? packageDocument, String? idRef) { | ||
// Check that the package document is non-empty | ||
// and contains an item ref element for the supplied id ref | ||
if (packageDocument == null) { | ||
throw Exception('A package document must be supplied to generate a CFI'); | ||
} | ||
// Commented, because there may be cases when id is not listed in object!!! | ||
// else if (getIdRefIndex(idRef, packageDocument) == -1) { | ||
// throw FlutterError( | ||
// ignore: lines_longer_than_80_chars | ||
// 'The id ref of the content document could not be found in the spine'); | ||
// } | ||
} | ||
|
||
void validateStartElement(Node? startElement) { | ||
if (startElement == null) { | ||
throw Exception('$startElement: CFI target element is null'); | ||
} | ||
|
||
if (startElement.nodeType != Node.ELEMENT_NODE) { | ||
throw Exception( | ||
'$startElement: CFI target element is not an HTML element'); | ||
} | ||
} | ||
|
||
String _fileNameAsChapterName(String path) => | ||
path.split('/').last.replaceFirst(RegExp(r'\.[^.]+$'), ''); | ||
} |
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,95 @@ | ||
import 'package:epubx/example/epub_cfi/_parser.dart'; | ||
import 'package:html/dom.dart'; | ||
|
||
class EpubCfiInterpreter { | ||
Element? searchLocalPathForHref( | ||
Element? htmlElement, CfiLocalPath localPathNode) { | ||
// Interpret the first local_path node, | ||
// which is a set of steps and and a terminus condition | ||
CfiStep nextStepNode; | ||
var currentElement = htmlElement; | ||
|
||
for (var stepNum = 1; stepNum < localPathNode.steps!.length; stepNum++) { | ||
nextStepNode = localPathNode.steps![stepNum]; | ||
if (nextStepNode.type == 'indexStep') { | ||
currentElement = interpretIndexStepNode(nextStepNode, currentElement); | ||
} else if (nextStepNode.type == 'indirectionStep') { | ||
currentElement = | ||
interpretIndirectionStepNode(nextStepNode, currentElement); | ||
} | ||
} | ||
|
||
return currentElement; | ||
} | ||
|
||
Element? interpretIndexStepNode( | ||
CfiStep? indexStepNode, Element? currentElement) { | ||
// Check node type; throw error if wrong type | ||
if (indexStepNode == null || indexStepNode.type != 'indexStep') { | ||
throw Exception('$indexStepNode: expected index step node'); | ||
} | ||
|
||
// Index step | ||
final stepTarget = _getNextNode(indexStepNode.stepLength, currentElement); | ||
|
||
// Check the id assertion, if it exists | ||
if ((indexStepNode.idAssertion ?? '').isNotEmpty) { | ||
if (!_targetIdMatchesIdAssertion( | ||
stepTarget!, indexStepNode.idAssertion)) { | ||
throw Exception( | ||
// ignore: lines_longer_than_80_chars | ||
'${indexStepNode.idAssertion}: ${stepTarget.attributes['id']} Id assertion failed'); | ||
} | ||
} | ||
|
||
return stepTarget; | ||
} | ||
|
||
Element? interpretIndirectionStepNode( | ||
CfiStep? indirectionStepNode, Element? currentElement) { | ||
// Check node type; throw error if wrong type | ||
if (indirectionStepNode == null || | ||
indirectionStepNode.type != 'indirectionStep') { | ||
throw Exception('$indirectionStepNode: expected indirection step node'); | ||
} | ||
|
||
// Indirection step | ||
final stepTarget = | ||
_getNextNode(indirectionStepNode.stepLength, currentElement); | ||
|
||
// Check the id assertion, if it exists | ||
if (indirectionStepNode.idAssertion != null) { | ||
if (!_targetIdMatchesIdAssertion( | ||
stepTarget!, indirectionStepNode.idAssertion)) { | ||
throw Exception( | ||
// ignore: lines_longer_than_80_chars | ||
'${indirectionStepNode.idAssertion}: ${stepTarget.attributes['id']} Id assertion failed'); | ||
} | ||
} | ||
|
||
return stepTarget; | ||
} | ||
|
||
bool _targetIdMatchesIdAssertion(Element foundNode, String? idAssertion) => | ||
foundNode.attributes.containsKey('id') && | ||
foundNode.attributes['id'] == idAssertion; | ||
|
||
Element? _getNextNode(int cfiStepValue, Element? currentNode) { | ||
if (cfiStepValue % 2 == 0) { | ||
return _elementNodeStep(cfiStepValue, currentNode!); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
Element _elementNodeStep(int cfiStepValue, Element currentNode) { | ||
final targetNodeIndex = ((cfiStepValue / 2) - 1).toInt(); | ||
final numElements = currentNode.children.length; | ||
|
||
if (targetNodeIndex > numElements) { | ||
throw RangeError.range(targetNodeIndex, 0, numElements - 1); | ||
} | ||
|
||
return currentNode.children[targetNodeIndex]; | ||
} | ||
} |
Oops, something went wrong.