pixelplanet/src/core/MString.js
2022-01-03 11:02:07 +01:00

216 lines
4.7 KiB
JavaScript

/*
* class for string iterations
* that is used by MarkdownParser.js
*/
export default class MString {
constructor(text, start) {
this.txt = text;
this.iter = start || 0;
}
done() {
return (this.iter >= this.txt.length);
}
moveForward() {
this.iter += 1;
return (this.iter < this.txt.length);
}
setIter(iter) {
this.iter = iter;
}
getChar() {
return this.txt[this.iter];
}
slice(start, end) {
return this.txt.slice(start, end || this.iter);
}
has(str) {
return this.txt.startsWith(str, this.iter);
}
move(cnt) {
this.iter += cnt;
return (this.iter < this.txt.length);
}
skipSpaces(skipNewlines = false) {
for (;this.iter < this.txt.length; this.iter += 1) {
const chr = this.txt[this.iter];
if (chr !== ' ' && chr !== '\t' && (!skipNewlines || chr !== '\n')) {
break;
}
}
}
countRepeatingCharacters() {
const chr = this.getChar();
let newIter = this.iter + 1;
for (;newIter < this.txt.length && this.txt[newIter] === chr;
newIter += 1
);
return newIter - this.iter;
}
moveToNextLine() {
const lineEnd = this.txt.indexOf('\n', this.iter);
if (lineEnd === -1) {
this.iter = this.txt.length;
} else {
this.iter = lineEnd + 1;
}
}
getLine() {
const startLine = this.iter;
this.moveToNextLine();
return this.txt.slice(startLine, this.iter);
}
getIndent(tabWidth) {
let indent = 0;
while (this.iter < this.txt.length) {
const chr = this.getChar();
if (chr === '\t') {
indent += tabWidth;
} else if (chr === ' ') {
indent += 1;
} else {
break;
}
this.iter += 1;
}
return indent;
}
goToCharInLine(chr) {
let { iter } = this;
for (;
iter < this.txt.length && this.txt[iter] !== '\n'
&& this.txt[iter] !== chr;
iter += 1
);
if (this.txt[iter] === chr) {
this.iter = iter;
return iter;
}
return false;
}
static isWhiteSpace(chr) {
return (chr === ' ' || chr === '\t' || chr === '\n');
}
/*
* check if the current '[' is part of a [y](z) enclosure
* returns [y, z] if it is enclosure, null otherwise
* moves iter to last closing braked if it is enclosure
*/
checkIfEnclosure(zIsLink) {
const yStart = this.iter + 1;
let yEnd = yStart;
while (this.txt[yEnd] !== ']') {
const chr = this.txt[yEnd];
if (yEnd >= this.txt.length
|| chr === '\n'
|| chr === '['
|| chr === '('
) {
return null;
}
yEnd += 1;
}
let zStart = yEnd + 1;
if (this.txt[zStart] !== '(') {
return null;
}
zStart += 1;
let zEnd = zStart;
let z = null;
while (this.txt[zEnd] !== ')') {
const chr = this.txt[zEnd];
if (zEnd >= this.txt.length
|| chr === '\n'
|| chr === '['
|| chr === '('
) {
return null;
}
if (zIsLink && chr === ':') {
// set this.iter temporarily to be able to use thischeckIfLink
const oldIter = this.iter;
this.iter = zEnd;
z = this.checkIfLink();
zEnd = this.iter;
this.iter = oldIter;
if (z === null) {
return null;
}
continue;
}
zEnd += 1;
}
if (zEnd < zStart + 1 || (!z && zIsLink)) {
return null;
}
if (!zIsLink) {
z = this.txt.slice(zStart, zEnd);
}
const y = this.txt.slice(yStart, yEnd);
this.iter = zEnd;
return [y, z];
}
/*
* Convoluted way to check if the current ':' is part of a link
* we do not check for a 'http' because we might support application links
* like tg://... or discord://..
* returns the link or false if there is none
* moves iter forward to after the link, if there's one
*/
checkIfLink() {
let cIter = this.iter;
if (!this.txt.startsWith('://', cIter) || cIter < 3) {
return null;
}
let linkStart = cIter - 1;
for (; linkStart >= 0
&& !MString.isWhiteSpace(this.txt[linkStart])
&& this.txt[linkStart] !== '('; linkStart -= 1);
linkStart += 1;
cIter += 3;
for (; cIter < this.txt.length
&& !MString.isWhiteSpace(this.txt[cIter])
&& this.txt[cIter] !== ')'; cIter += 1
);
if (cIter < this.iter + 4) {
return null;
}
/* special case where someone pasted a http link after a text
* without space in between
*/
let link = this.txt.slice(linkStart, cIter);
const httpOc = link.indexOf('http');
if (httpOc !== -1 && httpOc !== 0) {
linkStart += httpOc;
link = this.txt.slice(linkStart, cIter);
}
this.iter = cIter;
return link;
}
}