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", () => {
it("simple label", () => {
expect(attributeParser.lexer("#label")).toEqual(["#label"]);
expect(attributeParser.lexer("#label").map(t => t.text))
.toEqual(["#label"]);
});
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", () => {
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", () => {
expect(attributeParser.lexer("#'label a'='hello\"` world'"))
expect(attributeParser.lexer("#'label a'='hello\"` world'").map(t => t.text))
.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"]);
expect(attributeParser.lexer('#`label a` = `hello\'" world`'))
expect(attributeParser.lexer('#`label a` = `hello\'" world`').map(t => t.text))
.toEqual(["#label a", "=", "hello'\" world"]);
});
});
describe("Parser", () => {
it("simple label", () => {
const attrs = attributeParser.parser(["#token"]);
const attrs = attributeParser.parser(["#token"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('label');
@ -37,7 +43,7 @@ describe("Parser", () => {
});
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[0].type).toEqual('label');
@ -46,16 +52,24 @@ describe("Parser", () => {
});
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[0].type).toEqual('relation');
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", () => {
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) {
str = preprocess(str);
const expressionTokens = [];
const tokens = [];
let quotes = false;
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 === '') {
return;
}
expressionTokens.push(currentWord);
tokens.push({
text: currentWord,
startIndex: endIndex - currentWord.length,
endIndex: endIndex
});
currentWord = '';
}
@ -61,7 +68,7 @@ function lexer(str) {
else if (['"', "'", '`'].includes(chr)) {
if (!quotes) {
if (previousOperatorSymbol()) {
finishWord();
finishWord(i - 1);
}
quotes = chr;
@ -69,7 +76,7 @@ function lexer(str) {
else if (quotes === chr) {
quotes = false;
finishWord();
finishWord(i - 1);
}
else {
// 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;
}
else if (chr === ' ') {
finishWord();
continue;
}
else if (['(', ')', '.'].includes(chr)) {
finishWord();
currentWord += chr;
finishWord();
finishWord(i - 1);
continue;
}
else if (previousOperatorSymbol() !== isOperatorSymbol(chr)) {
finishWord();
finishWord(i - 1);
currentWord += chr;
continue;
@ -104,44 +105,46 @@ function lexer(str) {
currentWord += chr;
}
finishWord();
finishWord(str.length - 1);
return expressionTokens;
return tokens;
}
function parser(tokens) {
const attrs = [];
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 = {
type: 'label',
name: token.substr(1),
isInheritable: false // FIXME
name: text.substr(1),
isInheritable: false, // FIXME
startIndex,
endIndex
};
if (tokens[i + 1] === "=") {
if (i + 1 < tokens.length && tokens[i + 1].text === "=") {
if (i + 2 >= tokens.length) {
throw new Error(`Missing value for label "${token}"`);
throw new Error(`Missing value for label "${text}"`);
}
i += 2;
attr.value = tokens[i];
attr.value = tokens[i].text;
}
attrs.push(attr);
}
else if (token.startsWith('~')) {
if (i + 2 >= tokens.length || tokens[i + 1] !== '=') {
throw new Error(`Relation "${token}" should point to a note.`);
else if (text.startsWith('~')) {
if (i + 2 >= tokens.length || tokens[i + 1].text !== '=') {
throw new Error(`Relation "${text}" should point to a note.`);
}
i += 2;
let notePath = tokens[i];
let notePath = tokens[i].text;
if (notePath.startsWith("#")) {
notePath = notePath.substr(1);
}
@ -150,15 +153,17 @@ function parser(tokens) {
const attr = {
type: 'relation',
name: token.substr(1),
name: text.substr(1),
isInheritable: false, // FIXME
value: noteId
value: noteId,
startIndex,
endIndex
};
attrs.push(attr);
}
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.
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], {
removePlugins: [
'Enter',
@ -168,6 +173,9 @@ export default class NoteAttributesWidget extends TabAwareWidget {
});
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) {