Skip to content

Commit

Permalink
BEMHTML: Omit optional closing tags (fix for #360)
Browse files Browse the repository at this point in the history
  • Loading branch information
miripiruni committed Nov 9, 2016
1 parent 88a6d50 commit f0e8fc0
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 2 deletions.
40 changes: 40 additions & 0 deletions docs/en/3-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -211,6 +212,45 @@ Result:
<br>
```

### 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><tr><th>table header<td>table cell</table>
```

### Escaping

You can set `escapeContent` option to `true` to escape string values of `content` field with [`xmlEscape`](6-templates-context.md#xmlescape).
Expand Down
40 changes: 40 additions & 0 deletions docs/ru/3-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Разделители в именовании БЭМ-сущностей](#Разделители-в-именовании-БЭМ-сущностей)
- [Поддержка JS-экземпляров для элементов (bem-core v4+)](#Поддержка-js-экземпляров-для-элементов-bem-core-v4)
- [Закрытие одиночных элементов](#Закрытие-одиночных-элементов)
- [Опциональные закрывающие теги](#Опциональные-закрывающие-теги)
- [Экранирование](#Экранирование)
- [Расширение BEMContext](#Расширение-bemcontext)
- [Runtime проверки ошибок в шаблонах и входных данных](#Runtime-проверки-ошибок-в-шаблонах-и-входных-данных)
Expand Down Expand Up @@ -209,6 +210,45 @@ var html = templates.apply(bemjson);
<br>
```

### Опциональные закрывающие теги

При помощи опции `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><tr><th>table header<td>table cell</table>
```

### Экранирование

Вы можете включить экранирование содержимого поля `content` опцией `escapeContent`.
Expand Down
13 changes: 12 additions & 1 deletion lib/bemhtml/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function BEMHTML(options) {
this._shortTagCloser = xhtml ? '/>' : '>';

this._elemJsInstances = options.elemJsInstances;
this._omitOptionalEndTags = options.omitOptionalEndTags;
}

inherits(BEMHTML, BEMXJST);
Expand Down Expand Up @@ -141,6 +142,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,
Expand All @@ -165,7 +175,8 @@ BEMHTML.prototype.renderClose = function renderClose(prefix,
if (content || content === 0)
out += this.renderContent(content, isBEM);

out += '</' + tag + '>';
if (!this._omitOptionalEndTags || !OPTIONAL_END_TAGS.hasOwnProperty(tag))
out += '</' + tag + '>';
}

if (this.canFlush)
Expand Down
2 changes: 1 addition & 1 deletion lib/bemxjst/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
42 changes: 42 additions & 0 deletions test/bemhtml-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,46 @@ describe('BEMHTML engine tests', function() {
.should.equal('<br class="b">');
});
});

describe('omitOptionalEndTags option', function() {
it('should omit optional end tags with option', function() {
compile(function() {}, { omitOptionalEndTags: true })
.apply({ tag: 'p', content: 'test' })
.should.equal('<p>test');
});

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

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('<form>test</form>');
});

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('<p class="para">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('<p class="para">test</p>');
});

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('<form class="f">test</form>');
});
});
});

0 comments on commit f0e8fc0

Please sign in to comment.