Django, пользовательские фильтры шаблонов - проблемы с регулярным выражением - PullRequest
2 голосов
/ 23 мая 2009

Я пытаюсь реализовать шаблонный фильтр WikiLink в Django, который запрашивает модель базы данных, чтобы дать разные ответы в зависимости от существования страницы, идентичные красным ссылкам Википедии. Фильтр не вызывает ошибку, но вместо этого ничего не делает для ввода.

WikiLink определяется как: [[ThisIsAWikiLink | This is the alt text]]

Вот рабочий пример, который не запрашивает базу данных:

from django import template
from django.template.defaultfilters import stringfilter
from sites.wiki.models import Page
import re

register = template.Library()

@register.filter
@stringfilter
def wikilink(value):
    return re.sub(r'\[\[ ?(.*?) ?\| ?(.*?) ?\]\]', r'<a href="/Sites/wiki/\1">\2</a>', value)
wikilink.is_safe = True

Вход (value) - это многострочная строка, содержащая HTML и множество WikiLinks.

Ожидаемый выход заменяет [[ThisIsAWikiLink | This is the alt text]] на

  • <a href="/Sites/wiki/ThisIsAWikiLink">This is the alt text</a>

    или , если "ThisIsAWikiLink" не существует в базе данных:

  • <a href="/Sites/wiki/ThisIsAWikiLink/edit" class="redlink">This is the alt text</a>

и возвращаемое значение.

Вот нерабочий код (отредактированный в ответ на комментарии / ответы):

from django import template
from django.template.defaultfilters import stringfilter
from sites.wiki.models import Page
import re

register = template.Library()

@register.filter
@stringfilter
def wikilink(value):
    m = re.match(r'\[\[ ?(.*?) ?\| ?(.*?) ?\]\]', value)

    if(m):
        page_alias = m.group(2)
        page_title = m.group(3)
        try:
            page = Page.objects.get(alias=page_alias)
            return re.sub(r'(\[\[)(.*)\|(.*)(\]\])', r'<a href="Sites\/wiki\/\2">\3</a>', value)
        except Page.DoesNotExist:
             return re.sub(r'(\[\[)(.*)\|(.*)(\]\])', r'<a href="Sites\/wiki\/\2\/edit" class="redlink">\3</a>', value)
    else:
        return value
wikilink.is_safe = True

Код должен сделать следующее:

  • извлечь все WikiLinks в значение
  • запросить модель Page , чтобы узнать, существует ли страница
  • заменить все вики-ссылки обычными ссылками, стилизованными в зависимости от каждого существования вики-страницы.
  • возвращает измененное значение

Обновленный вопрос: Какое регулярное выражение (метод) может возвращать список WikiLinks на python, который можно изменить и использовать для замены исходных совпадений (после изменения).

Edit:

Я бы хотел сделать что-то вроде этого:

def wikilink(value):
    regex = re.magic_method(r'\[\[ ?(.*?) ?\| ?(.*?) ?\]\]', value)

    foreach wikilink in regex:
         alias = wikilink.group(0)
         text = wikilink.group(1)

         if(alias exists in Page):
              regex.sub("<a href="+alias+">"+ text +"</a>")
         else:
              regex.sub("<a href="+alias+" class='redlink'>"+ text +"</a>")

    return value

Ответы [ 4 ]

4 голосов
/ 23 мая 2009

Если ваша строка содержит другой текст в дополнение к вики-ссылке, ваш фильтр не будет работать, потому что вы используете re.match вместо re.search. re.match соответствует началу строки. re.search соответствует в любом месте строки. См. совпадение с поиском .

Кроме того, ваше регулярное выражение использует жадный *, поэтому он не будет работать, если одна строка содержит несколько вики-ссылок. Вместо этого используйте *?, чтобы сделать его нежадным:

re.search(r'\[\[(.*?)\|(.*?)\]\]', value)

Edit:

Что касается советов по исправлению вашего кода, я предлагаю вам использовать re.sub с обратным вызовом . Преимущества:

  • Это работает правильно, если у вас есть несколько вики-ссылок в одной строке.
  • Достаточно одного прохода через строку. Вам не нужен пропуск, чтобы найти вики-ссылки, и другой, чтобы сделать замену.

Вот эскиз внедрения:

import re

WIKILINK_RE = re.compile(r'\[\[(.*?)\|(.*?)\]\]')

def wikilink(value):
  def wikilink_sub_callback(match_obj):
    alias = match_obj.group(1).strip()
    text = match_obj.group(2).strip()
    if(alias exists in Page):
      class_attr = ''
    else:
      class_attr = ' class="redlink"'
    return '<a href="%s"%s>%s</a>' % (alias, class_attr, text)

  return WIKILINK_RE.sub(wikilink_sub_callback, value)
3 голосов
/ 23 мая 2009

Это тип проблемы, которая быстро выпадает из небольшого набора юнит-тестов.

Части фильтра, которые можно протестировать изолированно (с небольшой перестройкой кода):

  • Определение, содержит ли значение искомый шаблон
  • Какая строка генерируется при наличии соответствующей страницы
  • Какая строка генерируется, если нет соответствующей страницы

Это поможет вам определить, где что-то идет не так. Вы, вероятно, обнаружите, что вам нужно переписать регулярные выражения, чтобы учесть дополнительные пробелы вокруг |.

Кроме того, на первый взгляд кажется, что ваш фильтр пригоден для использования. Вы утверждаете, что результат безопасен, но вы не отфильтровали альтернативный текст для таких мерзостей, как теги скрипта.

1 голос
/ 24 мая 2009

Код:

import re

def page_exists(alias):
    if alias == 'ThisIsAWikiLink':
        return True

    return False

def wikilink(value):
    if value == None:
        return None

    for alias, text in re.findall('\[\[\s*(.*?)\s*\|\s*(.*?)\s*\]\]',value):
        if page_exists(alias):
            value = re.sub('\[\[\s*%s\s*\|\s*%s\s*\]\]' % (alias,text), '<a href="/Sites/wiki/%s">%s</a>' % (alias, text),value)            
        else:
            value = re.sub('\[\[\s*%s\s*\|\s*%s\s*\]\]' % (alias,text), '<a href="/Sites/wiki/%s/edit/" class="redtext">%s</a>' % (alias, text), value)

    return value

Пример результатов:

>>> import wikilink
>>> wikilink.wikilink(None)
>>> wikilink.wikilink('')
''
>>> wikilink.wikilink('Test')
'Test'
>>> wikilink.wikilink('[[ThisIsAWikiLink | This is the alt text]]')
'<a href="/Sites/wiki/ThisIsAWikiLink">This is the alt text</a>'
>>> wikilink.wikilink('[[ThisIsABadWikiLink | This is the alt text]]')
'<a href="/Sites/wiki/ThisIsABadWikiLink/edit/" class="redtext">This is the alt text</a>'
>>> wikilink.wikilink('[[ThisIsAWikiLink | This is the alt text]]\n[[ThisIsAWikiLink | This is another instance]]')
'<a href="/Sites/wiki/ThisIsAWikiLink">This is the alt text</a>\n<a href="/Sites/wiki/ThisIsAWikiLink">This is another instance</a>'
>>> wikilink.wikilink('[[ThisIsAWikiLink | This is the alt text]]\n[[ThisIsAWikiLink | This is another instance]]')

Общие комментарии:

  • findall - волшебная функция, которую вы ищете
  • Измените page_exists , чтобы выполнить любой запрос, который вы хотите
  • Уязвим к внедрению HTML (как упомянуто Дейвом В. Смитом выше)
  • Перекомпилировать регулярное выражение на каждой итерации неэффективно
  • Запросы к базе данных каждый раз неэффективны

Я думаю, что при таком подходе вы довольно быстро столкнетесь с проблемами производительности.

0 голосов
/ 24 мая 2009

Это рабочий код на тот случай, если он кому-то нужен:

from django import template
from django.template.defaultfilters import stringfilter
from sites.wiki.models import Page
import re

register = template.Library()

@register.filter
@stringfilter
def wikilink(value):
  WIKILINK_RE = re.compile(r'\[\[ ?(.*?) ?\| ?(.*?) ?\]\]')

  def wikilink_sub_callback(match_obj):
    alias = match_obj.group(1).strip()
    text = match_obj.group(2).strip()

    class_attr = ''
    try:
        Page.objects.get(alias=alias)
    except Page.DoesNotExist:
        class_attr = ' class="redlink"'
    return '<a href="%s"%s>%s</a>' % (alias, class_attr, text)

  return WIKILINK_RE.sub(wikilink_sub_callback, value)
wikilink.is_safe = True

Большое спасибо за все ответы!

...