diff --git a/src/Page.js b/src/Page.js
index 09e2ff7eef..7416ce8540 100644
--- a/src/Page.js
+++ b/src/Page.js
@@ -1,4 +1,4 @@
-const cheerio = require('cheerio');
+const cheerio = require('cheerio'); require('./lib/markbind/src/patches/htmlparser2');
const fm = require('fastmatter');
const fs = require('fs-extra-promise');
const htmlBeautify = require('js-beautify').html;
@@ -18,7 +18,6 @@ const logger = require('./util/logger');
const MarkBind = require('./lib/markbind/src/parser');
const md = require('./lib/markbind/src/lib/markdown-it');
const utils = require('./lib/markbind/src/utils');
-const urlUtils = require('./lib/markbind/src/utils/urls');
const CLI_VERSION = require('../package.json').version;
@@ -57,7 +56,6 @@ const {
TEMP_DROPDOWN_PLACEHOLDER_CLASS,
} = require('./constants');
-cheerio.prototype.options.xmlMode = true; // Enable xml mode for self-closing tag
cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities
class Page {
@@ -597,7 +595,7 @@ class Page {
// Insert content
.then(result => njUtil.renderRaw(result, {
[LAYOUT_PAGE_BODY_VARIABLE]: pageData,
- }, {}, false));
+ }));
}
@@ -707,11 +705,11 @@ class Page {
const siteNavHtml = md.render(navigationElements.length === 0
? siteNavMappedData.replace(SITE_NAV_EMPTY_LINE_REGEX, '\n')
: navigationElements.html().replace(SITE_NAV_EMPTY_LINE_REGEX, '\n'));
- const $nav = cheerio.load(siteNavHtml, { xmlMode: false });
+ const $nav = cheerio.load(siteNavHtml);
// Add anchor classes and highlight current page's anchor, if any.
const currentPageHtmlPath = this.src.replace(/\.(md|mbd)$/, '.html');
- const currentPageRegex = new RegExp(`{{ *baseUrl *}}/${currentPageHtmlPath}`);
+ const currentPageRegex = new RegExp(`${this.baseUrl}/${currentPageHtmlPath}`);
$nav('a[href]').each((i, elem) => {
if (currentPageRegex.test($nav(elem).attr('href'))) {
$nav(elem).addClass('current');
@@ -845,7 +843,7 @@ class Page {
*/
buildPageNav() {
if (this.isPageNavigationSpecifierValid()) {
- const $ = cheerio.load(this.content, { xmlMode: false });
+ const $ = cheerio.load(this.content);
this.navigableHeadings = {};
this.collectNavigableHeadings($(`#${CONTENT_WRAPPER_ID}`).html());
const pageNavTitleHtml = this.generatePageNavTitleHtml();
@@ -862,7 +860,7 @@ class Page {
}
}
- collectHeadFiles(baseUrl, hostBaseUrl) {
+ collectHeadFiles() {
const { head } = this.frontMatter;
if (head === FRONT_MATTER_NONE_ATTR) {
this.headFileTopContent = '';
@@ -890,18 +888,13 @@ class Page {
const headFileMappedData = this.variablePreprocessor.renderSiteVariables(this.sourcePath,
headFileContent).trim();
// Split top and bottom contents
- const $ = cheerio.load(headFileMappedData, { xmlMode: false });
+ const $ = cheerio.load(headFileMappedData);
if ($('head-top').length) {
- collectedTopContent.push(njUtil.renderRaw($('head-top').html(), {
- baseUrl,
- hostBaseUrl,
- }).trim().replace(/\n\s*\n/g, '\n').replace(/\n/g, '\n '));
+ collectedTopContent.push($('head-top').html().trim().replace(/\n\s*\n/g, '\n')
+ .replace(/\n/g, '\n '));
$('head-top').remove();
}
- collectedBottomContent.push(njUtil.renderRaw($.html(), {
- baseUrl,
- hostBaseUrl,
- }).trim().replace(/\n\s*\n/g, '\n').replace(/\n/g, '\n '));
+ collectedBottomContent.push($.html().trim().replace(/\n\s*\n/g, '\n').replace(/\n/g, '\n '));
});
this.headFileTopContent = collectedTopContent.join('\n ');
this.headFileBottomContent = collectedBottomContent.join('\n ');
@@ -924,7 +917,7 @@ class Page {
}
collectPageSection(section) {
- const $ = cheerio.load(this.content, { xmlMode: false });
+ const $ = cheerio.load(this.content);
const pageSection = $(section);
if (pageSection.length === 0) {
return;
@@ -962,6 +955,7 @@ class Page {
*/
const fileConfig = {
baseUrlMap: this.baseUrlMap,
+ baseUrl: this.baseUrl,
rootPath: this.rootPath,
headerIdMap: this.headerIdMap,
fixedHeader: this.fixedHeader,
@@ -981,27 +975,16 @@ class Page {
.then(result => this.insertHeaderFile(result, fileConfig))
.then(result => this.insertFooterFile(result))
.then(result => Page.insertTemporaryStyles(result))
- .then(result => markbinder.resolveBaseUrl(result, fileConfig))
.then(result => markbinder.render(result, this.sourcePath, fileConfig))
.then(result => this.postRender(result))
.then(result => this.collectPluginsAssets(result))
- .then(result => markbinder.processDynamicResources(this.sourcePath, result))
+ .then(result => MarkBind.processDynamicResources(this.sourcePath, result, fileConfig))
.then(result => MarkBind.unwrapIncludeSrc(result))
.then((result) => {
- this.content = result;
-
- const { relative } = urlUtils.getParentSiteAbsoluteAndRelativePaths(this.sourcePath, this.rootPath,
- this.baseUrlMap);
- const baseUrl = relative ? `${this.baseUrl}/${utils.ensurePosix(relative)}` : this.baseUrl;
- const hostBaseUrl = this.baseUrl;
+ this.addLayoutScriptsAndStyles();
+ this.collectHeadFiles();
- this.addLayoutFiles();
- this.collectHeadFiles(baseUrl, hostBaseUrl);
-
- this.content = njUtil.renderString(this.content, {
- baseUrl,
- hostBaseUrl,
- });
+ this.content = result;
this.collectAllPageSections();
this.buildPageNav();
@@ -1089,7 +1072,7 @@ class Page {
pageContextSources.forEach((src) => {
if (src === undefined || src === '' || utils.isUrl(src)) {
return;
- } else if (utils.isAbsolutePath(src)) {
+ } else if (path.isAbsolute(src)) {
self.pluginSourceFiles.add(path.resolve(src));
return;
}
@@ -1103,7 +1086,7 @@ class Page {
}
if (domTagSourcesMap) {
- const $ = cheerio.load(content, { xmlMode: true });
+ const $ = cheerio.load(content);
domTagSourcesMap.forEach(([tagName, attrName]) => {
if (!_.isString(tagName) || !_.isString(attrName)) {
@@ -1121,7 +1104,7 @@ class Page {
src = ensurePosix(src);
if (src === '' || utils.isUrl(src)) {
return;
- } else if (utils.isAbsolutePath(src)) {
+ } else if (path.isAbsolute(src)) {
self.pluginSourceFiles.add(path.resolve(src));
return;
}
@@ -1182,7 +1165,7 @@ class Page {
* @return String html of the element, with the attribute's asset resolved
*/
getResolvedAssetElement(assetElementHtml, tagName, attrName, plugin, pluginName) {
- const $ = cheerio.load(assetElementHtml, { xmlMode: false });
+ const $ = cheerio.load(assetElementHtml);
const el = $(`${tagName}[${attrName}]`);
el.attr(attrName, (i, assetPath) => {
@@ -1245,7 +1228,7 @@ class Page {
/**
* Adds linked layout files to page assets
*/
- addLayoutFiles() {
+ addLayoutScriptsAndStyles() {
this.asset.layoutScript = path.join(this.layoutsAssetPath, this.frontMatter.layout, 'scripts.js');
this.asset.layoutStyle = path.join(this.layoutsAssetPath, this.frontMatter.layout, 'styles.css');
}
@@ -1272,41 +1255,30 @@ class Page {
const markbinder = new MarkBind({
variablePreprocessor: this.variablePreprocessor,
});
+ /**
+ * @type {FileConfig}
+ */
+ const fileConfig = {
+ baseUrlMap: this.baseUrlMap,
+ baseUrl: this.baseUrl,
+ rootPath: this.rootPath,
+ headerIdMap: {},
+ cwf: file,
+ };
return fs.readFileAsync(dependency.to, 'utf-8')
- .then(result => markbinder.includeFile(dependency.to, result, {
- baseUrlMap: this.baseUrlMap,
- rootPath: this.rootPath,
- cwf: file,
- }))
+ .then(result => markbinder.includeFile(dependency.to, result, fileConfig))
.then(result => Page.removeFrontMatter(result))
.then(result => this.collectPluginSources(result))
.then(result => this.preRender(result))
- .then(result => markbinder.resolveBaseUrl(result, {
- baseUrlMap: this.baseUrlMap,
- rootPath: this.rootPath,
- }))
- .then(result => markbinder.render(result, this.sourcePath, {
- baseUrlMap: this.baseUrlMap,
- rootPath: this.rootPath,
- headerIdMap: {},
- }))
+ .then(result => markbinder.render(result, this.sourcePath, fileConfig))
.then(result => this.postRender(result))
.then(result => this.collectPluginsAssets(result))
- .then(result => markbinder.processDynamicResources(file, result))
+ .then(result => MarkBind.processDynamicResources(file, result, fileConfig))
.then(result => MarkBind.unwrapIncludeSrc(result))
.then((result) => {
- // resolve the site base url here
- const { relative } = urlUtils.getParentSiteAbsoluteAndRelativePaths(file, this.rootPath,
- this.baseUrlMap);
- const baseUrl = relative ? `${this.baseUrl}/${utils.ensurePosix(relative)}` : this.baseUrl;
- const hostBaseUrl = this.baseUrl;
- const content = njUtil.renderString(result, {
- baseUrl,
- hostBaseUrl,
- });
const outputContentHTML = this.disableHtmlBeautify
- ? content
- : htmlBeautify(content, Page.htmlBeautifyOptions);
+ ? result
+ : htmlBeautify(result, Page.htmlBeautifyOptions);
return fs.outputFileAsync(resultPath, outputContentHTML);
})
.then(() => {
diff --git a/src/Site.js b/src/Site.js
index fd4aca7b31..91335d7ebc 100644
--- a/src/Site.js
+++ b/src/Site.js
@@ -1,4 +1,4 @@
-const cheerio = require('cheerio');
+const cheerio = require('cheerio'); require('./lib/markbind/src/patches/htmlparser2');
const fs = require('fs-extra-promise');
const ghpages = require('gh-pages');
const ignore = require('ignore');
@@ -11,6 +11,7 @@ const njUtil = require('./lib/markbind/src/utils/nunjuckUtils');
const injectHtmlParser2SpecialTags = require('./lib/markbind/src/patches/htmlparser2');
const injectMarkdownItSpecialTags = require(
'./lib/markbind/src/lib/markdown-it/markdown-it-escape-special-tags');
+const utils = require('./lib/markbind/src/utils');
const _ = {};
_.difference = require('lodash/difference');
@@ -524,8 +525,6 @@ class Site {
* Collects the user defined variables map in the site/subsites
*/
collectUserDefinedVariablesMap() {
- // The key is the base directory of the site/subsites,
- // while the value is a mapping of user defined variables
this.variablePreprocessor.resetUserDefinedVariablesMap();
this.baseUrlMap.forEach((base) => {
@@ -540,13 +539,17 @@ class Site {
}
/*
- This is to prevent the first nunjuck call from converting {{baseUrl}} to an empty string
- and let the baseUrl value be injected later.
+ We retrieve the baseUrl of the (sub)site by appending the relative to the configured base url
+ i.e. We ignore the configured baseUrl of the sub sites.
*/
- this.variablePreprocessor.addUserDefinedVariable(base, 'baseUrl', '{{baseUrl}}');
+ const siteRelativePathFromRoot = utils.ensurePosix(path.relative(this.rootPath, base));
+ const siteBaseUrl = siteRelativePathFromRoot === ''
+ ? this.siteConfig.baseUrl
+ : path.posix.join(this.siteConfig.baseUrl || '/', siteRelativePathFromRoot);
+ this.variablePreprocessor.addUserDefinedVariable(base, 'baseUrl', siteBaseUrl);
this.variablePreprocessor.addUserDefinedVariable(base, 'MarkBind', MARKBIND_LINK_HTML);
- const $ = cheerio.load(content);
+ const $ = cheerio.load(content, { decodeEntities: false });
$('variable,span').each((index, element) => {
const name = $(element).attr('name') || $(element).attr('id');
const variableSource = $(element).attr('from');
diff --git a/src/lib/markbind/src/constants.js b/src/lib/markbind/src/constants.js
index 3ff5b152fc..2d8dd96aa0 100644
--- a/src/lib/markbind/src/constants.js
+++ b/src/lib/markbind/src/constants.js
@@ -1,6 +1,5 @@
module.exports = {
// src/lib/markbind/src/parser.js
- ATTRIB_INCLUDE_PATH: 'include-path',
ATTRIB_CWF: 'cwf',
BOILERPLATE_FOLDER_NAME: '_markbind/boilerplates',
diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js
index 30a83b3303..c50574b697 100644
--- a/src/lib/markbind/src/parser.js
+++ b/src/lib/markbind/src/parser.js
@@ -3,11 +3,9 @@ const htmlparser = require('htmlparser2'); require('./patches/htmlparser2');
const path = require('path');
const Promise = require('bluebird');
const slugify = require('@sindresorhus/slugify');
-const ensurePosix = require('ensure-posix-path');
const componentParser = require('./parsers/componentParser');
const componentPreprocessor = require('./preprocessors/componentPreprocessor');
const logger = require('../../../util/logger');
-const njUtil = require('./utils/nunjuckUtils');
const _ = {};
_.clone = require('lodash/clone');
@@ -18,16 +16,9 @@ _.isEmpty = require('lodash/isEmpty');
const md = require('./lib/markdown-it');
const utils = require('./utils');
-const urlUtils = require('./utils/urls');
-cheerio.prototype.options.xmlMode = true; // Enable xml mode for self-closing tag
cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities
-const {
- ATTRIB_INCLUDE_PATH,
- ATTRIB_CWF,
-} = require('./constants');
-
class Parser {
constructor(config) {
this.variablePreprocessor = config.variablePreprocessor;
@@ -48,20 +39,16 @@ class Parser {
return _.clone(this.missingIncludeSrc);
}
- processDynamicResources(context, html) {
- const $ = cheerio.load(html, {
- xmlMode: false,
- decodeEntities: false,
- });
+ static processDynamicResources(context, html, config) {
+ const $ = cheerio.load(html);
- const { rootPath } = this;
function getAbsoluteResourcePath(elem, relativeResourcePath) {
const firstParent = elem.closest('div[data-included-from], span[data-included-from]');
const originalSrc = utils.ensurePosix(firstParent.attr('data-included-from') || context);
const originalSrcFolder = path.posix.dirname(originalSrc);
const fullResourcePath = path.posix.join(originalSrcFolder, relativeResourcePath);
- const resolvedResourcePath = path.posix.relative(utils.ensurePosix(rootPath), fullResourcePath);
- return path.posix.join('{{hostBaseUrl}}', resolvedResourcePath);
+ const resolvedResourcePath = path.posix.relative(utils.ensurePosix(config.rootPath), fullResourcePath);
+ return path.posix.join(config.baseUrl || '/', resolvedResourcePath);
}
$('img, pic, thumbnail').each(function () {
@@ -70,7 +57,7 @@ class Parser {
return;
}
const resourcePath = utils.ensurePosix(elem.attr('src'));
- if (utils.isAbsolutePath(resourcePath) || utils.isUrl(resourcePath)) {
+ if (path.isAbsolute(resourcePath) || utils.isUrl(resourcePath)) {
// Do not rewrite.
return;
}
@@ -84,7 +71,7 @@ class Parser {
// Found empty href resource in resourcePath
return;
}
- if (utils.isAbsolutePath(resourcePath) || utils.isUrl(resourcePath) || resourcePath.startsWith('#')) {
+ if (path.isAbsolute(resourcePath) || utils.isUrl(resourcePath) || resourcePath.startsWith('#')) {
// Do not rewrite.
return;
}
@@ -95,10 +82,7 @@ class Parser {
}
static unwrapIncludeSrc(html) {
- const $ = cheerio.load(html, {
- xmlMode: false,
- decodeEntities: false,
- });
+ const $ = cheerio.load(html);
$('div[data-included-from], span[data-included-from]').each(function () {
$(this).replaceWith($(this).contents());
});
@@ -139,32 +123,12 @@ class Parser {
switch (node.name) {
case 'md':
node.name = 'span';
- cheerio.prototype.options.xmlMode = false;
node.children = cheerio.parseHTML(md.renderInline(cheerio.html(node.children)), true);
- cheerio.prototype.options.xmlMode = true;
break;
case 'markdown':
node.name = 'div';
- cheerio.prototype.options.xmlMode = false;
node.children = cheerio.parseHTML(md.render(cheerio.html(node.children)), true);
- cheerio.prototype.options.xmlMode = true;
- break;
- case 'panel': {
- if (!_.hasIn(node.attribs, 'src')) { // dynamic panel
- break;
- }
- const fileExists = utils.fileExists(node.attribs.src)
- || utils.fileExists(urlUtils.calculateBoilerplateFilePath(node.attribs.boilerplate,
- node.attribs.src, config));
- if (fileExists) {
- const { src, fragment } = node.attribs;
- const resultDir = path.dirname(path.join('{{hostBaseUrl}}', path.relative(config.rootPath, src)));
- const resultPath = path.join(resultDir, utils.setExtension(path.basename(src), '._include_.html'));
- node.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath);
- }
- delete node.attribs.boilerplate;
break;
- }
default:
break;
}
@@ -230,10 +194,7 @@ class Parser {
});
resolve(cheerio.html(nodes));
});
- const parser = new htmlparser.Parser(handler, {
- xmlMode: true,
- decodeEntities: true,
- });
+ const parser = new htmlparser.Parser(handler);
const renderedContent = this.variablePreprocessor.renderPage(file, content, additionalVariables);
@@ -272,14 +233,9 @@ class Parser {
nodes.forEach((d) => {
this._trimNodes(d);
});
- cheerio.prototype.options.xmlMode = false;
resolve(cheerio.html(nodes));
- cheerio.prototype.options.xmlMode = true;
- });
- const parser = new htmlparser.Parser(handler, {
- xmlMode: true,
- decodeEntities: false,
});
+ const parser = new htmlparser.Parser(handler);
const fileExt = utils.getExt(filePath);
if (utils.isMarkdownFileExt(fileExt)) {
const renderedContent = md.render(content);
@@ -293,78 +249,6 @@ class Parser {
});
}
- resolveBaseUrl(pageData, config) {
- const { baseUrlMap, rootPath } = config;
- this.baseUrlMap = baseUrlMap;
- this.rootPath = rootPath;
-
- return new Promise((resolve, reject) => {
- const handler = new htmlparser.DomHandler((error, dom) => {
- if (error) {
- reject(error);
- return;
- }
- const nodes = dom.map(d => this._rebaseReference(d));
- cheerio.prototype.options.xmlMode = false;
- resolve(cheerio.html(nodes));
- cheerio.prototype.options.xmlMode = true;
- });
- const parser = new htmlparser.Parser(handler, {
- xmlMode: true,
- decodeEntities: true,
- });
- parser.parseComplete(pageData);
- });
- }
-
- /**
- * Pre-renders the baseUrl of the provided node and its children according to
- * the site they belong to, such that the final call to render baseUrl correctly
- * resolves baseUrl according to the said site.
- */
- _rebaseReference(node) {
- if (_.isArray(node)) {
- return node.map(el => this._rebaseReference(el));
- }
- if (Parser.isText(node)) {
- return node;
- }
-
- // Rebase children elements
- node.children.forEach(el => this._rebaseReference(el));
-
- const includeSourceFile = node.attribs[ATTRIB_CWF];
- const includedSourceFile = node.attribs[ATTRIB_INCLUDE_PATH];
- delete node.attribs[ATTRIB_CWF];
- delete node.attribs[ATTRIB_INCLUDE_PATH];
-
- // Skip rebasing for non-includes
- if (!includedSourceFile || !node.children) {
- return node;
- }
-
- // The site at which the file with the include element was pre-processed
- const currentBase = urlUtils.getParentSiteAbsolutePath(includeSourceFile, this.rootPath, this.baseUrlMap);
- // The site of the included file
- const newBase = urlUtils.getParentSiteAbsoluteAndRelativePaths(includedSourceFile, this.rootPath,
- this.baseUrlMap);
-
- // Only re-render if include src and content are from different sites
- if (currentBase !== newBase.absolute) {
- cheerio.prototype.options.xmlMode = false;
- const rendered = njUtil.renderRaw(cheerio.html(node.children), {
- // This is to prevent the nunjuck call from converting {{hostBaseUrl}} to an empty string
- // and let the hostBaseUrl value be injected later.
- hostBaseUrl: '{{hostBaseUrl}}',
- baseUrl: `{{hostBaseUrl}}/${ensurePosix(newBase.relative)}`,
- }, { path: includedSourceFile });
- node.children = cheerio.parseHTML(rendered, true);
- cheerio.prototype.options.xmlMode = true;
- }
-
- return node;
- }
-
static isText(node) {
return node.type === 'text' || node.type === 'comment';
}
diff --git a/src/lib/markbind/src/parsers/componentParser.js b/src/lib/markbind/src/parsers/componentParser.js
index e4665b59f3..1c4735b4c5 100644
--- a/src/lib/markbind/src/parsers/componentParser.js
+++ b/src/lib/markbind/src/parsers/componentParser.js
@@ -6,7 +6,10 @@ _.has = require('lodash/has');
const md = require('../lib/markdown-it');
const logger = require('../../../../util/logger');
-cheerio.prototype.options.xmlMode = true; // Enable xml mode for self-closing tag
+const {
+ ATTRIB_CWF,
+} = require('../constants');
+
cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities
/*
@@ -483,6 +486,10 @@ function postParseComponents(node) {
} catch (error) {
logger.error(error);
}
+
+ if (node.attribs) {
+ delete node.attribs[ATTRIB_CWF];
+ }
}
diff --git a/src/lib/markbind/src/patches/htmlparser2.js b/src/lib/markbind/src/patches/htmlparser2.js
index 526e3b7db2..62b5ab360d 100644
--- a/src/lib/markbind/src/patches/htmlparser2.js
+++ b/src/lib/markbind/src/patches/htmlparser2.js
@@ -1,4 +1,32 @@
-const { Tokenizer } = require('htmlparser2');
+/*
+ * Four behaviours of htmlparser2 are patched here, the first 2 being 'convenience' patches
+ * to avoid repeated passing of lowerCaseAttributeNames and recognizeSelfClosing options.
+ * 1. Defaulting to self closing tag recognition
+ * 2. Disabling automatic attribute name conversion to lower case
+ * 3. Recognising text in markdown inline code / code blocks as raw text
+ * 4. Ability to inject/whitelist certain tags to be parsed like script/style tags do. (special tags)
+ */
+
+const { Tokenizer, Parser } = require('htmlparser2');
+
+/*
+ Enable any self closing tags '