Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = {
"lodash/prefer-lodash-method": [0],
"lodash/prefer-noop": [0],
"max-len": ["error", { "code": 110 }],
"no-param-reassign": ["error", { "props": false }],
"operator-linebreak": ["error", "before"],
// override airbnb-base dev dependencies, latest version does not white list __mocks__
"import/no-extraneous-dependencies": [
Expand Down
142 changes: 67 additions & 75 deletions src/lib/markbind/src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,22 @@ class Parser {
return _.clone(this.missingIncludeSrc);
}

_renderIncludeFile(filePath, element, context, config, asIfAt = filePath) {
_renderIncludeFile(filePath, node, context, config, asIfAt = filePath) {
try {
this._fileCache[filePath] = this._fileCache[filePath]
? this._fileCache[filePath] : fs.readFileSync(filePath, 'utf8');
} catch (e) {
// Read file fail
const missingReferenceErrorMessage = `Missing reference in: ${element.attribs[ATTRIB_CWF]}`;
const missingReferenceErrorMessage = `Missing reference in: ${node.attribs[ATTRIB_CWF]}`;
e.message += `\n${missingReferenceErrorMessage}`;
this._onError(e);
return utils.createErrorNode(element, e);
return utils.createErrorNode(node, e);
}
const fileContent = this._fileCache[filePath]; // cache the file contents to save some I/O
const { parent, relative } = urlUtils.calculateNewBaseUrls(asIfAt, config.rootPath, config.baseUrlMap);
const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)];
// Extract included variables from the PARENT file
const includeVariables = Parser.extractIncludeVariables(element, context.variables);
const includeVariables = Parser.extractIncludeVariables(node, context.variables);
// Extract page variables from the CHILD file
const pageVariables
= this.extractPageVariables(asIfAt, fileContent, userDefinedVariables, includeVariables);
Expand All @@ -162,22 +162,22 @@ class Parser {
});
const aliases = new Map();
Parser.FILE_ALIASES.set(cwf, aliases);
$('import[from]').each((index, element) => {
const filePath = path.resolve(path.dirname(cwf), element.attribs.from);
const variableNames = Object.keys(element.attribs)
$('import[from]').each((index, node) => {
const filePath = path.resolve(path.dirname(cwf), node.attribs.from);
const variableNames = Object.keys(node.attribs)
.filter(name => name !== 'from' && name !== 'as');
// If no namespace is provided, we use the smallest name as one
const largestName = variableNames.sort()[0];
// ... and prepend it with $__MARKBIND__ to reduce collisions.
const generatedAlias = IMPORTED_VARIABLE_PREFIX + largestName;
const alias = _.hasIn(element.attribs, 'as')
? element.attribs.as
const alias = _.hasIn(node.attribs, 'as')
? node.attribs.as
: generatedAlias;
aliases.set(alias, filePath);
this.staticIncludeSrc.push({ from: context.cwf, to: filePath });
// Render inner file content
const { content: renderedContent, childContext, userDefinedVariables }
= this._renderIncludeFile(filePath, element, context, config);
= this._renderIncludeFile(filePath, node, context, config);
this.extractInnerVariablesIfNotProcessed(renderedContent, childContext, config, filePath);
const innerVariables = this.getImportedVariableMap(filePath);
Parser.VARIABLE_LOOKUP.get(filePath).forEach((value, variableName, map) => {
Expand All @@ -194,17 +194,23 @@ class Parser {
}

processDynamicResources(context, html) {
const self = this;
const $ = cheerio.load(html, {
xmlMode: false,
decodeEntities: false,
});

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);
}

$('img, pic, thumbnail').each(function () {
const elem = $(this);
if (elem[0].name === 'thumbnail' && elem.attr('src') === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted 204-207 #1092

// Thumbnail tag without src
return;
}
const resourcePath = utils.ensurePosix(elem.attr('src'));
if (resourcePath === undefined || resourcePath === '') {
// Found empty img/pic resource in resourcePath
Expand All @@ -214,12 +220,7 @@ class Parser {
// Do not rewrite.
return;
}
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, resourcePath);
const resolvedResourcePath = path.posix.relative(utils.ensurePosix(self.rootPath), fullResourcePath);
const absoluteResourcePath = path.posix.join('{{hostBaseUrl}}', resolvedResourcePath);
const absoluteResourcePath = getAbsoluteResourcePath(elem, resourcePath);
$(this).attr('src', absoluteResourcePath);
});
$('a, link').each(function () {
Expand All @@ -233,12 +234,7 @@ class Parser {
// Do not rewrite.
return;
}
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, resourcePath);
const resolvedResourcePath = path.posix.relative(utils.ensurePosix(self.rootPath), fullResourcePath);
const absoluteResourcePath = path.posix.join('{{hostBaseUrl}}', resolvedResourcePath);
const absoluteResourcePath = getAbsoluteResourcePath(elem, resourcePath);
$(this).attr('href', absoluteResourcePath);
});
return $.html();
Expand All @@ -256,20 +252,18 @@ class Parser {
}

_parse(node, context, config) {
const element = node;
const self = this;
if (_.isArray(element)) {
return element.map(el => self._parse(el, context, config));
if (_.isArray(node)) {
return node.map(el => this._parse(el, context, config));
}
if (Parser.isText(element)) {
return element;
if (Parser.isText(node)) {
return node;
}
if (element.name) {
element.name = element.name.toLowerCase();
if (node.name) {
node.name = node.name.toLowerCase();
}

if ((/^h[1-6]$/).test(element.name) && !element.attribs.id) {
const textContent = utils.getTextContent(element);
if ((/^h[1-6]$/).test(node.name) && !node.attribs.id) {
const textContent = utils.getTextContent(node);
const slugifiedHeading = slugify(textContent, { decamelize: false });

let headerId = slugifiedHeading;
Expand All @@ -281,59 +275,58 @@ class Parser {
headerIdMap[slugifiedHeading] = 2;
}

element.attribs.id = headerId;
node.attribs.id = headerId;
}

switch (element.name) {
switch (node.name) {
case 'md':
element.name = 'span';
node.name = 'span';
cheerio.prototype.options.xmlMode = false;
element.children = cheerio.parseHTML(md.renderInline(cheerio.html(element.children)), true);
node.children = cheerio.parseHTML(md.renderInline(cheerio.html(node.children)), true);
cheerio.prototype.options.xmlMode = true;
break;
case 'markdown':
element.name = 'div';
node.name = 'div';
cheerio.prototype.options.xmlMode = false;
element.children = cheerio.parseHTML(md.render(cheerio.html(element.children)), true);
node.children = cheerio.parseHTML(md.render(cheerio.html(node.children)), true);
cheerio.prototype.options.xmlMode = true;
break;
case 'panel': {
if (!_.hasIn(element.attribs, 'src')) { // dynamic panel
if (!_.hasIn(node.attribs, 'src')) { // dynamic panel
break;
}
const fileExists = utils.fileExists(element.attribs.src)
const fileExists = utils.fileExists(node.attribs.src)
|| utils.fileExists(
urlUtils.calculateBoilerplateFilePath(
element.attribs.boilerplate,
element.attribs.src, config));
node.attribs.boilerplate,
node.attribs.src, config));
if (fileExists) {
const { src, fragment } = element.attribs;
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'));
element.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath);
node.attribs.src = utils.ensurePosix(fragment ? `${resultPath}#${fragment}` : resultPath);
}
delete element.attribs.boilerplate;
delete node.attribs.boilerplate;
break;
}
default:
break;
}

componentParser.parseComponents(element, this._onError);
componentParser.parseComponents(node, this._onError);

if (element.children) {
element.children.forEach((child) => {
self._parse(child, context, config);
if (node.children) {
node.children.forEach((child) => {
this._parse(child, context, config);
});
}

componentParser.postParseComponents(element, this._onError);
componentParser.postParseComponents(node, this._onError);

return element;
return node;
}

_trimNodes(node) {
const self = this;
if (node.name === 'pre' || node.name === 'code') {
return;
}
Expand All @@ -346,7 +339,7 @@ class Parser {
node.children.splice(n, 1);
n--;
} else if (child.type === 'tag') {
self._trimNodes(child);
this._trimNodes(child);
}
}
/* eslint-enable no-plusplus */
Expand Down Expand Up @@ -589,21 +582,20 @@ class Parser {
}

_rebaseReference(node, foundBase) {
const element = node;
if (_.isArray(element)) {
return element.map(el => this._rebaseReference(el, foundBase));
if (_.isArray(node)) {
return node.map(el => this._rebaseReference(el, foundBase));
}
if (Parser.isText(element)) {
return element;
if (Parser.isText(node)) {
return node;
}
// Rebase children element
const childrenBase = {};
element.children.forEach((el) => {
node.children.forEach((el) => {
this._rebaseReference(el, childrenBase);
});
// rebase current element
if (element.attribs[ATTRIB_INCLUDE_PATH]) {
const filePath = element.attribs[ATTRIB_INCLUDE_PATH];
if (node.attribs[ATTRIB_INCLUDE_PATH]) {
const filePath = node.attribs[ATTRIB_INCLUDE_PATH];
let newBaseUrl = urlUtils.calculateNewBaseUrls(filePath, this.rootPath, this.baseUrlMap);
if (newBaseUrl) {
const { relative, parent } = newBaseUrl;
Expand All @@ -616,27 +608,27 @@ class Parser {
if (bases.length !== 0) {
// need to rebase
newBaseUrl = combinedBases[bases[0]];
if (element.children) {
if (node.children) {
// ATTRIB_CWF is where the element was preprocessed
const currentBase = urlUtils.calculateNewBaseUrls(element.attribs[ATTRIB_CWF],
const currentBase = urlUtils.calculateNewBaseUrls(node.attribs[ATTRIB_CWF],
this.rootPath, this.baseUrlMap);
if (currentBase && currentBase.relative !== newBaseUrl) {
cheerio.prototype.options.xmlMode = false;
const rendered = nunjucks.renderString(cheerio.html(element.children), {
const rendered = nunjucks.renderString(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}}/${newBaseUrl}`,
}, { path: filePath });
element.children = cheerio.parseHTML(rendered, true);
node.children = cheerio.parseHTML(rendered, true);
cheerio.prototype.options.xmlMode = true;
}
}
}
delete element.attribs[ATTRIB_INCLUDE_PATH];
delete node.attribs[ATTRIB_INCLUDE_PATH];
}
delete element.attribs[ATTRIB_CWF];
return element;
delete node.attribs[ATTRIB_CWF];
return node;
}

static resetVariables() {
Expand All @@ -645,8 +637,8 @@ class Parser {
Parser.PROCESSED_INNER_VARIABLES.clear();
}

static isText(element) {
return element.type === 'text' || element.type === 'comment';
static isText(node) {
return node.type === 'text' || node.type === 'comment';
}

/**
Expand Down
Loading