attribute parser preserves indexes from original string

This commit is contained in:
zadam 2020-06-06 10:39:27 +02:00
parent f245d51746
commit ef1d062745
5 changed files with 69 additions and 42 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,32 +3,38 @@ import {describe, it, expect, execute} from './mini_test.js';
describe("Lexer", () => { describe("Lexer", () => {
it("simple label", () => { it("simple label", () => {
expect(attributeParser.lexer("#label")).toEqual(["#label"]); expect(attributeParser.lexer("#label").map(t => t.text))
.toEqual(["#label"]);
}); });
it("label with value", () => { it("label with value", () => {
expect(attributeParser.lexer("#label=Hallo")).toEqual(["#label", "=", "Hallo"]); expect(attributeParser.lexer("#label=Hallo").map(t => t.text))
.toEqual(["#label", "=", "Hallo"]);
}); });
it("relation with value", () => { it("relation with value", () => {
expect(attributeParser.lexer('~relation=<a class="reference-link" href="#root/RclIpMauTOKS/NFi2gL4xtPxM" data-note-path="root/RclIpMauTOKS/NFi2gL4xtPxM">note</a>')).toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); expect(attributeParser.lexer('~relation=<a class="reference-link" href="#root/RclIpMauTOKS/NFi2gL4xtPxM" data-note-path="root/RclIpMauTOKS/NFi2gL4xtPxM">note</a>').map(t => t.text))
.toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]);
expect(attributeParser.lexer('~relation=<a class="reference-link" id="abc" href="#NFi2gL4xtPxM">note</a>').map(t => t.text))
.toEqual(["~relation", "=", "#NFi2gL4xtPxM"]);
}); });
it("use quotes to define value", () => { it("use quotes to define value", () => {
expect(attributeParser.lexer("#'label a'='hello\"` world'")) expect(attributeParser.lexer("#'label a'='hello\"` world'").map(t => t.text))
.toEqual(["#label a", "=", 'hello"` world']); .toEqual(["#label a", "=", 'hello"` world']);
expect(attributeParser.lexer('#"label a" = "hello\'` world"')) expect(attributeParser.lexer('#"label a" = "hello\'` world"').map(t => t.text))
.toEqual(["#label a", "=", "hello'` world"]); .toEqual(["#label a", "=", "hello'` world"]);
expect(attributeParser.lexer('#`label a` = `hello\'" world`')) expect(attributeParser.lexer('#`label a` = `hello\'" world`').map(t => t.text))
.toEqual(["#label a", "=", "hello'\" world"]); .toEqual(["#label a", "=", "hello'\" world"]);
}); });
}); });
describe("Parser", () => { describe("Parser", () => {
it("simple label", () => { it("simple label", () => {
const attrs = attributeParser.parser(["#token"]); const attrs = attributeParser.parser(["#token"].map(t => ({text: t})));
expect(attrs.length).toEqual(1); expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('label'); expect(attrs[0].type).toEqual('label');
@ -37,7 +43,7 @@ describe("Parser", () => {
}); });
it("label with value", () => { it("label with value", () => {
const attrs = attributeParser.parser(["#token", "=", "val"]); const attrs = attributeParser.parser(["#token", "=", "val"].map(t => ({text: t})));
expect(attrs.length).toEqual(1); expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('label'); expect(attrs[0].type).toEqual('label');
@ -46,16 +52,24 @@ describe("Parser", () => {
}); });
it("relation", () => { it("relation", () => {
const attrs = attributeParser.parser(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); let attrs = attributeParser.parser(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"].map(t => ({text: t})));
expect(attrs.length).toEqual(1); expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('relation'); expect(attrs[0].type).toEqual('relation');
expect(attrs[0].name).toEqual("token"); expect(attrs[0].name).toEqual("token");
expect(attrs[0].value).toEqual('#root/RclIpMauTOKS/NFi2gL4xtPxM'); expect(attrs[0].value).toEqual('NFi2gL4xtPxM');
attrs = attributeParser.parser(["~token", "=", "#NFi2gL4xtPxM"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('relation');
expect(attrs[0].name).toEqual("token");
expect(attrs[0].value).toEqual('NFi2gL4xtPxM');
}); });
it("error cases", () => { it("error cases", () => {
expect(() => attributeParser.parser(["~token"])).toThrow('Relation "~token" should point to a note.'); expect(() => attributeParser.parser(["~token"].map(t => ({text: t}))))
.toThrow('Relation "~token" should point to a note.');
}); });
}); });

View File

@ -15,7 +15,7 @@ function preprocess(str) {
function lexer(str) { function lexer(str) {
str = preprocess(str); str = preprocess(str);
const expressionTokens = []; const tokens = [];
let quotes = false; let quotes = false;
let currentWord = ''; let currentWord = '';
@ -33,12 +33,19 @@ function lexer(str) {
} }
} }
function finishWord() { /**
* @param endIndex - index of the last character of the token
*/
function finishWord(endIndex) {
if (currentWord === '') { if (currentWord === '') {
return; return;
} }
expressionTokens.push(currentWord); tokens.push({
text: currentWord,
startIndex: endIndex - currentWord.length,
endIndex: endIndex
});
currentWord = ''; currentWord = '';
} }
@ -61,7 +68,7 @@ function lexer(str) {
else if (['"', "'", '`'].includes(chr)) { else if (['"', "'", '`'].includes(chr)) {
if (!quotes) { if (!quotes) {
if (previousOperatorSymbol()) { if (previousOperatorSymbol()) {
finishWord(); finishWord(i - 1);
} }
quotes = chr; quotes = chr;
@ -69,7 +76,7 @@ function lexer(str) {
else if (quotes === chr) { else if (quotes === chr) {
quotes = false; quotes = false;
finishWord(); finishWord(i - 1);
} }
else { else {
// it's a quote but within other kind of quotes so it's valid as a literal character // it's a quote but within other kind of quotes so it's valid as a literal character
@ -84,17 +91,11 @@ function lexer(str) {
continue; continue;
} }
else if (chr === ' ') { else if (chr === ' ') {
finishWord(); finishWord(i - 1);
continue;
}
else if (['(', ')', '.'].includes(chr)) {
finishWord();
currentWord += chr;
finishWord();
continue; continue;
} }
else if (previousOperatorSymbol() !== isOperatorSymbol(chr)) { else if (previousOperatorSymbol() !== isOperatorSymbol(chr)) {
finishWord(); finishWord(i - 1);
currentWord += chr; currentWord += chr;
continue; continue;
@ -104,44 +105,46 @@ function lexer(str) {
currentWord += chr; currentWord += chr;
} }
finishWord(); finishWord(str.length - 1);
return expressionTokens; return tokens;
} }
function parser(tokens) { function parser(tokens) {
const attrs = []; const attrs = [];
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]; const {text, startIndex, endIndex} = tokens[i];
if (token.startsWith('#')) { if (text.startsWith('#')) {
const attr = { const attr = {
type: 'label', type: 'label',
name: token.substr(1), name: text.substr(1),
isInheritable: false // FIXME isInheritable: false, // FIXME
startIndex,
endIndex
}; };
if (tokens[i + 1] === "=") { if (i + 1 < tokens.length && tokens[i + 1].text === "=") {
if (i + 2 >= tokens.length) { if (i + 2 >= tokens.length) {
throw new Error(`Missing value for label "${token}"`); throw new Error(`Missing value for label "${text}"`);
} }
i += 2; i += 2;
attr.value = tokens[i]; attr.value = tokens[i].text;
} }
attrs.push(attr); attrs.push(attr);
} }
else if (token.startsWith('~')) { else if (text.startsWith('~')) {
if (i + 2 >= tokens.length || tokens[i + 1] !== '=') { if (i + 2 >= tokens.length || tokens[i + 1].text !== '=') {
throw new Error(`Relation "${token}" should point to a note.`); throw new Error(`Relation "${text}" should point to a note.`);
} }
i += 2; i += 2;
let notePath = tokens[i]; let notePath = tokens[i].text;
if (notePath.startsWith("#")) { if (notePath.startsWith("#")) {
notePath = notePath.substr(1); notePath = notePath.substr(1);
} }
@ -150,15 +153,17 @@ function parser(tokens) {
const attr = { const attr = {
type: 'relation', type: 'relation',
name: token.substr(1), name: text.substr(1),
isInheritable: false, // FIXME isInheritable: false, // FIXME
value: noteId value: noteId,
startIndex,
endIndex
}; };
attrs.push(attr); attrs.push(attr);
} }
else { else {
throw new Error(`Unrecognized attribute "${token}"`); throw new Error(`Unrecognized attribute "${text}"`);
} }
} }

View File

@ -114,6 +114,11 @@ export default class NoteAttributesWidget extends TabAwareWidget {
// display of $widget in both branches. // display of $widget in both branches.
this.$widget.show(); this.$widget.show();
this.$editor.on("click", () => {
const pos = this.textEditor.model.document.selection.getFirstPosition();
console.log(pos.textNode && pos.textNode.data, pos.parent.textNode && pos.parent.textNode.data, pos.offset);
});
this.textEditor = await BalloonEditor.create(this.$editor[0], { this.textEditor = await BalloonEditor.create(this.$editor[0], {
removePlugins: [ removePlugins: [
'Enter', 'Enter',
@ -168,6 +173,9 @@ export default class NoteAttributesWidget extends TabAwareWidget {
}); });
this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js');
CKEditorInspector.attach(this.textEditor);
} }
async loadReferenceLinkTitle(noteId, $el) { async loadReferenceLinkTitle(noteId, $el) {