Безопасная подстановка шаблонов Python в формате двойных скобок
Я пытаюсь заменить переменные в формате {{var}} шаблоном Python.
# -*- coding: utf-8 -*-
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
from string import Template
class CustomTemplate(Template):
delimiter = '{{'
pattern = r'''
\{\{(?:
(?P<escaped>\{\{)|
(?P<named>[_a-z][_a-z0-9]*)\}\}|
(?P<braced>[_a-z][_a-z0-9]*)\}\}|
(?P<invalid>)
)
'''
replacement_dict = {
"test": "hello"
}
tpl = '''{
"unaltered": "{{foo}}",
"replaced": "{{test}}"
}'''
a = CustomTemplate(tpl)
b = a.safe_substitute(replacement_dict)
print b
Выход:
{
"unaltered": "{{foo",
"replaced": "hello"
}
Как видите, переменная {{foo}} (которая не является частью замены) отсекает закрывающие скобки.
Я думаю, что так написано регулярное выражение (закрытие}}). Я хочу решить это с шаблоном, а не с любыми другими внешними библиотеками.
2 ответа
Я не уверен, как ты заставил это работать. В Linux, в Python 3.4.3 (я думал, что это работает с некоторой версией 2.7) мне нужно было сделать строку tpl
tpl = '''
"unaltered": "{{foo}}",
"replaced": "{{test}}"
'''
чтобы избежать получения TypeError
>>> tpl = '''
... "unaltered": "{{foo}}",
... "replaced": "{{test}}"
... '''
>>> a = CustomTemplate(tpl)
>>> a.template
'\n "unaltered": "{{foo}}",\n "replaced": "{{test}}"\n'
>>> b = a.safe_substitute(replacement_dict)
>>> b
'\n "unaltered": "{{foo}}",\n "replaced": "hello"\n'
Когда я делаю это, {{foo}} не изменяется.
Я попробовал приведенный выше код, и похоже, что код на самом деле не работает с Python 2.7.6. Я посмотрю, смогу ли я найти способ заставить его работать с 2.7.6, так как это похоже на распространенную версию последних дистрибутивов Linux.
Обновить:
Похоже, что это была известная ошибка на 2007 год. http://bugs.python.org/issue1686 Насколько я могу сказать, она была применена к python 3.2 в 2010 году и python 2.7 в 2014 году. Что касается того, чтобы заставить это работать Вы можете применить исправление для проблемы 1686 или переопределить safe_substitute() в своем классе фактическим исходным кодом из этого исправления https://hg.python.org/cpython/file/8a98ee6baa1e/Lib/string.py,
Этот код работает в 2.7.6 и 3.4.3
from string import Template
class CustomTemplate(Template):
delimiter = '{{'
pattern = r'''
\{\{(?:
(?P<escaped>\{\{)|
(?P<named>[_a-z][_a-z0-9]*)\}\}|
(?P<braced>[_a-z][_a-z0-9]*)\}\}|
(?P<invalid>)
)
'''
def safe_substitute(self, *args, **kws):
if len(args) > 1:
raise TypeError('Too many positional arguments')
if not args:
mapping = kws
elif kws:
mapping = _multimap(kws, args[0])
else:
mapping = args[0]
# Helper function for .sub()
def convert(mo):
named = mo.group('named') or mo.group('braced')
if named is not None:
try:
# We use this idiom instead of str() because the latter
# will fail if val is a Unicode containing non-ASCII
return '%s' % (mapping[named],)
except KeyError:
return mo.group()
if mo.group('escaped') is not None:
return self.delimiter
if mo.group('invalid') is not None:
return mo.group()
raise ValueError('Unrecognized named group in pattern',
self.pattern)
return self.pattern.sub(convert, self.template)
replacement_dict = {
"test": "hello"
}
tpl = '''{
"escaped": "{{{{",
"unaltered": "{{foo}}",
"replaced": "{{test}}",
"invalid": "{{az"
}'''
a = CustomTemplate(tpl)
b = a.safe_substitute(replacement_dict)
print (b)
Результаты:
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import template
{
"escaped": "{{",
"unaltered": "{{foo}}",
"replaced": "hello",
"invalid": "{{az"
}
>>>
проблема
- разработчик reggie хочет использовать собственные разделители-заполнители со строкой шаблона PEP292 в Python.
Решение (обходной путь)
- разработчик может изменить префиксный символ (ы).
- этот подход позволяет использовать синтаксис разделителя из совершенно другого языка программирования.
- этот подход выполняет основную цель изменения синтаксиса заполнителя, чтобы предотвратить проблемы коллизии.
- этот подход обеспечивает наиболее простой случай создания подклассов string.Template.
Ловушки
- этот подход не поддерживает заполнители с двойными фигурными скобками, такие как
{{var}}
, - этот подход не помогает в случаях, когда разработчик не может изменить синтаксис шаблона по своему желанию.
пример
строка импорта проходить Класс TemplateRubyish(string.Template): разделитель = '#' idpattern = r'[az][\w\,\+\=\:\-\.\x2f\x5c\*\(\)\[\]\x7c]*' Класс TemplatePerlish(string.Template): delimiter = 'qq' idpattern = r'[az][\w\,\+\=\:\-\.\x2f\x5c\*\(\)\[\]\x7c]*' replace_dict = {} replacement_dict.update({ "возраст": "34", "fname": "Homer", "lname": "Симпсон", }); проходить ## vout = TemplateRubyish("""\ Привет #{имя_фамила} #{имя_имя}, Вам #{возраст} лет. """).Safe_substitute(replacement_dict); печать (Vout) проходить ## vout = TemplatePerlish("""\ Привет qq{имя_файла} qq{имя_имя}, Вам qq{age} лет. """).Safe_substitute(replacement_dict); печать (Vout) проходить
Результат
Привет Гомер Симпсон, Вам 34 года. Привет Гомер Симпсон, Вам 34 года.