From 042dcb13857e5c6122ab63ecb475b4c5d08ccc08 Mon Sep 17 00:00:00 2001 From: miripiruni Date: Tue, 4 Oct 2016 12:51:49 +0300 Subject: [PATCH] BEMHTML: Omit optional closing tags (fix for #360) --- docs/en/3-api.md | 40 ++++++++++++++++++++++++++++++++++++++++ docs/ru/3-api.md | 40 ++++++++++++++++++++++++++++++++++++++++ lib/bemhtml/index.js | 14 ++++++++++++-- lib/bemxjst/utils.js | 2 +- test/bemhtml-test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 3 deletions(-) diff --git a/docs/en/3-api.md b/docs/en/3-api.md index 42cc0748..841ba32c 100644 --- a/docs/en/3-api.md +++ b/docs/en/3-api.md @@ -6,6 +6,7 @@ - [Delimiters in names of BEM entities](#delimiters-in-names-of-bem-entities) - [Support JS-instances for elements (bem-core v4+)](#support-js-instances-for-elements-bem-core-v4) - [XHTML option](#xhtml-option) + - [Optional End Tags](#optional-end-tags) - [Escaping](#escaping) - [Extending BEMContext](#extending-bemcontext) - [Runtime linting](#runtime-linting) @@ -211,6 +212,45 @@ Result:
``` +### Optional End Tags + +With option `omitOptionalEndTags` template engine will ommit +optional end tags. The option is turn off by default. + +You can find list of optional end tags in specifications: +[HTML4](https://html.spec.whatwg.org/multipage/syntax.html#optional-tags) and +[HTML5](https://www.w3.org/TR/html5/syntax.html#optional-tags). + +```js +var bemxjst = require('bem-xjst'); +var templates = bemxjst.bemhtml.compile(function() { + // In this example we will add no templates. + // Default behaviour is used for HTML rendering. + }, { + // Turn off optional end tags + omitOptionalEndTags: true + }); + +var bemjson = { + tag: 'table', + content: { + tag: 'tr', + content: [ + { tag: 'th', content: 'table header' }, + { tag: 'td', content: 'table cell' } + ] + } +}; + +var html = templates.apply(bemjson); +``` + +Result: + +```html +
table headertable cell
+``` + ### Escaping You can set `escapeContent` option to `true` to escape string values of `content` field with [`xmlEscape`](6-templates-context.md#xmlescape). diff --git a/docs/ru/3-api.md b/docs/ru/3-api.md index 0e33f07d..4f82772d 100644 --- a/docs/ru/3-api.md +++ b/docs/ru/3-api.md @@ -6,6 +6,7 @@ - [Разделители в именовании БЭМ-сущностей](#Разделители-в-именовании-БЭМ-сущностей) - [Поддержка JS-экземпляров для элементов (bem-core v4+)](#Поддержка-js-экземпляров-для-элементов-bem-core-v4) - [Закрытие одиночных элементов](#Закрытие-одиночных-элементов) + - [Опциональные закрывающие теги](#Опциональные-закрывающие-теги) - [Экранирование](#Экранирование) - [Расширение BEMContext](#Расширение-bemcontext) - [Runtime проверки ошибок в шаблонах и входных данных](#Runtime-проверки-ошибок-в-шаблонах-и-входных-данных) @@ -209,6 +210,45 @@ var html = templates.apply(bemjson);
``` +### Опциональные закрывающие теги + +При помощи опции `omitOptionalEndTags` шаблонизатор не будет выводить +опциональные закрывающие теги. По умолчанию эта опция выключена. + +Список опциональных закрывающих тегов можно найти в спецификациях +[HTML4](https://html.spec.whatwg.org/multipage/syntax.html#optional-tags) и +[HTML5](https://www.w3.org/TR/html5/syntax.html#optional-tags). + +```js +var bemxjst = require('bem-xjst'); +var templates = bemxjst.bemhtml.compile(function() { + // В этом примере мы не добавляем пользовательских шаблонов. + // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию. + }, { + // Отключаем вывод опциональных закрывающих тегов + omitOptionalEndTags: true + }); + +var bemjson = { + tag: 'table', + content: { + tag: 'tr', + content: [ + { tag: 'th', content: 'table header' }, + { tag: 'td', content: 'table cell' } + ] + } +}; + +var html = templates.apply(bemjson); +``` + +В результате `html` будет содержать строку: + +```html +
table headertable cell
+``` + ### Экранирование Вы можете включить экранирование содержимого поля `content` опцией `escapeContent`. diff --git a/lib/bemhtml/index.js b/lib/bemhtml/index.js index 78953ae8..cd5044f2 100644 --- a/lib/bemhtml/index.js +++ b/lib/bemhtml/index.js @@ -10,6 +10,7 @@ function BEMHTML(options) { this._shortTagCloser = xhtml ? '/>' : '>'; this._elemJsInstances = options.elemJsInstances; + this._omitOptionalEndTags = options.omitOptionalEndTags; } inherits(BEMHTML, BEMXJST); @@ -128,6 +129,15 @@ BEMHTML.prototype.render = function render(context, return this.renderClose(out, context, tag, attrs, isBEM, ctx, content); }; +var OPTIONAL_END_TAGS = { + // html4 https://html.spec.whatwg.org/multipage/syntax.html#optional-tags + html: 1, head: 1, body: 1, p: 1, ul: 1, ol: 1, li: 1, dt: 1, dd: 1, + colgroup: 1, thead: 1, tbody: 1, tfoot: 1, tr: 1, th: 1, td: 1, option: 1, + + // html5 https://www.w3.org/TR/html5/syntax.html#optional-tags + /* dl — Neither tag is omissible */ rb: 1, rt: 1, rtc: 1, rp: 1, optgroup: 1 +}; + BEMHTML.prototype.renderClose = function renderClose(prefix, context, tag, @@ -152,14 +162,14 @@ BEMHTML.prototype.renderClose = function renderClose(prefix, if (content || content === 0) out += this.renderContent(content, isBEM); - out += ''; + if (!this._omitOptionalEndTags || !OPTIONAL_END_TAGS.hasOwnProperty(tag)) + out += ''; } if (this.canFlush) out = context._flush(out); return out; }; - BEMHTML.prototype.renderAttrs = function renderAttrs(attrs) { var out = ''; diff --git a/lib/bemxjst/utils.js b/lib/bemxjst/utils.js index c44d2b56..2b443b63 100644 --- a/lib/bemxjst/utils.js +++ b/lib/bemxjst/utils.js @@ -137,7 +137,7 @@ exports.extend = function extend(o1, o2) { return res; }; -var SHORT_TAGS = { // хэш для быстрого определения, является ли тэг коротким +var SHORT_TAGS = { // hash for quick check if tag short area: 1, base: 1, br: 1, col: 1, command: 1, embed: 1, hr: 1, img: 1, input: 1, keygen: 1, link: 1, meta: 1, param: 1, source: 1, wbr: 1 }; diff --git a/test/bemhtml-test.js b/test/bemhtml-test.js index 4abc8222..758e0ab5 100644 --- a/test/bemhtml-test.js +++ b/test/bemhtml-test.js @@ -119,4 +119,46 @@ describe('BEMHTML engine tests', function() { .should.equal('
'); }); }); + + describe('omitOptionalEndTags option', function() { + it('should omit optional end tags with option', function() { + compile(function() {}, { omitOptionalEndTags: true }) + .apply({ tag: 'p', content: 'test' }) + .should.equal('

test'); + }); + + it('should’t omit optional end tags without option', function() { + compile('') + .apply({ tag: 'p', content: 'test' }) + .should.equal('

test

'); + }); + + it('should’t omit optional end tags with option if tag is mandatory', + function() { + compile(function() {}, { omitOptionalEndTags: true }) + .apply({ tag: 'form', content: 'test' }) + .should.equal('
test
'); + }); + + it('should omit optional end tags from templates with option', function() { + compile(function() { block('para').tag()('p'); }, + { omitOptionalEndTags: true }) + .apply({ block: 'para', content: 'test' }) + .should.equal('

test'); + }); + + it('should’t omit optional end tags from templates w/o option', function() { + compile(function() { block('para').tag()('p'); }) + .apply({ block: 'para', content: 'test' }) + .should.equal('

test

'); + }); + + it('should’t omit optional end tags from templates with option ' + + 'if tag is mandatory', function() { + compile(function() { block('f').tag()('form'); }, + { omitOptionalEndTags: true }) + .apply({ block: 'f', content: 'test' }) + .should.equal('
test
'); + }); + }); });