Skip to content

Commit

Permalink
t
Browse files Browse the repository at this point in the history
  • Loading branch information
NedoPrograMax committed Jan 11, 2024
1 parent 2651c54 commit 59d3358
Show file tree
Hide file tree
Showing 17 changed files with 4,371 additions and 46 deletions.
67 changes: 67 additions & 0 deletions lib/example/datasource.dart
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'];
}
139 changes: 139 additions & 0 deletions lib/example/epub_cfi/_generator.dart
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'\.[^.]+$'), '');
}
95 changes: 95 additions & 0 deletions lib/example/epub_cfi/_interpreter.dart
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];
}
}
Loading

0 comments on commit 59d3358

Please sign in to comment.