Разбор «иерархического» URL с помощью регулярных выражений, если в логике разбора есть разбиения - PullRequest
0 голосов
/ 11 июня 2018

Есть ли способ настроить оставшийся шаблон регулярного выражения в соответствии с тем, что уже было сопоставлено?Грубый набросок, иллюстрирующий идею:

          pattern
       /     |     \
      /      |      \
prefix1   prefix2   prefix3
   |         |         |
postfix1  postfix2  postfix3

Это довольно теоретический вопрос;нижеприведенное практическое приложение предназначено только для иллюстрации.

Я пытаюсь найти первые URL-адреса популярных платформ для размещения кода, таких как github, gitlab и т. д., в большом тексте.Проблема в том, что все платформы имеют разные шаблоны URL:

github.com/<user>/<repo>
gitlab.com/<group1>/<group2>/.../<repo>
sourceforge.net/projects/<repo>

Я могу использовать выражения lookbehind, но тогда выражение становится действительно чудовищным (Python re):

pattern = re.compile(
    r"(github\.com|bitbucket\.org|gitlab\.com|sourceforge\.net)/"
    # middle part - empty for all except sourceforge
    r"(?:(?<=github\.com/)|(?<=bitbucket\.org/)|(?<=gitlab\.com/)|"
    r"(?<=sourceforge\.net/)projects/)("
    # final part, the repository pattern
    r"(?<=github\.com/)[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+|"
    r"(?<=bitbucket\.org/)[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+|"
    r"(?<=gitlab\.com/)[a-zA-Z0-9_.-]+(?:/[a-zA-Z0-9_.-]+)+|"
    r"(?<=sourceforge\.net/projects/)[a-zA-Z0-9_.-]+"
    r")")

Есть либолее элегантный способ сделать что-то подобное?

1 Ответ

0 голосов
/ 12 июня 2018

Лучшим способом, вероятно, было бы скорее использовать собственный синтаксический анализатор и анализировать стиль конечного автомата: сначала определите сайт, а затем перейдите к определенному для сайта маршруту:

patterns={
    'github.com': r'/(?P<user>[^/]+)/(?P<project>[^/#]+)(?:[/#]|$)',
    'sourceforge.net': r'/projects/(?P<project>)[^/]+/',
    <etc etc etc>
}

import urllib.parse
pr = urllib.parse.urlparse(url)

site = pr.hostname    # in case port is specified
parts = re.match(patterns[site], pr.path).groupdict()

Вместо регулярных выражений путиможет быть также проанализирован с помощью конечного автомата, который, вероятно, будет более управляемым, если будут дальнейшие разбиения:
( они рекомендуют enum вместо магических строк для состояний ; Iиспользовали магические строки исключительно для упрощения примера кода)

def parse_github(path):
    r = argparse.Namespace()
    pp = path.split('/')
    p = pp.pop(0)
    assert(p == '')
    state='user'
    for p in pp:    # we dont need to backtrack in this case,
                    # so `for' is a fitting mechanism to iterate
                    # over the parts.
                    # if we needed to backtrack, we'd have to use
                    # an index variable or a stack or something
        if state=='user':
            r.user=p
            state='project'
        else if state=='project':
            r.project==p
            state='kind'
        else if state=='kind':
            if p in {'pull','commit','blob'}:
                state=p
            else: break  #end parsing, ignore anything that's left
        else if state=='pull':
            r.pr=p
            state='pr_tab'
        <etc etc>
    return r

В принципе, здесь нет рекурсивных конструкций, поэтому можно делать только с регулярными выражениями,но это очень неловко:

site_patterns = [
    r"(github\.com/)[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+",
    r"(bitbucket\.org/)[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+",
    r"(gitlab\.com/)[a-zA-Z0-9_.-]+(?:/[a-zA-Z0-9_.-]+)+",
    r"(sourceforge\.net/projects/)[a-zA-Z0-9_.-]+",
    <etc etc etc>
    ]
r_all = re.compile("("+"|".join(site_patterns)+")")   #good luck debugging this monster
...