this.toHtmlPage = function (mode, caption, title) {
return utils.htmlToPage(this.toHtml(mode, caption), title);
};
const htmlHeader = function (mode, caption) {
let modeName;
switch (mode) {
case MODE_HEX:
modeName = 'hexadecimal';
break;
case MODE_DEC:
modeName = 'decimal';
break;
case MODE_ASCII:
modeName = 'ASCII';
break;
case MODE_UNICODE:
modeName = 'UNICODE';
break;
default:
throw new Error(`${thisFileName}htmlHeader: unrecognized mode: ${mode}`);
}
let header = '';
header += `<p>display mode: ${modeName}</p>\n`;
header += `<table class="${style.CLASS_TRACE}">\n`;
if (typeof caption === 'string') {
header += `<caption>${caption}</caption>`;
}
return header;
};
const htmlFooter = function () {
let footer = '';
footer += '</table>\n';
footer += `<p class="${style.CLASS_MONOSPACE}">legend:<br>\n`;
footer += '(a) - line number<br>\n';
footer += '(b) - matching line number<br>\n';
footer += '(c) - phrase offset<br>\n';
footer += '(d) - phrase length<br>\n';
footer += '(e) - tree depth<br>\n';
footer += '(f) - operator state<br>\n';
footer += ` - <span class="${style.CLASS_ACTIVE}">↓</span> phrase opened<br>\n`;
footer += ` - <span class="${style.CLASS_MATCH}">↑M</span> phrase matched<br>\n`;
footer += ` - <span class="${style.CLASS_EMPTY}">↑E</span> empty phrase matched<br>\n`;
footer += ` - <span class="${style.CLASS_NOMATCH}">↑N</span> phrase not matched<br>\n`;
footer +=
'operator - ALT, CAT, REP, RNM, TRG, TLS, TBS<sup>†</sup>, UDT, AND, NOT, BKA, BKN, BKR, ABG, AEN<sup>‡</sup><br>\n';
footer += `phrase - up to ${MAX_PHRASE} characters of the phrase being matched<br>\n`;
footer += ` - <span class="${style.CLASS_MATCH}">matched characters</span><br>\n`;
footer += ` - <span class="${style.CLASS_LOOKAHEAD}">matched characters in look ahead mode</span><br>\n`;
footer += ` - <span class="${style.CLASS_LOOKBEHIND}">matched characters in look behind mode</span><br>\n`;
footer += ` - <span class="${style.CLASS_REMAINDER}">remainder characters(not yet examined by parser)</span><br>\n`;
footer += ` - <span class="${style.CLASS_CTRLCHAR}">control characters, TAB, LF, CR, etc. (ASCII mode only)</span><br>\n`;
footer += ` - ${PHRASE_EMPTY} empty string<br>\n`;
footer += ` - ${PHRASE_END} end of input string<br>\n`;
footer += ` - ${PHRASE_CONTINUE} input string display truncated<br>\n`;
footer += '</p>\n';
footer += `<p class="${style.CLASS_MONOSPACE}">\n`;
footer += '<sup>†</sup>original ABNF operators:<br>\n';
footer += 'ALT - alternation<br>\n';
footer += 'CAT - concatenation<br>\n';
footer += 'REP - repetition<br>\n';
footer += 'RNM - rule name<br>\n';
footer += 'TRG - terminal range<br>\n';
footer += 'TLS - terminal literal string (case insensitive)<br>\n';
footer += 'TBS - terminal binary string (case sensitive)<br>\n';
footer += '<br>\n';
footer += '<sup>‡</sup>super set SABNF operators:<br>\n';
footer += 'UDT - user-defined terminal<br>\n';
footer += 'AND - positive look ahead<br>\n';
footer += 'NOT - negative look ahead<br>\n';
footer += 'BKA - positive look behind<br>\n';
footer += 'BKN - negative look behind<br>\n';
footer += 'BKR - back reference<br>\n';
footer += 'ABG - anchor - begin of input string<br>\n';
footer += 'AEN - anchor - end of input string<br>\n';
footer += '</p>\n';
return footer;
};
this.indent = function (depth) {
let html = '';
for (let i = 0; i < depth; i += 1) {
html += '.';
}
return html;
};
const displayTrg = function (mode, op) {
let html = '';
if (op.type === id.TRG) {
if (mode === MODE_HEX || mode === MODE_UNICODE) {
let hex = op.min.toString(16).toUpperCase();
if (hex.length % 2 !== 0) {
hex = `0${hex}`;
}
html += mode === MODE_HEX ? '%x' : 'U+';
html += hex;
hex = op.max.toString(16).toUpperCase();
if (hex.length % 2 !== 0) {
hex = `0${hex}`;
}
html += `–${hex}`;
} else {
html = `%d${op.min.toString(10)}–${op.max.toString(10)}`;
}
}
return html;
};
const displayRep = function (mode, op) {
let html = '';
if (op.type === id.REP) {
if (mode === MODE_HEX) {
let hex = op.min.toString(16).toUpperCase();
if (hex.length % 2 !== 0) {
hex = `0${hex}`;
}
html = `x${hex}`;
if (op.max < Infinity) {
hex = op.max.toString(16).toUpperCase();
if (hex.length % 2 !== 0) {
hex = `0${hex}`;
}
} else {
hex = 'inf';
}
html += `–${hex}`;
} else if (op.max < Infinity) {
html = `${op.min.toString(10)}–${op.max.toString(10)}`;
} else {
html = `${op.min.toString(10)}–inf`;
}
}
return html;
};
const displayTbs = function (mode, op) {
let html = '';
if (op.type === id.TBS) {
const len = Math.min(op.string.length, MAX_TLS * 2);
if (mode === MODE_HEX || mode === MODE_UNICODE) {
html += mode === MODE_HEX ? '%x' : 'U+';
for (let i = 0; i < len; i += 1) {
let hex;
if (i > 0) {
html += '.';
}
hex = op.string[i].toString(16).toUpperCase();
if (hex.length % 2 !== 0) {
hex = `0${hex}`;
}
html += hex;
}
} else {
html = '%d';
for (let i = 0; i < len; i += 1) {
if (i > 0) {
html += '.';
}
html += op.string[i].toString(10);
}
}
if (len < op.string.length) {
html += PHRASE_CONTINUE;
}
}
return html;
};
const displayTls = function (mode, op) {
let html = '';
if (op.type === id.TLS) {
const len = Math.min(op.string.length, MAX_TLS);
if (mode === MODE_HEX || mode === MODE_DEC) {
let charu;
let charl;
let base;
if (mode === MODE_HEX) {
html = '%x';
base = 16;
} else {
html = '%d';
base = 10;
}
for (let i = 0; i < len; i += 1) {
if (i > 0) {
html += '.';
}
charl = op.string[i];
if (charl >= 97 && charl <= 122) {
charu = charl - 32;
html += `${charu.toString(base)}/${charl.toString(base)}`.toUpperCase();
} else if (charl >= 65 && charl <= 90) {
charu = charl;
charl += 32;
html += `${charu.toString(base)}/${charl.toString(base)}`.toUpperCase();
} else {
html += charl.toString(base).toUpperCase();
}
}
if (len < op.string.length) {
html += PHRASE_CONTINUE;
}
} else {
html = '"';
for (let i = 0; i < len; i += 1) {
html += utils.asciiChars[op.string[i]];
}
if (len < op.string.length) {
html += PHRASE_CONTINUE;
}
html += '"';
}
}
return html;
};
const subPhrase = function (mode, charsArg, index, length, prev) {
if (length === 0) {
return '';
}
let phrase = '';
const comma = prev ? ',' : '';
switch (mode) {
case MODE_HEX:
phrase = comma + utils.charsToHex(charsArg, index, length);
break;
case MODE_DEC:
if (prev) {
return `,${utils.charsToDec(charsArg, index, length)}`;
}
phrase = comma + utils.charsToDec(charsArg, index, length);
break;
case MODE_UNICODE:
phrase = utils.charsToUnicode(charsArg, index, length);
break;
case MODE_ASCII:
default:
phrase = utils.charsToAsciiHtml(charsArg, index, length);
break;
}
return phrase;
};
const displayBehind = function (mode, charsArg, state, index, length, anchor) {
let html = '';
let beg1;
let len1;
let beg2;
let len2;
let lastchar = PHRASE_END;
const spanBehind = `<span class="${style.CLASS_LOOKBEHIND}">`;
const spanRemainder = `<span class="${style.CLASS_REMAINDER}">`;
const spanend = '</span>';
let prev = false;
switch (state) {
case id.EMPTY:
html += PHRASE_EMPTY;
case id.NOMATCH:
case id.MATCH:
case id.ACTIVE:
beg1 = index - length;
len1 = anchor - beg1;
beg2 = anchor;
len2 = charsArg.length - beg2;
break;
default:
throw new Error('unrecognized state');
}
lastchar = PHRASE_END;
if (len1 > MAX_PHRASE) {
len1 = MAX_PHRASE;
lastchar = PHRASE_CONTINUE;
len2 = 0;
} else if (len1 + len2 > MAX_PHRASE) {
lastchar = PHRASE_CONTINUE;
len2 = MAX_PHRASE - len1;
}
if (len1 > 0) {
html += spanBehind;
html += subPhrase(mode, charsArg, beg1, len1, prev);
html += spanend;
prev = true;
}
if (len2 > 0) {
html += spanRemainder;
html += subPhrase(mode, charsArg, beg2, len2, prev);
html += spanend;
}
return html + lastchar;
};
const displayForward = function (mode, charsArg, state, index, length, spanAhead) {
let html = '';
let beg1;
let len1;
let beg2;
let len2;
let lastchar = PHRASE_END;
const spanRemainder = `<span class="${style.CLASS_REMAINDER}">`;
const spanend = '</span>';
let prev = false;
switch (state) {
case id.EMPTY:
html += PHRASE_EMPTY;
case id.NOMATCH:
case id.ACTIVE:
beg1 = index;
len1 = 0;
beg2 = index;
len2 = charsArg.length - beg2;
break;
case id.MATCH:
beg1 = index;
len1 = length;
beg2 = index + len1;
len2 = charsArg.length - beg2;
break;
default:
throw new Error('unrecognized state');
}
lastchar = PHRASE_END;
if (len1 > MAX_PHRASE) {
len1 = MAX_PHRASE;
lastchar = PHRASE_CONTINUE;
len2 = 0;
} else if (len1 + len2 > MAX_PHRASE) {
lastchar = PHRASE_CONTINUE;
len2 = MAX_PHRASE - len1;
}
if (len1 > 0) {
html += spanAhead;
html += subPhrase(mode, charsArg, beg1, len1, prev);
html += spanend;
prev = true;
}
if (len2 > 0) {
html += spanRemainder;
html += subPhrase(mode, charsArg, beg2, len2, prev);
html += spanend;
}
return html + lastchar;
};
const displayAhead = function (mode, charsArg, state, index, length) {
const spanAhead = `<span class="${style.CLASS_LOOKAHEAD}">`;
return displayForward(mode, charsArg, state, index, length, spanAhead);
};
const displayNone = function (mode, charsArg, state, index, length) {
const spanAhead = `<span class="${style.CLASS_MATCH}">`;
return displayForward(mode, charsArg, state, index, length, spanAhead);
};
const htmlTable = function (mode) {
if (rules === null) {
return '';
}
let html = '';
let thisLine;
let thatLine;
let lookAhead;
let lookBehind;
let lookAround;
let anchor;
html += '<tr><th>(a)</th><th>(b)</th><th>(c)</th><th>(d)</th><th>(e)</th><th>(f)</th>';
html += '<th>operator</th><th>phrase</th></tr>\n';
circular.forEach((lineIndex) => {
const line = records[lineIndex];
thisLine = line.thisLine;
thatLine = line.thatLine !== undefined ? line.thatLine : '--';
lookAhead = false;
lookBehind = false;
lookAround = false;
if (line.lookAround === id.LOOKAROUND_AHEAD) {
lookAhead = true;
lookAround = true;
anchor = line.lookAnchor;
}
if (line.opcode.type === id.AND || line.opcode.type === id.NOT) {
lookAhead = true;
lookAround = true;
anchor = line.phraseIndex;
}
if (line.lookAround === id.LOOKAROUND_BEHIND) {
lookBehind = true;
lookAround = true;
anchor = line.lookAnchor;
}
if (line.opcode.type === id.BKA || line.opcode.type === id.BKN) {
lookBehind = true;
lookAround = true;
anchor = line.phraseIndex;
}
html += '<tr>';
html += `<td>${thisLine}</td><td>${thatLine}</td>`;
html += `<td>${line.phraseIndex}</td>`;
html += `<td>${line.phraseLength}</td>`;
html += `<td>${line.depth}</td>`;
html += '<td>';
switch (line.state) {
case id.ACTIVE:
html += `<span class="${style.CLASS_ACTIVE}">↓ </span>`;
break;
case id.MATCH:
html += `<span class="${style.CLASS_MATCH}">↑M</span>`;
break;
case id.NOMATCH:
html += `<span class="${style.CLASS_NOMATCH}">↑N</span>`;
break;
case id.EMPTY:
html += `<span class="${style.CLASS_EMPTY}">↑E</span>`;
break;
default:
html += `<span class="${style.CLASS_ACTIVE}">--</span>`;
break;
}
html += '</td>';
html += '<td>';
html += that.indent(line.depth);
if (lookAhead) {
html += `<span class="${style.CLASS_LOOKAHEAD}">`;
} else if (lookBehind) {
html += `<span class="${style.CLASS_LOOKBEHIND}">`;
}
html += utils.opcodeToString(line.opcode.type);
if (line.opcode.type === id.RNM) {
html += `(${rules[line.opcode.index].name}) `;
}
if (line.opcode.type === id.BKR) {
const casetype = line.opcode.bkrCase === id.BKR_MODE_CI ? '%i' : '%s';
const modetype = line.opcode.bkrMode === id.BKR_MODE_UM ? '%u' : '%p';
html += `(\\${casetype}${modetype}${rules[line.opcode.index].name}) `;
}
if (line.opcode.type === id.UDT) {
html += `(${udts[line.opcode.index].name}) `;
}
if (line.opcode.type === id.TRG) {
html += `(${displayTrg(mode, line.opcode)}) `;
}
if (line.opcode.type === id.TBS) {
html += `(${displayTbs(mode, line.opcode)}) `;
}
if (line.opcode.type === id.TLS) {
html += `(${displayTls(mode, line.opcode)}) `;
}
if (line.opcode.type === id.REP) {
html += `(${displayRep(mode, line.opcode)}) `;
}
if (lookAround) {
html += '</span>';
}
html += '</td>';
html += '<td>';
if (lookBehind) {
html += displayBehind(mode, chars, line.state, line.phraseIndex, line.phraseLength, anchor);
} else if (lookAhead) {
html += displayAhead(mode, chars, line.state, line.phraseIndex, line.phraseLength);
} else {
html += displayNone(mode, chars, line.state, line.phraseIndex, line.phraseLength);
}
html += '</td></tr>\n';
});
html += '<tr><th>(a)</th><th>(b)</th><th>(c)</th><th>(d)</th><th>(e)</th><th>(f)</th>';
html += '<th>operator</th><th>phrase</th></tr>\n';
html += '</table>\n';
return html;
};