UNPKG

razjs

Version:

JS-version of the Razor-Express library (Razor-like template engine for NodeJS Express).

1,379 lines (1,191 loc) 97.6 kB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ module.exports = class HtmlString { constructor(html) { this.html = html; } toString() { return this.html; } } },{}],2:[function(require,module,exports){ 'use strict'; window.raz = { set debug(value) { require('../core/dbg/debugger').isDebugMode = value; }, get debug(){ return require('../core/dbg/debugger').isDebugMode; }, render(template, model) { if (!this.parser) this.parser = require('./parser')(); return this.parser.compileSync(template, model); } } },{"../core/dbg/debugger":3,"./parser":8}],3:[function(require,module,exports){ module.exports = { isDebugMode: false, isBrowser: (typeof window !== 'undefined') } },{}],4:[function(require,module,exports){ const htmlEncode = require('../libs/js-htmlencode'); class RazorError extends Error { constructor(message, captureFrame) { super(message); if (Error.captureStackTrace) Error.captureStackTrace(this, captureFrame || this.constructor); } static new(args) { let exc = new RazorError(args.message, args.capture || this.new); this.extend(exc, args); return exc; } static extend(exc, args) { const { isDebugMode, isBrowser } = require('../dbg/debugger'); exc.isRazorError = true; if (!isDebugMode) { exc.html = () => { const errorRefUrl = (isBrowser) ? "https://www.npmjs.com/package/razjs#example-2-handling-and-displaying-errors" : "https://github.com/DevelAx/RazorExpress/blob/master/docs/Debugging.md#production--development-modes"; const error = `Razor template compilation error occured.<br/>Turn <a href="${errorRefUrl}" target="_blank">DEBUG MODE</a> on to get details.`; if (isBrowser) return `<div style="color: red;">${htmlEncode(exc.message)}</div><hr/>${error}`; else return error; } return; } if (exc.data) { var oldData = exc.data; } exc.data = Object.assign({ line: args.line, pos: args.pos, len: args.len }, args.info); if (exc.__dbg && exc.__dbg.pos) exc.data = Object.assign({ posRange: { start: exc.__dbg.pos.start, end: exc.__dbg.pos.end } }, exc.data); if (oldData) exc.data.inner = oldData; if (!exc.html) exc.html = RazorError.prototype.html; } html() { let codeHtml = '', mainInfo = { title: '' }; let stackHtml = stackToHtml(this, this.data, mainInfo); for (var data = this.data; data; data = data.inner) { if (Utils.isServer) codeHtml += "<div class='arrow'>&#8681;</div>" codeHtml += dataToHtml(data, mainInfo); codeHtml += '<hr />' } var html = ` <!DOCTYPE html> <html> <head> <style type="text/css" scoped> h1, h2, h3{ font-weight: normal; } div.filepath { font-weight: bold; font-size: 1.05rem; } body { font-size: .813em; font-family: Consolas, "Courier New", courier, monospace; } .stack{ color: orange; white-space: pre; } .stack .error{ color: #e20000; white-space: pre-wrap; font-weight: bold; } ol { color: darkgray; } ol li { background-color: #fbfbfb; white-space: pre; } ol li.highlight{ color: red;// darkslategray; background-color: #f8f9b3; } ol li span { color: #196684;//darkslategray; } ol li.highlight span { color: black; } ol li span.highlight { border: solid 1px red; } ol li span.multilight { background-color: #ebef00; font-weight: bold; color: red; } ol li.comment { color: lightgray; } ol li.comment span { color: darkgray; } .arrow{ font-size: 1.5rem; color: orange; margin-left: 1rem; } hr { opacity: 0.5; } </style> </head> <body> <h1>A template compilation error occured</h1> <div class="stack">${stackHtml}</div> <hr /> ${codeHtml} </body> </html> `; return html; } } module.exports = RazorError; function stackToHtml(exc, data, mainInfo) { let lines = exc.stack.split('\n'); let fireFox = (typeof navigator !== 'undefined') && navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; // for compatibility with FireFox if (fireFox) { let message = `${exc.name}: ${exc.message}`; lines.unshift(message); } let html = '<div>'; for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (fireFox){ let parts = line.split('@'); if (parts.length === 2){ if (!parts[0]) // empty parts[0] = "<anonymous>"; line = `at ${parts[0]} (${parts[1]})`; } } else if (i === 0 && (line.startsWith("evalmachine.") || line.startsWith("undefined:"))) { let nextLineExists = i < lines.length + 1; if (nextLineExists && data.jshtml && data.posRange) { // This is likely HTML parsing error (not code runtime error). // Let's try to narrow the error area by the data from the stack. let codeLine = lines[i + 1].trimRight(); let errorCodeFragment = data.jshtml.substring(data.posRange.start, data.posRange.end); let codePos = errorCodeFragment.indexOf(codeLine); // Check if it exists and only once in the `errorCodeFragment`. if (codePos !== -1 && codePos === errorCodeFragment.lastIndexOf(codeLine)) { // Set a more precise location of the error. data.posRange.start = data.posRange.start + codePos; data.posRange.end = data.posRange.start + codeLine.length; // Include '@' symbol. if (data.posRange.start > 0 && data.jshtml[data.posRange.start - 1] === '@') data.posRange.start -= 1; } } continue; // skip the very first line like "evalmachine.<anonymous>:22" } if (mainInfo.title && !pointer){ var trim = line.substring(dLen); var pointer = trim; } else{ trim = line.trim(); } var dLen = line.length - trim.length; let encodedLine = htmlEncode(trim); let style = ''; if (trim && trim !== '^' && !trim.startsWith("at ")) { if (trim.startsWith('RazorError') || mainInfo.title){ style = 'id="error" class="error"'; // the second line is the error description } else { mainInfo.errorLine = trim; style = 'class="error"'; } if (mainInfo.title) mainInfo.title += '\r\n'; mainInfo.title += encodedLine; } html += `<span ${style}>${encodedLine}</span><br/>`; } html += '</div>'; return html; } function dataToHtml(data, mainInfo) { let html; if (data.jshtml) { let textCursor = 0; lines = data.jshtml.split('\n'); let startLine = data.startLine ? data.startLine : 0; html = `<ol start='${startLine}'>`; let isLastData = !data.inner; let hasErrorCoordinates = data.posRange && data.posRange.start || data.pos; if (isLastData && !hasErrorCoordinates && mainInfo.errorLine) { let occur = data.jshtml.numberOfOccurrences(mainInfo.errorLine); if (occur.num === 1) { let extend = 0; if (occur.pos > 0 && data.jshtml[occur.pos - 1] === '@') extend = 1; // Include the '@' symbol for beauty. data.posRange = { start: occur.pos - extend, end: occur.pos + mainInfo.errorLine.length }; } } for (let i = 0; i < lines.length; i++) { let line = lines[i]; let highlight, htmlLine, comment, multilight; let textCursorEnd = textCursor + line.length + 1; // + '\n' if (data.posRange && data.posRange.start < data.posRange.end) { if (data.posRange.start >= textCursor && data.posRange.start < textCursorEnd) { var pos = data.posRange.start - textCursor; if (data.posRange.end < textCursorEnd) { var len = data.posRange.end - data.posRange.start; data.posRange = null; // prevent further useless computation during the next iterations of this cycle } else { len = line.length; data.posRange.start = textCursorEnd; // move to the beginning of the next line } multilight = "multilight"; } } else if (data.line === i) { pos = data.pos; len = data.len || 1; } if (pos != null && typeof pos !== 'undefined') { if (pos < line.length) { let start = htmlEncode(line.substring(0, pos)); let one = htmlEncode(line.substring(pos, pos + len)); let end = htmlEncode(line.substring(pos + len)); htmlLine = `<span>${start}</span><span class='${multilight || "highlight"} source-error' title='${mainInfo.title}'>${one}</span><span>${end}</span>`; highlight = "class='highlight'"; } pos = null; } else { let trim = line.trim(); if (trim.length > 6 && trim.startsWith("<!--") && trim.endsWith("-->")) comment = "class='comment'"; //htmlLine = `<span class="comment">${htmlEncode(trim)}</span>`; } html += `<li ${comment || highlight}><span>`; html += htmlLine ? htmlLine : htmlEncode(line); html += "</span></li>"; textCursor = textCursorEnd; }// for //let fileFolder = path.dirname(data.filename); let fileName = `<div class="filepath">${Utils.isServer ? Utils.path.basename(data.filename) : "Template:"}</div>`; html += "</ol>"; html = ` <div class="code"> ${fileName} ${html} </div> `; }// if (this.data.jshtml) return html; } // /** // * HELPERS // */ // function getIndentifier(codeLine, startPos){ // let ch = codeLine[startPos]; // let isIdn = Char.isLetter(ch) || '_$'.includes(ch); // is it identifier // let result = ch; // for(let i = startPos + 1, ch = codeLine[i]; i < codeLine.length && (isIdn ? Char.isIdentifier(ch) : !Char.isIdentifier(ch)); i++, ch = codeLine[i]) // result += ch; // return result; // } },{"../dbg/debugger":3,"../libs/js-htmlencode":7}],5:[function(require,module,exports){ const RazorError = require('./RazorError'); class ParserErrorFactory { constructor(templateInfo, linesBaseNumber) { this.startLineNum = linesBaseNumber; this.info = templateInfo; this.info.startLine = linesBaseNumber; } endOfFileFoundAfterAtSign(lineNum, posNum) { var message = `End-of-file was found after the "@" character at line ${lineNum + this.startLineNum} pos ${posNum + 1}. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"`; return RazorError.new({ message, info: this.info, line: lineNum, pos: posNum, capture: this.endOfFileFoundAfterAtSign }); } unexpectedCharacter(ch, lineNum, posNum, line) { var message = `Unexpected '${ch}' character at line ${lineNum + this.startLineNum} pos ${posNum + 1} after '${line}'`; return RazorError.new({ message, info: this.info, line: lineNum, pos: posNum, capture: this.unexpectedCharacter }); } unexpectedAtCharacter(lineNum, posNum) { var message = `Unexpected '@' character at line ${lineNum + this.startLineNum} pos ${posNum + 1}. Once inside the body of a code block (@if {}, @{}, etc.) or a section (@section{}) you do not need to use "@" character to switch to code.`; return RazorError.new({ message, info: this.info, line: lineNum, pos: posNum, capture: this.unexpectedAtCharacter }); } notValidStartOfCodeBlock(ch, lineNum, posNum) { var message = `"${ch}" is not valid at the start of a code block at line ${lineNum + this.startLineNum} pos ${posNum + 1}. Only identifiers, keywords, "(" and "{" are valid.`; return RazorError.new({ message, info: this.info, line: lineNum, pos: posNum, capture: this.notValidStartOfCodeBlock }); } unexpectedEndOfFile(text) { var message = `Unexpected end of file after '${text}'.`; return RazorError.new({ message, info: this.info, capture: this.unexpectedEndOfFile }); } characterExpected(ch, line, pos) { var message = `'${ch}' character is expected at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.characterExpected }); } characterExpectedAfter(ch, line, pos, after) { var message = `'${ch}' character is expected after '${after}' at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.characterExpectedAfter }); } expressionMissingEnd(expr, ch, line, pos) { var message = `The explicit expression "${expr}" is missing a closing character "${ch}" at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.expressionMissingEnd }); } jsCodeBlockMissingClosingChar(line, codeFirstLine) { var message = `The code or section block is missing a closing "}" character. Make sure you have a matching "}" character for all the "{" characters within this block, and that none of the "}" characters are being interpreted as markup. The block starts at line ${line + this.startLineNum} with text: "${codeFirstLine}"`; return RazorError.new({ message, info: this.info, line, capture: this.jsCodeBlockMissingClosingChar }); } wordExpected(word, line, pos, len) { var message = `'${word}' expected at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, len, capture: this.wordExpected }); } missingMatchingStartTag(tag, line, pos) { var message = `'${tag}' tag at line ${line + this.startLineNum} pos ${pos + 1} is missing matching start tag.`; return RazorError.new({ message, info: this.info, line, pos, len: tag.length, capture: this.missingMatchingStartTag }); } missingMatchingEndTag(tag, line, pos) { var message = `'${tag}' tag at line ${line + this.startLineNum} pos ${pos + 1} is missing matching end tag.`; return RazorError.new({ message, info: this.info, line, pos, len: tag.length, capture: this.missingMatchingEndTag }); } invalidExpressionChar(ch, line, pos, afterText) { var message = `Invalid "${ch}" symbol in expression at line ${line + this.startLineNum} pos ${pos + 1}` + (afterText ? ` after "${afterText}".` : "."); return RazorError.new({ message, info: this.info, line, pos, capture: this.invalidExpressionChar }); } invalidHtmlTag(tag, line, pos) { var message = `Invalid HTML-tag: '${tag}'`; return RazorError.new({ message, info: this.info, line, pos, len: tag && tag.length, capture: this.invalidHtmlTag }); } // forbiddenViewName(viewName) { // var message = `The file "${viewName}" is not available.`; // return new RazorError(message, this.info); // } whiteSpaceExpectedAfter(keyword, line, pos) { var message = `A whitespace expected after the "${keyword}" keyword at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.whiteSpaceExpectedAfter }); // cannot be tested. } tagNameExpected(line, pos) { var message = `Tag name expected at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.tagNameExpected }); } sectionNameExpectedAfter(keyword, line, pos) { var message = `A section name expected after the "${keyword}" keyword at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.sectionNameExpectedAfter }); } sectionNameCannotStartWith(ch, line, pos) { var message = `A section name cannot start with '${ch}' at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.sectionNameCannotStartWith }); } sectionNameCannotInclude(ch, line, pos) { var message = `A section name cannot include '${ch}' character at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.sectionNameCannotInclude }); } unexpectedLiteralFollowingTheSection(ch, line, pos) { var message = `Unexpected literal '${ch}' following the 'section' directive at line ${line + this.startLineNum} pos ${pos + 1}. Expected '{'.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.unexpectedLiteralFollowingTheSection }); } sectionIsAlreadyDefined(sectionName, line, pos, viewFilePath) { var message = `Section '${sectionName}' at line ${line + this.startLineNum} pos ${pos + 1} has been already defined in the file '${viewFilePath}'. You cannot assign the same name to different sections in the same file.`; return RazorError.new({ message, info: this.info, line, pos, len: sectionName.length, capture: this.sectionIsAlreadyDefined }); } // sectionBlockIsMissingClosingBrace(sectionName) { // var message = `The section block '${sectionName}' is missing a closing "}" character.`; // return new RazorError(message, this.info); // } sectionsCannotBeNested(line, pos) { var message = `Section blocks cannot be nested at line ${line + this.startLineNum} pos ${pos + 1}.`; return RazorError.new({ message, info: this.info, line, pos, capture: this.sectionsCannotBeNested }); } sectionIsNotFound(sectionName, filePath) { var message = `View '${filePath}' requires the section '${sectionName}' which cannot be found.`; return RazorError.new({ message, info: this.info, capture: this.sectionIsNotFound }); } sectionIsNotCompiled(sectionName, filePath) { var message = `You try to render the section '${sectionName}' from the '${filePath}' view. This section has not been compiled yet. Make sure it is defined before the '@Html.section' method is called.`; return RazorError.new({ message, info: this.info, capture: this.sectionIsNotCompiled }); } sectionsAlreadyRendered(sectionName, renderedBy, attemptedBy) { var message = `Sections named '${sectionName}' has already been rendered by '${renderedBy}'. There is an atempt to rendered it again by '${attemptedBy}'.`; return RazorError.new({ message, info: this.info, capture: this.sectionsAlreadyRendered }); } sectionNeverRendered(sectionName, viewPath) { var message = `Section '${sectionName}' in '${viewPath}' has never been rendered. If a section exists it must be rendered.`; return RazorError.new({ message, info: this.info, capture: this.sectionNeverRendered }); } partialViewNotFound(partialView, searchedLocations) { let viewTypeName = (this.isLayout) ? "layout" : "partial"; let message = `The view "${this.info.filename}" cannot find the ${viewTypeName} view "${partialView}".\nThe following locations were searched:\n${searchedLocations.map(l => `"${l}"`).join("\n")}`; return RazorError.new({ message, info: this.info, capture: this.partialViewNotFound }); } errorReadingFile(error) { let message = `Reading file '${this.info.filename}' caused an error: ${error}`; let parserError = RazorError.new({ message, info: this.info, capture: this.errorReadingFile }); setInnerError(parserError, error); return parserError; } errorReadingView(filename, error) { let message = `Reading view file '${filename}' caused an error: ${error}`; let parserError = RazorError.new({ message, info: this.info, capture: this.errorReadingView }); setInnerError(parserError, error); return parserError; } partialLayoutNameExpected() { let message = "Partial layout name is expected." return RazorError.new({ message, info: this.info, capture: this.partialLayoutNameExpected }); } // invalidViewExtension(viewName, expectedExtension){ // let message = `The view '${viewName}' includes invalid extension. Expected extension '${expectedExtension}'`; // return new ParserError(message, this.args); // } /** * * Doesn't produce a `ParserError`, just extends the existant one in other prevent VM from adding additional lines to the `.Stack` when rethrowing. */ extendError(exc) { RazorError.extend(exc, { info: this.info }); } } ParserErrorFactory.templateShouldBeString = 'The [template] argument should be a string.'; function setInnerError(parserError, error) { if (error.message) parserError.inner = error; } module.exports = ParserErrorFactory; },{"./RazorError":4}],6:[function(require,module,exports){ module.exports = require("./errors.en"); },{"./errors.en":5}],7:[function(require,module,exports){ /** * [js-htmlencode]{@link https://github.com/emn178/js-htmlencode} * * @version 0.3.0 * @author Chen, Yi-Cyuan [emn178@gmail.com] * @copyright Chen, Yi-Cyuan 2014-2017 * @license MIT */ /*jslint bitwise: true */ (function () { 'use strict'; var HTML_ENTITIES = { '&nbsp;' : '\u00A0', '&iexcl;' : '\u00A1', '&cent;' : '\u00A2', '&pound;' : '\u00A3', '&curren;' : '\u00A4', '&yen;' : '\u00A5', '&brvbar;' : '\u00A6', '&sect;' : '\u00A7', '&uml;' : '\u00A8', '&copy;' : '\u00A9', '&ordf;' : '\u00AA', '&laquo;' : '\u00AB', '&not;' : '\u00AC', '&shy;' : '\u00AD', '&reg;' : '\u00AE', '&macr;' : '\u00AF', '&deg;' : '\u00B0', '&plusmn;' : '\u00B1', '&sup2;' : '\u00B2', '&sup3;' : '\u00B3', '&acute;' : '\u00B4', '&micro;' : '\u00B5', '&para;' : '\u00B6', '&middot;' : '\u00B7', '&cedil;' : '\u00B8', '&sup1;' : '\u00B9', '&ordm;' : '\u00BA', '&raquo;' : '\u00BB', '&frac14;' : '\u00BC', '&frac12;' : '\u00BD', '&frac34;' : '\u00BE', '&iquest;' : '\u00BF', '&Agrave;' : '\u00C0', '&Aacute;' : '\u00C1', '&Acirc;' : '\u00C2', '&Atilde;' : '\u00C3', '&Auml;' : '\u00C4', '&Aring;' : '\u00C5', '&AElig;' : '\u00C6', '&Ccedil;' : '\u00C7', '&Egrave;' : '\u00C8', '&Eacute;' : '\u00C9', '&Ecirc;' : '\u00CA', '&Euml;' : '\u00CB', '&Igrave;' : '\u00CC', '&Iacute;' : '\u00CD', '&Icirc;' : '\u00CE', '&Iuml;' : '\u00CF', '&ETH;' : '\u00D0', '&Ntilde;' : '\u00D1', '&Ograve;' : '\u00D2', '&Oacute;' : '\u00D3', '&Ocirc;' : '\u00D4', '&Otilde;' : '\u00D5', '&Ouml;' : '\u00D6', '&times;' : '\u00D7', '&Oslash;' : '\u00D8', '&Ugrave;' : '\u00D9', '&Uacute;' : '\u00DA', '&Ucirc;' : '\u00DB', '&Uuml;' : '\u00DC', '&Yacute;' : '\u00DD', '&THORN;' : '\u00DE', '&szlig;' : '\u00DF', '&agrave;' : '\u00E0', '&aacute;' : '\u00E1', '&acirc;' : '\u00E2', '&atilde;' : '\u00E3', '&auml;' : '\u00E4', '&aring;' : '\u00E5', '&aelig;' : '\u00E6', '&ccedil;' : '\u00E7', '&egrave;' : '\u00E8', '&eacute;' : '\u00E9', '&ecirc;' : '\u00EA', '&euml;' : '\u00EB', '&igrave;' : '\u00EC', '&iacute;' : '\u00ED', '&icirc;' : '\u00EE', '&iuml;' : '\u00EF', '&eth;' : '\u00F0', '&ntilde;' : '\u00F1', '&ograve;' : '\u00F2', '&oacute;' : '\u00F3', '&ocirc;' : '\u00F4', '&otilde;' : '\u00F5', '&ouml;' : '\u00F6', '&divide;' : '\u00F7', '&oslash;' : '\u00F8', '&ugrave;' : '\u00F9', '&uacute;' : '\u00FA', '&ucirc;' : '\u00FB', '&uuml;' : '\u00FC', '&yacute;' : '\u00FD', '&thorn;' : '\u00FE', '&yuml;' : '\u00FF', '&quot;' : '\u0022', '&amp;' : '\u0026', '&lt;' : '\u003C', '&gt;' : '\u003E', '&apos;' : '\u0027', '&OElig;' : '\u0152', '&oelig;' : '\u0153', '&Scaron;' : '\u0160', '&scaron;' : '\u0161', '&Yuml;' : '\u0178', '&circ;' : '\u02C6', '&tilde;' : '\u02DC', '&ensp;' : '\u2002', '&emsp;' : '\u2003', '&thinsp;' : '\u2009', '&zwnj;' : '\u200C', '&zwj;' : '\u200D', '&lrm;' : '\u200E', '&rlm;' : '\u200F', '&ndash;' : '\u2013', '&mdash;' : '\u2014', '&lsquo;' : '\u2018', '&rsquo;' : '\u2019', '&sbquo;' : '\u201A', '&ldquo;' : '\u201C', '&rdquo;' : '\u201D', '&bdquo;' : '\u201E', '&dagger;' : '\u2020', '&Dagger;' : '\u2021', '&permil;' : '\u2030', '&lsaquo;' : '\u2039', '&rsaquo;' : '\u203A', '&euro;' : '\u20AC', '&fnof;' : '\u0192', '&Alpha;' : '\u0391', '&Beta;' : '\u0392', '&Gamma;' : '\u0393', '&Delta;' : '\u0394', '&Epsilon;' : '\u0395', '&Zeta;' : '\u0396', '&Eta;' : '\u0397', '&Theta;' : '\u0398', '&Iota;' : '\u0399', '&Kappa;' : '\u039A', '&Lambda;' : '\u039B', '&Mu;' : '\u039C', '&Nu;' : '\u039D', '&Xi;' : '\u039E', '&Omicron;' : '\u039F', '&Pi;' : '\u03A0', '&Rho;' : '\u03A1', '&Sigma;' : '\u03A3', '&Tau;' : '\u03A4', '&Upsilon;' : '\u03A5', '&Phi;' : '\u03A6', '&Chi;' : '\u03A7', '&Psi;' : '\u03A8', '&Omega;' : '\u03A9', '&alpha;' : '\u03B1', '&beta;' : '\u03B2', '&gamma;' : '\u03B3', '&delta;' : '\u03B4', '&epsilon;' : '\u03B5', '&zeta;' : '\u03B6', '&eta;' : '\u03B7', '&theta;' : '\u03B8', '&iota;' : '\u03B9', '&kappa;' : '\u03BA', '&lambda;' : '\u03BB', '&mu;' : '\u03BC', '&nu;' : '\u03BD', '&xi;' : '\u03BE', '&omicron;' : '\u03BF', '&pi;' : '\u03C0', '&rho;' : '\u03C1', '&sigmaf;' : '\u03C2', '&sigma;' : '\u03C3', '&tau;' : '\u03C4', '&upsilon;' : '\u03C5', '&phi;' : '\u03C6', '&chi;' : '\u03C7', '&psi;' : '\u03C8', '&omega;' : '\u03C9', '&thetasym;' : '\u03D1', '&upsih;' : '\u03D2', '&piv;' : '\u03D6', '&bull;' : '\u2022', '&hellip;' : '\u2026', '&prime;' : '\u2032', '&Prime;' : '\u2033', '&oline;' : '\u203E', '&frasl;' : '\u2044', '&weierp;' : '\u2118', '&image;' : '\u2111', '&real;' : '\u211C', '&trade;' : '\u2122', '&alefsym;' : '\u2135', '&larr;' : '\u2190', '&uarr;' : '\u2191', '&rarr;' : '\u2192', '&darr;' : '\u2193', '&harr;' : '\u2194', '&crarr;' : '\u21B5', '&lArr;' : '\u21D0', '&uArr;' : '\u21D1', '&rArr;' : '\u21D2', '&dArr;' : '\u21D3', '&hArr;' : '\u21D4', '&forall;' : '\u2200', '&part;' : '\u2202', '&exist;' : '\u2203', '&empty;' : '\u2205', '&nabla;' : '\u2207', '&isin;' : '\u2208', '&notin;' : '\u2209', '&ni;' : '\u220B', '&prod;' : '\u220F', '&sum;' : '\u2211', '&minus;' : '\u2212', '&lowast;' : '\u2217', '&radic;' : '\u221A', '&prop;' : '\u221D', '&infin;' : '\u221E', '&ang;' : '\u2220', '&and;' : '\u2227', '&or;' : '\u2228', '&cap;' : '\u2229', '&cup;' : '\u222A', '&int;' : '\u222B', '&there4;' : '\u2234', '&sim;' : '\u223C', '&cong;' : '\u2245', '&asymp;' : '\u2248', '&ne;' : '\u2260', '&equiv;' : '\u2261', '&le;' : '\u2264', '&ge;' : '\u2265', '&sub;' : '\u2282', '&sup;' : '\u2283', '&nsub;' : '\u2284', '&sube;' : '\u2286', '&supe;' : '\u2287', '&oplus;' : '\u2295', '&otimes;' : '\u2297', '&perp;' : '\u22A5', '&sdot;' : '\u22C5', '&lceil;' : '\u2308', '&rceil;' : '\u2309', '&lfloor;' : '\u230A', '&rfloor;' : '\u230B', '&lang;' : '\u2329', '&rang;' : '\u232A', '&loz;' : '\u25CA', '&spades;' : '\u2660', '&clubs;' : '\u2663', '&hearts;' : '\u2665', '&diams;' : '\u2666' }; var decodeEntity = function (code) { // name type if (code.charAt(1) !== '#') { return HTML_ENTITIES[code] || code; } var n, c = code.charAt(2); // hex number if (c === 'x' || c === 'X') { c = code.substring(3, code.length - 1); n = parseInt(c, 16); } else { c = code.substring(2, code.length - 1); n = parseInt(c); } return isNaN(n) ? code : String.fromCharCode(n); }; var htmlEncode = function (str) { return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;') .replace(/</g, '&lt;').replace(/>/g, '&gt;'); }; var htmlDecode = function (str) { return str.replace(/&#?\w+;/g, decodeEntity); }; module.exports = htmlEncode; htmlEncode.htmlEncode = htmlEncode; htmlEncode.htmlDecode = htmlDecode; })(); },{}],8:[function(require,module,exports){ 'use strict'; require('./utils'); function compilePageSync(html, model, viewData, scope, isDebugMode) { let vm = html._vm; if (vm) { let sandbox = html._sandbox; // Creates cope variables. if (scope) { Object.keys(scope).forEach((k) => { defineConstant(sandbox, k, scope[k]); }); } defineConstant(sandbox, "Html", html); defineConstant(sandbox, "Model", model); defineConstant(sandbox, "ViewData", viewData); defineConstant(sandbox, "debug", isDebugMode); vm.runInNewContext(html._js, sandbox); } else { const argNames = ["Html", "Model", "ViewData", "debug"]; const argValues = [html, model, viewData, isDebugMode]; if (scope) { // Add cope variables (we should but can't make them constants because of `eval` limitation in sctict-mode). Object.keys(scope).forEach((k) => { argNames.push(k); argValues.push(scope[k]); }); } // Put the JS-scipt to be executed. argNames.push(html._js); // Execute JS-script via function with arguments. Function.apply(undefined, argNames).apply(undefined, argValues); } function defineConstant(obj, name, value) { Object.defineProperty(obj, name, { value, writable: false }); } } function compilePage(html, model, viewData, scope, isDebugMode, done) { try { compilePageSync(html, model, viewData, scope, isDebugMode); return html.__renderLayout(done); } catch (exc) { done(exc); } } module.exports = function (opts) { opts = opts || {}; const dbg = require('../core/dbg/debugger'); const debugMode = dbg.isDebugMode; const isBrowser = dbg.isBrowser; const log = opts.log || { debug: () => { } }; log.debug(`Parse debug mode is '${!!debugMode}'.`); const HtmlString = require('./HtmlString'); const htmlEncode = require('./libs/js-htmlencode'); const vm = opts.vm; //////////////////// /// Html class //////////////////// function Html(args) { // Non-user section. this._vm = vm; if (debugMode && !isBrowser) { this._sandbox = Object.create(null); vm.createContext(this._sandbox); } // function (process,...){...}() prevents [this] to exist for the 'vm.runInNewContext()' method this._js = ` 'use strict'; (function (process, window, global, module, require, compilePage, compilePageSync, navigator, undefined) { delete Html._js; delete Html._vm; delete Html._sandbox; ${args.js} }).call();`; // User section. if (debugMode) this.__dbg = { viewName: args.filePath, template: args.template, pos: [] } this.$ = this.layout = null; // Private let sectionName = null; let sections = args.parsedSections; this.__val = function (i) { return args.jsValues.getAt(i); }; this.__renderLayout = (done) => { if (!this.layout) // if the layout is not defined.. return Promise.resolve().then(() => done(null, args.html)), null; // looking for the `Layout`.. args.er.isLayout = true; // the crutch args.findPartial(this.layout, args.filePath, args.er, (err, result) => { args.er.isLayout = false; if (err) return done(err); let compileOpt = { scope: args.scope, template: result.data, filePath: result.filePath, model: args.model, bodyHtml: args.html, findPartial: args.findPartial, findPartialSync: args.findPartialSync, parsedSections: args.parsedSections, partialsCache: args.partialsCache, viewData: args.viewData }; compile(compileOpt, done); }); }; this.__sec = function (name) { // in section if (!sectionName) { sectionName = name; } else if (sectionName === name) { sections[sectionName][args.filePath].compiled = true; sectionName = null; } else { throw new Error(`Unexpected section name = '${name}'.`); // Cannot be tested via user-inputs. } }; this.raw = function (val) { // render if (!isVisibleValue(val)) // 'undefined' can be passed when `Html.raw()` is used by user in the view, in this case it will be wrapped into `Html.ecnode()` anyway an it will call `Html.raw` passing 'undefined' to it. return; if (sectionName) { let sec = sections[sectionName][args.filePath]; if (!sec.compiled) // it could have been compiled already if it's defined in a partial view which is rendred more than once sec.html += val; } else { args.html += val; } }; this.encode = function (val) { var encoded = this.getEncoded(val); this.raw(encoded); }; this.getEncoded = function (val) { if (!isVisibleValue(val)) return ''; if (typeof val === "number" || val instanceof Number || val instanceof HtmlString) return val; if (String.is(val)) return htmlEncode(val); return htmlEncode(val.toString()); }; this.body = function () { return new HtmlString(args.bodyHtml); }; this.section = function (name, required) { if (!args.filePath) throw new Error("'args.filePath' is not set."); let secGroup = sections[name]; if (secGroup) { if (secGroup.renderedBy) throw args.er.sectionsAlreadyRendered(name, secGroup.renderedBy, args.filePath); // TESTME: let html = ''; for (var key in secGroup) { if (secGroup.hasOwnProperty(key)) { let sec = secGroup[key]; if (!sec.compiled) throw args.er.sectionIsNotCompiled(name, args.filePath); // [#3.2] html += sec.html; } } secGroup.renderedBy = args.filePath; return new HtmlString(html); } else { if (required) throw args.er.sectionIsNotFound(name, args.filePath); // [#3.3] } return ''; }; this.getPartial = function (viewName, viewModel) { let compileOpt = { scope: args.scope, model: viewModel === undefined ? args.model : viewModel, // if is not set explicitly, set default (parent) model findPartial: args.findPartial, findPartialSync: args.findPartialSync, sections, parsedSections: args.parsedSections, partialsCache: args.partialsCache, viewData: args.viewData }; // Read file and complie to JS. let partial = args.findPartialSync(viewName, args.filePath, args.er, args.partialsCache); compileOpt.template = partial.data; compileOpt.filePath = partial.filePath; if (partial.js) { // if it's taken from cache compileOpt.js = partial.js; compileOpt.jsValues = partial.jsValues; } let { html, precompiled } = compileSync(compileOpt); partial.js = precompiled.js; // put to cache partial.jsValues = precompiled.jsValues; // put to cache return html; }; this.partial = function (viewName, viewModel) { var partialHtml = this.getPartial(viewName, viewModel); this.raw(partialHtml) }; } class Block { constructor(type, name) { this.type = type; if (name) this.name = name; this.text = ''; } append(ch) { this.text += ch; //this.text += (ch === '"') ? '\\"' : ch; } toScript(jsValues) { return toScript(this, jsValues); } } function isVisibleValue(val) { return (val != null && val !== ''); } function toScript(block, jsValues) { if (block.type === blockType.section) { let secMarker = `\r\nHtml.__sec("${block.name}");`; let script = secMarker; for (let n = 0; n < block.blocks.length; n++) { let sectionBlock = block.blocks[n]; script += toScript(sectionBlock, jsValues); } script += secMarker; return script; } else { let i; switch (block.type) { case blockType.html: i = jsValues.enq(block.text); return "\r\nHtml.raw(Html.__val(" + i + "));"; case blockType.expr: i = jsValues.enq(block.text); let code = `Html.encode(eval(Html.__val(${i})));`; return debugMode ? setDbg(code, block) : "\r\n" + code; case blockType.code: return debugMode ? setDbg(block.text, block) : "\r\n" + block.text; default: throw new Error(`Unexpected block type = "${blockType}".`); } } throw new Error(`Unexpected code behaviour, block type = "${blockType}".`); } function setDbg(code, block) { return ` Html.__dbg.pos = { start:${block.posStart}, end: ${block.posEnd} }; ${code} Html.__dbg.pos = null;`; } class Queue { constructor() { this._items = []; } enq(item) { //if (opts.debug) log.debug(item); return this._items.push(item) - 1; } getAt(i) { if (opts.debug) { let item = this._items[i]; //log.debug(item); return item; } else { return this._items[i]; } } } const _sectionKeyword = "section"; //const _functionKeyword = "function"; const blockType = { none: 0, html: 1, code: 2, expr: 3, section: 4 }; const ErrorsFactory = require('./errors/errors'); const voidTags = "area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr".toUpperCase().split("|").map(s => s.trim()); //////////////// // PARSER // //////////////// class Parser { constructor(args) { args.filePath = args.filePath || "js-script"; let linesBaseNumber = (debugMode && opts.express) ? 0 : 1; // in debug-mode the file-path of a template is added as a very first line comment this.args = args; this.er = new ErrorsFactory({ filename: args.filePath, jshtml: args.template }, linesBaseNumber); } compile(done) { log.debug(); let errorFactory = this.er; try { var htmlObj = this.getHtml({}, done); } catch (exc) { return error(exc); } compilePage(htmlObj, this.args.model, this.args.viewData, this.args.scope, debugMode, (err, html) => { if (err) return error(err, htmlObj.__dbg); try { this.checkSections(); } catch (exc) { return error(exc, htmlObj.__dbg); } return done(null, html); }); function error(err, dbg) { err.__dbg = dbg; var parserError = toParserError(err, errorFactory); return Promise.resolve().then(() => done(parserError)), null; } } compileSync() { try { log.debug(); var htmlArgs = {}; var html = this.getHtml(htmlArgs); compilePageSync(html, this.args.model, this.args.viewData, this.args.scope, debugMode); this.checkSections(); } catch (exc) { exc.__dbg = html && html.__dbg; throw toParserError(exc, this.er); } return { html: htmlArgs.html, precompiled: { js: htmlArgs.js, jsValues: htmlArgs.jsValues } }; } getHtml(htmlArgs) { log.debug(this.args.filePath); // extract scope.. var model = this.args.model; if (model && model.$) { this.args.scope = model.$; delete model.$; } this.args.parsedSections = this.args.parsedSections || {}; this.args.viewData = this.args.viewData || this.args.ViewData || {}; this.args.partialsCache = this.args.partialsCache || {}; let js = this.args.js; let jsValues = this.args.jsValues; let template = this.args.template; if (!js) { var isString = String.is(template); if (!isString) throw new Error(ErrorsFactory.templateShouldBeString); this.text = template; this.line = '', this.lineNum = 0, this.pos = 0, this.padding = ''; this.inSection = false; this.blocks = []; this.parseHtml(this.blocks); jsValues = new Queue(); var scripts = this.blocks.map(b => b.toScript(jsValues)); js = scripts.join(""); } Object.assign(htmlArgs, { html: '', jsValues, js, template, er: this.er }); Object.assign(htmlArgs, this.args); var html = new Html(htmlArgs); return html; } // Check if all sections have been rendered. checkSections() { if (!this.args.root) return; let sections = this.args.parsedSections; for (var key in sections) { if (sections.hasOwnProperty(key)) { let secGroup = sections[key]; if (!secGroup.renderedBy) { let sec = secGroup[Object.keys(secGroup)[0]]; // just any section from the group throw this.er.sectionNeverRendered(key, sec.filePath); } } } } parseHtml(blocks, outerWaitTag) { log.debug(); const docTypeName = "!DOCTYPE"; const textQuotes = `'"\``; var quotes = []; const tagKinds = { open: 0, close: 1, selfclose: 2 }; var openTags = []; var tag = '', lineLastLiteral = '', lastLiteral = ''; var block = this.newBlock(blockType.html, blocks); let stop = false, inComments = false; let inJs = "script".equal(outerWaitTag, true); var lastCh = ''; for (var ch = this.pickChar(); ch; ch = this.pickChar()) { let isSpace = Char.isWhiteSpace(ch); let nextCh = this.pickNextChar(); let inQuotes = (quotes.length > 0); if (inComments) { if (ch === '-') { if (!tag || tag === '-') tag += ch; else tag = ''; } else if (ch === '>') { if (tag === '--') inComments = false; tag = ''; } else { tag = ''; } } else if (ch === '@') { if (nextCh === '@') { // checking for '@@' that means just text '@' ch = this.fetchChar(); // skip the next '@' nextCh = this.pickNextChar(); } else { this.fetchChar(); this.parseCode(blocks); if (tag === '<' || tag === '</') tag = ''; block = this.newBlock(blockType.html, blocks);