diff --git a/docs/userGuide/reusingContents.md b/docs/userGuide/reusingContents.md
index fe7c61a965..d9fad356fe 100644
--- a/docs/userGuide/reusingContents.md
+++ b/docs/userGuide/reusingContents.md
@@ -157,6 +157,20 @@ You must use the `safe` filter when using such variables:
{{ right_hand_2 }} {{ icon_arrow_right }} {{glyphicon_hand_right}}
+### Page Variables
+
+**You can also declare variables for use within a single page.** These variables work exactly the same as regular variables, except that their values only apply to the page they are declared in. This can be done by using the `` tag.
+
+
+
+{{ icon_example }} Declaring page variables:
+
+`John Doe`
+My name is {{ full_name }}. This is {{ full_name }}'s site.
+
+
+Note: These variables will also be applied to [`` files]({{ baseUrl }}/userGuide/reusingContents.html#the-include-tag). If the same variable is defined in a chain of pages (e.g. `a.md` includes `b.md` includes `c.md`...), variables defined in the top-most page will take precedence. Global variables (`_markbind/variables.md`) will take precedence over any page variables.
+
## The `include` tag
diff --git a/src/lib/markbind/src/parser.js b/src/lib/markbind/src/parser.js
index 2635d2472f..517c720dac 100644
--- a/src/lib/markbind/src/parser.js
+++ b/src/lib/markbind/src/parser.js
@@ -91,9 +91,9 @@ function Parser(options) {
/**
* Extract variables from an include element
* @param includeElement include element to extract variables from
- * @param contextVariables local variables defined by parent pages
+ * @param contextVariables variables defined by parent pages
*/
-function extractVariables(includeElement, contextVariables) {
+function extractIncludeVariables(includeElement, contextVariables) {
const includedVariables = { ...contextVariables };
if (includeElement.children) {
includeElement.children.forEach((child) => {
@@ -114,6 +114,33 @@ function extractVariables(includeElement, contextVariables) {
return includedVariables;
}
+/**
+ * Extract page variables from a page
+ * @param filename for error printing
+ * @param data to extract variables from
+ * @param userDefinedVariables global variables
+ * @param contextVariables variables defined by parent pages
+ */
+function extractPageVariables(fileName, data, userDefinedVariables, contextVariables) {
+ const $ = cheerio.load(data);
+ const pageVariables = { ...contextVariables };
+ $('variable').each(function () {
+ const variableElement = $(this);
+ const variableName = variableElement.attr('name');
+ if (!variableName) {
+ // eslint-disable-next-line no-console
+ console.warn(`Missing 'name' for variable in ${fileName}\n`);
+ return;
+ }
+ if (!pageVariables[variableName]) {
+ pageVariables[variableName]
+ = nunjucks.renderString(md.renderInline(variableElement.html()),
+ { ...pageVariables, ...userDefinedVariables });
+ }
+ });
+ return pageVariables;
+}
+
Parser.prototype.getDynamicIncludeSrc = function () {
return _.clone(this.dynamicIncludeSrc);
};
@@ -232,10 +259,15 @@ Parser.prototype._preprocess = function (node, context, config) {
const { parent, relative } = calculateNewBaseUrls(filePath, config.rootPath, config.baseUrlMap);
const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)];
- // process variables declared within the include
- const includedVariables = extractVariables(element, context.includedVariables);
+ // Extract included variables from the PARENT file
+ let allVariables = extractIncludeVariables(element, context.variables);
+
+ // Extract page variables from the CHILD file
+ allVariables = extractPageVariables(element.attribs.src, fileContent, userDefinedVariables, allVariables);
+
+ // Render inner file content
+ fileContent = nunjucks.renderString(fileContent, { ...allVariables, ...userDefinedVariables });
- fileContent = nunjucks.renderString(fileContent, { ...includedVariables, ...userDefinedVariables });
delete element.attribs.boilerplate;
delete element.attribs.src;
delete element.attribs.inline;
@@ -295,7 +327,7 @@ Parser.prototype._preprocess = function (node, context, config) {
const childContext = _.cloneDeep(context);
childContext.cwf = filePath;
childContext.source = isIncludeSrcMd ? 'md' : 'html';
- childContext.includedVariables = includedVariables;
+ childContext.variables = allVariables;
if (element.children && element.children.length > 0) {
element.children = element.children.map(e => self._preprocess(e, childContext, config));
}
@@ -306,6 +338,8 @@ Parser.prototype._preprocess = function (node, context, config) {
element.attribs.src = filePath;
this.dynamicIncludeSrc.push({ from: context.cwf, to: actualFilePath, asIfTo: filePath });
return element;
+ } else if (element.name === 'variable') {
+ return createEmptyNode();
} else {
if (element.name === 'body') {
// eslint-disable-next-line no-console
@@ -444,7 +478,9 @@ Parser.prototype.includeFile = function (file, config) {
}
const { parent, relative } = calculateNewBaseUrls(file, config.rootPath, config.baseUrlMap);
const userDefinedVariables = config.userDefinedVariablesMap[path.resolve(parent, relative)];
- const fileContent = nunjucks.renderString(data, userDefinedVariables);
+ const pageVariables = extractPageVariables(path.basename(file), data, userDefinedVariables, {});
+ const fileContent = nunjucks.renderString(data, { ...pageVariables, ...userDefinedVariables });
+ context.variables = pageVariables;
const fileExt = utils.getExt(file);
if (utils.isMarkdownFileExt(fileExt)) {
context.source = 'md';
diff --git a/test/functional/test_site/_markbind/variables.md b/test/functional/test_site/_markbind/variables.md
index da1cb897c8..77be110efc 100644
--- a/test/functional/test_site/_markbind/variables.md
+++ b/test/functional/test_site/_markbind/variables.md
@@ -8,5 +8,6 @@
{{reference_level_2}}{{reference_level_3}}
-Global variable overriding included variable
-Global variable
+Global Variable Overriding Included Variable
+Global Variable
+Global Variable Overriding Page Variable
diff --git a/test/functional/test_site/expected/index.html b/test/functional/test_site/expected/index.html
index 21af6137f0..48b201c9a0 100644
--- a/test/functional/test_site/expected/index.html
+++ b/test/functional/test_site/expected/index.html
@@ -42,6 +42,17 @@
Testing Page Navigation
# Included variables should not leak into other files
diff --git a/test/functional/test_site/testIncludeVariables.md b/test/functional/test_site/testIncludeVariables.md
index b2e174d685..6dfda12238 100644
--- a/test/functional/test_site/testIncludeVariables.md
+++ b/test/functional/test_site/testIncludeVariables.md
@@ -26,11 +26,11 @@
{{ included_variable_with_global_variable }}
{% set included_variable = "Inner variable overridden by set" %}
-{% set included_global_variable = "Global variable overridden by set" %}
+{% set global_variable = "Global variable overridden by set" %}
# Test included variable overridden by set
{{ included_variable }}
-{{ included_global_variable }}
+{{ global_variable }}
# Test missing variable with default
{{ missing_variable or "Missing Variable" }}
diff --git a/test/functional/test_site/testPageVariablesInInclude.md b/test/functional/test_site/testPageVariablesInInclude.md
new file mode 100644
index 0000000000..2cff09fa63
--- /dev/null
+++ b/test/functional/test_site/testPageVariablesInInclude.md
@@ -0,0 +1,15 @@
+# Outer Page Variable Overriding Inner Page Variable
+Inner Page Variable Overridden by Outer Page Variable
+{{ page_variable }}
+
+# Included Variable Overriding Page Variable
+Page Variable Overridden by Included Variable
+{{ included_variable_overriding_page_variable }}
+
+# Page Variable Referencing Included Variable
+Page Variable Referencing {{ included_variable }}
+{{ page_variable_referencing_included_variable }}
+
+# Page Variable Referencing Outer Page Variable
+Page Variable Referencing Outer {{ page_variable }}
+{{ page_variable_referencing_outer_page_variable }}