mirror of
https://github.com/miggymofongo/asteroid.git
synced 2024-10-18 21:41:49 +00:00
236 lines
9.4 KiB
JavaScript
236 lines
9.4 KiB
JavaScript
|
Bibi.x({
|
||
|
|
||
|
id: 'EPUBCFI',
|
||
|
description: 'Utilities for EPUBCFI', // An Example Is at the Bottom of This Document.
|
||
|
author: 'Satoru Matsushima (@satorumurmur)',
|
||
|
version: '1.2.0-demo'
|
||
|
|
||
|
})(function() {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
this.CFIString = '';
|
||
|
this.Current = 0;
|
||
|
this.Log = false;
|
||
|
this.LogCorrection = false;
|
||
|
this.LogCancelation = false;
|
||
|
|
||
|
this.parse = (CFIString, Scope) => {
|
||
|
if(!CFIString || typeof CFIString != 'string') return null;
|
||
|
try { CFIString = decodeURIComponent(CFIString); } catch(Err) { this.log(0, `Unregulated URIEncoding.`); return null; }
|
||
|
if(!Scope || typeof Scope != 'string' || typeof this['parse' + Scope] != 'function') Scope = 'Fragment';
|
||
|
if(Scope == 'Fragment') CFIString = CFIString.replace(/^(epubcfi\()?/, 'epubcfi(').replace(/(\))?$/, ')');
|
||
|
this.CFIString = CFIString, this.Current = 0;
|
||
|
if(this.Log) {
|
||
|
this.log(1, `Bibi EPUB-CFI`);
|
||
|
this.log(2, `parse`);
|
||
|
this.log(3, `CFIString: ${ this.CFIString }`);
|
||
|
}
|
||
|
return this['parse' + Scope]();
|
||
|
};
|
||
|
|
||
|
this.parseFragment = () => {
|
||
|
const Foothold = this.Current;
|
||
|
if(!this.parseString('epubcfi(')) return this.cancel(Foothold, `Fragment`);
|
||
|
const CFI = this.parseCFI();
|
||
|
if(CFI === null) return this.cancel(Foothold);
|
||
|
if(!this.parseString(')')) return this.cancel(Foothold, `Fragment`);
|
||
|
return CFI;
|
||
|
};
|
||
|
this.parseCFI = () => {
|
||
|
const Foothold = this.Current, CFI = { Type: 'CFI', Path: this.parsePath() };
|
||
|
if(!CFI.Path) return this.cancel(Foothold, `CFI`);
|
||
|
if(this.parseString(',')) {
|
||
|
CFI.Start = this.parseLocalPath();
|
||
|
if(!CFI.Start.Steps.length && !CFI.Start.TermStep) return this.cancel(Foothold, `CFI > Range`);
|
||
|
if(!this.parseString(',')) return this.cancel(Foothold, 'CFI > Range');
|
||
|
CFI.End = this.parseLocalPath();
|
||
|
if( !CFI.End.Steps.length && !CFI.End.TermStep) return this.cancel(Foothold, `CFI > Range`);
|
||
|
}
|
||
|
return CFI;
|
||
|
};
|
||
|
this.parsePath = () => {
|
||
|
const Foothold = this.Current, Path = { Type: 'Path', Steps: [this.parseStep()] }, LocalPath = this.parseLocalPath();
|
||
|
if(!Path.Steps[0]) return this.cancel(Foothold, `Path`);
|
||
|
if(LocalPath) Path.Steps = Path.Steps.concat(LocalPath.Steps);
|
||
|
else return this.cancel(Foothold, `Path`);
|
||
|
return Path;
|
||
|
};
|
||
|
this.parseLocalPath = () => {
|
||
|
const Foothold = this.Current, LocalPath = { Type: 'LocalPath', Steps: [] };
|
||
|
let StepRoot = LocalPath, Step = this.parseStep('Local'), TermStep = null;
|
||
|
while(Step !== null) {
|
||
|
StepRoot.Steps.push(Step);
|
||
|
Step = this.parseStep('Local');
|
||
|
if(!Step) break;
|
||
|
if(Step.Type == 'IndirectStep') {
|
||
|
const IndirectPath = { Type: 'IndirectPath', Steps: [] };
|
||
|
StepRoot.Steps.push(IndirectPath);
|
||
|
StepRoot = IndirectPath;
|
||
|
} else if(Step.Type == 'TermStep') {
|
||
|
TermStep = Step;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(TermStep) StepRoot.Steps.push(TermStep);
|
||
|
return (LocalPath.Steps.length ? LocalPath : null);
|
||
|
};
|
||
|
this.parseStep = Local => {
|
||
|
const Foothold = this.Current, Step = {};
|
||
|
if( this.parseString( '/')) Step.Type = 'Step';
|
||
|
else if(Local && this.parseString('!/')) Step.Type = 'IndirectStep';
|
||
|
else if(Local && this.parseString( ':')) Step.Type = 'TermStep';
|
||
|
else return this.cancel(Foothold, `Step`);
|
||
|
Step.Index = this.parseString(/^(0|[1-9][0-9]*)/);
|
||
|
if(Step.Index === null) return this.cancel(Foothold, `Step`);
|
||
|
Step.Index = parseInt(Step.Index);
|
||
|
if(this.parseString('[')) {
|
||
|
if(Step.Type != 'TermStep') {
|
||
|
Step.ID = this.parseString(/^[a-zA-Z_:][a-zA-Z0-9_:\-\.]+/);
|
||
|
if(!Step.ID) return this.cancel(Foothold, `Step > Assertion > ID`);
|
||
|
} else {
|
||
|
const CSV = [], Side = null, ValueRegExp = /^((\^[\^\[\]\(\)\,\;\=])|[_a-zA-Z0-9%\- ])*/;
|
||
|
CSV.push(this.parseString(ValueRegExp));
|
||
|
if(this.parseString(',')) CSV.push(this.parseString(ValueRegExp));
|
||
|
if(CSV[0]) Step.Preceding = CSV[0];
|
||
|
if(CSV[1]) Step.Following = CSV[1];
|
||
|
if(this.parseString(/^;s=/)) Side = this.parseString(/^[ab]/);
|
||
|
if(Side) Step.Side = Side;
|
||
|
if(!Step.Preceding && !Step.Following && !Step.Side) return this.cancel(Foothold, `Step > Assertion > TextLocation`);
|
||
|
}
|
||
|
if(!this.parseString(']')) return this.cancel(Foothold, `Step > Assertion`);
|
||
|
}
|
||
|
return Step;
|
||
|
};
|
||
|
this.parseString = S => {
|
||
|
let Correction = null, Matched = false;
|
||
|
if(S instanceof RegExp) {
|
||
|
const CFIString = this.CFIString.substr(this.Current, this.CFIString.length - this.Current);
|
||
|
if(S.test(CFIString)) {
|
||
|
Matched = true;
|
||
|
S = CFIString.match(S)[0];
|
||
|
}
|
||
|
} else if(this.CFIString.substr(this.Current, S.length) === S) {
|
||
|
Matched = true;
|
||
|
}
|
||
|
if(Matched) {
|
||
|
this.Current += S.length;
|
||
|
Correction = S;
|
||
|
}
|
||
|
return this.correct(Correction);
|
||
|
};
|
||
|
|
||
|
this.correct = Correction => {
|
||
|
if(this.Log && this.LogCorrection && Correction) this.log(3, Correction);
|
||
|
return Correction;
|
||
|
};
|
||
|
this.cancel = (Foothold, Parser) => {
|
||
|
if(this.Log && this.LogCancelation) this.log(4, `cancel: parse ${ Parser } (${ Foothold }-${ this.Current }/${ this.CFIString.length })`);
|
||
|
if(typeof Foothold == 'number') this.Current = Foothold;
|
||
|
return null;
|
||
|
};
|
||
|
this.log = (Lv, Message) => {
|
||
|
if(!this.Log) return;
|
||
|
if(Lv == 0) Message = `[ERROR] ${ Message }`;
|
||
|
else if(Lv == 1) Message = `---------------- ${ Message } ----------------`;
|
||
|
else if(Lv == 2) Message = Message;
|
||
|
else if(Lv == 3) Message = ` - ${ Message }`;
|
||
|
else if(Lv == 4) Message = ` . ${ Message }`;
|
||
|
O.log(`EPUBCFI: ${ Message }`);
|
||
|
};
|
||
|
|
||
|
this.getDestination = CFIString => {
|
||
|
const CFI = X['EPUBCFI'].parse(CFIString);
|
||
|
if(!CFI || CFI.Path.Steps.length < 2 || !CFI.Path.Steps[1].Index || CFI.Path.Steps[1].Index % 2 == 1) return null;
|
||
|
const ItemIndexInAll = CFI.Path.Steps[1].Index / 2 - 1;
|
||
|
let ElementSelector = null, TextNodeIndex = null, TermStep = null, IndirectPath = null;
|
||
|
if(CFI.Path.Steps[2] && CFI.Path.Steps[2].Steps) {
|
||
|
ElementSelector = '';
|
||
|
CFI.Path.Steps[2].Steps.forEach((Step, i) => {
|
||
|
if(Step.Type == 'IndirectPath') { IndirectPath = Step; return false; }
|
||
|
if(Step.Type == 'TermStep') { TermStep = Step; return false; }
|
||
|
if(Step.Index % 2 == 1) {
|
||
|
TextNodeIndex = Step.Index - 1;
|
||
|
if(i != CFI.Path.Steps[2].Steps.length - 2) return false;
|
||
|
}
|
||
|
if(TextNodeIndex === null) ElementSelector = Step.ID ? '#' + Step.ID : ElementSelector + '>*:nth-child(' + (Step.Index / 2) + ')';
|
||
|
});
|
||
|
if(ElementSelector && /^>/.test(ElementSelector)) ElementSelector = 'html' + ElementSelector;
|
||
|
if(!ElementSelector) ElementSelector = null;
|
||
|
}
|
||
|
return {
|
||
|
CFI: CFI,
|
||
|
CFIString: CFIString,
|
||
|
ItemIndexInAll: ItemIndexInAll,
|
||
|
ElementSelector: ElementSelector,
|
||
|
TextNodeIndex: TextNodeIndex,
|
||
|
TermStep: TermStep,
|
||
|
IndirectPath: IndirectPath
|
||
|
};
|
||
|
};
|
||
|
|
||
|
});
|
||
|
|
||
|
/* -----------------------------------------------------------------------------------------------------------------
|
||
|
|
||
|
// EXAMPLE:
|
||
|
|
||
|
X.EPUBCFI.parse('epubcfi(/6/4!/4/10!/4/2:32[All%20You%20Need%20Is,Love;s=a])'); // returns following object.
|
||
|
|
||
|
--------------------------------------------------------------------------------------------------------------------
|
||
|
|
||
|
{
|
||
|
Type: 'CFI',
|
||
|
Path: {
|
||
|
Type: 'Path',
|
||
|
Steps: [
|
||
|
{
|
||
|
Type: 'Step',
|
||
|
Index: '6'
|
||
|
},
|
||
|
{
|
||
|
Type: 'Step',
|
||
|
Index: '4'
|
||
|
},
|
||
|
{
|
||
|
Type: 'IndirectPath',
|
||
|
Steps: [
|
||
|
{
|
||
|
Type: 'IndirectStep',
|
||
|
Index: '4'
|
||
|
},
|
||
|
{
|
||
|
Type: 'Step',
|
||
|
Index: '10'
|
||
|
},
|
||
|
{
|
||
|
Type: 'IndirectPath',
|
||
|
Steps: [
|
||
|
{
|
||
|
Type: 'IndirectStep',
|
||
|
Index: '4'
|
||
|
},
|
||
|
{
|
||
|
Type: 'Step',
|
||
|
Index: '2'
|
||
|
}
|
||
|
],
|
||
|
TermStep: {
|
||
|
Type: 'TermStep',
|
||
|
Index: '32',
|
||
|
Preceding: 'All You Need Is',
|
||
|
Following: 'Love',
|
||
|
Side: 'a'
|
||
|
}
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
----------------------------------------------------------------------------------------------------------------- */
|
||
|
|
||
|
|
||
|
|