Безопасная подстановка шаблонов 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 года.
Другие вопросы по тегам