template_parser: Add parsing support for self closing tags as per HTML5.

In this commit we add support for some tags which are also called
void-elements according to
http://w3c.github.io/html/syntax.html#void-elements to be parsed by
our template parser and get tagged as singleton_html_tags.

Fixes: #8387.
This commit is contained in:
Aditya Bansal
2018-02-16 02:46:40 +05:30
committed by showell
parent 271cfd4d7a
commit 6fce1d7834
9 changed files with 46 additions and 18 deletions

View File

@@ -7,7 +7,7 @@
<p class="hotspot-description">{{description}}</p> <p class="hotspot-description">{{description}}</p>
</div> </div>
<div class="hotspot-popover-bottom"> <div class="hotspot-popover-bottom">
<img class="hotspot-img" alt=_("hotspot illustration") src="{{img}}"></img> <img class="hotspot-img" alt=_("hotspot illustration") src="{{img}}">
<button class="hotspot-confirm">{{t 'Got it!' }}</button> <button class="hotspot-confirm">{{t 'Got it!' }}</button>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
{{#with realm_domain}} {{#with realm_domain}}
<tr> <tr>
<td class="domain">{{domain}}</td> <td class="domain">{{domain}}</td>
<td><input type="checkbox" class="allow-subdomains" {{#if allow_subdomains}}checked{{/if}}></input></td> <td><input type="checkbox" class="allow-subdomains" {{#if allow_subdomains}}checked{{/if}}></td>
<td><button class="button btn-danger small rounded delete_realm_domain">{{t "Remove" }}</button></td> <td><button class="button btn-danger small rounded delete_realm_domain">{{t "Remove" }}</button></td>
</tr> </tr>
{{/with}} {{/with}}

View File

@@ -11,7 +11,7 @@
<div class="settings-section-title">{{t "Add new default stream" }}</div> <div class="settings-section-title">{{t "Add new default stream" }}</div>
<div class="inline-block" id="default_stream_inputs"> <div class="inline-block" id="default_stream_inputs">
<label for="default_stream_name">{{t "Stream name" }}</label> <label for="default_stream_name">{{t "Stream name" }}</label>
<input class="create_default_stream" type="text" placeholder="{{t "Stream name" }}" name="stream_name" autocomplete="off" aria-label="{{t "Stream name" }}"></input> <input class="create_default_stream" type="text" placeholder="{{t "Stream name" }}" name="stream_name" autocomplete="off" aria-label="{{t "Stream name" }}">
</div> </div>
<div class="inline-block"> <div class="inline-block">
<button type="submit" id="do_submit_stream" class="button rounded sea-green">{{t "Add stream" }}</button> <button type="submit" id="do_submit_stream" class="button rounded sea-green">{{t "Add stream" }}</button>

View File

@@ -14,8 +14,8 @@
</tbody> </tbody>
<tfoot> <tfoot>
<tr id="add-realm-domain-widget"> <tr id="add-realm-domain-widget">
<td><input type="text" class="new-realm-domain" placeholder="acme.com"></input></td> <td><input type="text" class="new-realm-domain" placeholder="acme.com"></td>
<td><input type="checkbox" class="new-realm-domain-allow-subdomains"></input></td> <td><input type="checkbox" class="new-realm-domain-allow-subdomains"></td>
<td><button type="button" class="button sea-green small rounded" id="submit-add-realm-domain">{{t "Add" }}</button></td> <td><button type="button" class="button sea-green small rounded" id="submit-add-realm-domain">{{t "Add" }}</button></td>
</tr> </tr>
</tfoot> </tfoot>

View File

@@ -49,7 +49,7 @@
<p> <p>
Cheers, Cheers,
<br></br> <br />
The Zulip Team The Zulip Team
</p> </p>
{% endblock %} {% endblock %}

View File

@@ -517,49 +517,49 @@
<div class="integration-icons"> <div class="integration-icons">
<a href="/integrations/doc/travis"> <a href="/integrations/doc/travis">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/travis.svg" alt="{{ _('Travis logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/travis.svg" alt="{{ _('Travis logo') }}">
<h3 class="integration-name">Travis CI</h3> <h3 class="integration-name">Travis CI</h3>
<p class="integration-description">See build results immediately</p> <p class="integration-description">See build results immediately</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/github"> <a href="/integrations/doc/github">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/github.svg" alt="{{ _('Github logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/github.svg" alt="{{ _('Github logo') }}">
<h3 class="integration-name">Github</h3> <h3 class="integration-name">Github</h3>
<p class="integration-description">Track issues and pull requests</p> <p class="integration-description">Track issues and pull requests</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/heroku"> <a href="/integrations/doc/heroku">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/heroku.svg" alt="{{ _('Heroku logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/heroku.svg" alt="{{ _('Heroku logo') }}">
<h3 class="integration-name">Heroku</h3> <h3 class="integration-name">Heroku</h3>
<p class="integration-description">Keep up with deployments</p> <p class="integration-description">Keep up with deployments</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/zendesk"> <a href="/integrations/doc/zendesk">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/zendesk.svg" alt="{{ _('Zendesk logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/zendesk.svg" alt="{{ _('Zendesk logo') }}">
<h3 class="integration-name">Zendesk</h3> <h3 class="integration-name">Zendesk</h3>
<p class="integration-description">Receive support tickets and updates</p> <p class="integration-description">Receive support tickets and updates</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/jira"> <a href="/integrations/doc/jira">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/jira.svg" alt="{{ _('JIRA logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/jira.svg" alt="{{ _('JIRA logo') }}">
<h3 class="integration-name">JIRA</h3> <h3 class="integration-name">JIRA</h3>
<p class="integration-description">Monitor project bugs and issues</p> <p class="integration-description">Monitor project bugs and issues</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/sentry"> <a href="/integrations/doc/sentry">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/sentry.svg" alt="{{ _('Sentry logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/sentry.svg" alt="{{ _('Sentry logo') }}">
<h3 class="integration-name">Sentry</h3> <h3 class="integration-name">Sentry</h3>
<p class="integration-description">See real-time error tracking</p> <p class="integration-description">See real-time error tracking</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/pagerduty" class="hide-1"> <a href="/integrations/doc/pagerduty" class="hide-1">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/pagerduty.svg" alt="{{ _('Pagerduty logo') }}"></img> <img class="integration-logo" src="/static/images/integrations/logos/pagerduty.svg" alt="{{ _('Pagerduty logo') }}">
<h3 class="integration-name">Pagerduty</h3> <h3 class="integration-name">Pagerduty</h3>
<p class="integration-description">Connect to your monitoring systems</p> <p class="integration-description">Connect to your monitoring systems</p>
</div> </div>

View File

@@ -1,4 +1,4 @@
from typing import Callable, List, Optional from typing import Callable, List, Optional, Text
class TemplateParserException(Exception): class TemplateParserException(Exception):
def __init__(self, message): def __init__(self, message):
@@ -112,7 +112,7 @@ def tokenize(text):
if is_special_html_tag(s, tag): if is_special_html_tag(s, tag):
kind = 'html_special' kind = 'html_special'
elif s.endswith('/>'): elif is_self_closing_html_tag(s, tag):
kind = 'html_singleton' kind = 'html_singleton'
else: else:
kind = 'html_start' kind = 'html_start'
@@ -272,6 +272,24 @@ def is_special_html_tag(s, tag):
# type: (str, str) -> bool # type: (str, str) -> bool
return tag in ['link', 'meta', '!DOCTYPE'] return tag in ['link', 'meta', '!DOCTYPE']
def is_self_closing_html_tag(s: Text, tag: Text) -> bool:
self_closing_tag = tag in [
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'param',
'source',
'track',
'wbr',
]
singleton_tag = s.endswith('/>')
return self_closing_tag or singleton_tag
def is_django_block_tag(tag): def is_django_block_tag(tag):
# type: (str) -> bool # type: (str) -> bool
return tag in [ return tag in [

View File

@@ -515,7 +515,7 @@ def build_custom_checkers(by_lang):
'exclude_line': [('templates/zerver/register.html', 'placeholder="acme"'), 'exclude_line': [('templates/zerver/register.html', 'placeholder="acme"'),
('templates/zerver/register.html', 'placeholder="Acme or Aκμή"'), ('templates/zerver/register.html', 'placeholder="Acme or Aκμή"'),
('static/templates/settings/realm-domains-modal.handlebars', ('static/templates/settings/realm-domains-modal.handlebars',
'<td><input type="text" class="new-realm-domain" placeholder="acme.com"></input></td>'), '<td><input type="text" class="new-realm-domain" placeholder="acme.com"></td>'),
("static/templates/user-groups-admin.handlebars", ("static/templates/user-groups-admin.handlebars",
'<input type="text" name="name" id="user_group_name" placeholder="hamletcharacters" />')], '<input type="text" name="name" id="user_group_name" placeholder="hamletcharacters" />')],
'exclude': set(["static/templates/settings/emoji-settings-admin.handlebars", 'exclude': set(["static/templates/settings/emoji-settings-admin.handlebars",
@@ -523,11 +523,11 @@ def build_custom_checkers(by_lang):
"static/templates/settings/bot-settings.handlebars", "static/templates/settings/bot-settings.handlebars",
"templates/zerver/email_log.html"]), "templates/zerver/email_log.html"]),
'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'], 'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'],
'bad_lines': ['<input placeholder="foo"></input>']}, 'bad_lines': ['<input placeholder="foo">']},
{'pattern': "placeholder='[^{]", {'pattern': "placeholder='[^{]",
'description': "`placeholder` value should be translatable.", 'description': "`placeholder` value should be translatable.",
'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'], 'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'],
'bad_lines': ["<input placeholder='foo'></input>"]}, 'bad_lines': ["<input placeholder='foo'>"]},
{'pattern': "aria-label='[^{]", {'pattern': "aria-label='[^{]",
'description': "`aria-label` value should be translatable.", 'description': "`aria-label` value should be translatable.",
'good_lines': ['<button type="button" class="close close-alert-word-status" aria-label="{{t \'Close\' }}">'], 'good_lines': ['<button type="button" class="close close-alert-word-status" aria-label="{{t \'Close\' }}">'],

View File

@@ -225,6 +225,16 @@ class ParserTest(unittest.TestCase):
self.assertEqual(token.kind, 'html_singleton') self.assertEqual(token.kind, 'html_singleton')
self.assertEqual(token.tag, 'br') self.assertEqual(token.tag, 'br')
tag = '<input>bla'
token = tokenize(tag)[0]
self.assertEqual(token.kind, 'html_singleton')
self.assertEqual(token.tag, 'input')
tag = '<input />bla'
token = tokenize(tag)[0]
self.assertEqual(token.kind, 'html_singleton')
self.assertEqual(token.tag, 'input')
tag = '</a>bla' tag = '</a>bla'
token = tokenize(tag)[0] token = tokenize(tag)[0]
self.assertEqual(token.kind, 'html_end') self.assertEqual(token.kind, 'html_end')