Python + анализ пользовательского файла конфигурации - PullRequest
1 голос
/ 23 февраля 2010

У меня довольно большой пользовательский конфигурационный файл, который мне нужен для извлечения данных раз в неделю. Это конфигурационный файл "in house", который не соответствует ни одному из известных стандартов, таких как INI или тому подобное.

Мой быстрый и грязный подход состоял в том, чтобы использовать re, чтобы найти заголовок раздела, который я хочу, и затем извлечь одну или две строки информации под этим заголовком, который я хочу. Это оказывается довольно сложной задачей, и я думаю, что должен быть более простой / более надежный способ сделать это, но я продолжаю думать, что мне потребуется реализовать полный синтаксический анализатор для анализа этого файла, а затем только для извлечения 5 строк данные мне нужны.

«Разделы» выглядят примерно так:

Registry com.name.version =
Registry "unique-name I search for using re" =
    String name = "modulename";
    String timestamp = "not specified";
    String java = "not specified";
    String user = "not specified";
    String host = "not specified";
    String system = "not specified";
    String version = "This I want";
    String "version-major" = "not specified";
    String "version-minor" = "not specified";
    String scm = "not specified";
    String scmrevision = "not specified";
    String mode = "release";
    String teamCityBuildNumber = "not specified";
;

Ответы [ 4 ]

2 голосов
/ 23 февраля 2010

Простой синтаксический анализатор, использующий pyparsing, может дать вам нечто похожее на десериализатор, который позволит вам получить доступ к полям по имени ключа (как в dict) или как атрибуты. Вот парсер:

from pyparsing import (Suppress,quotedString,removeQuotes,Word,alphas,
        alphanums, printables,delimitedList,Group,Dict,ZeroOrMore,OneOrMore)

# define punctuation and constants - suppress from parsed output
EQ,SEMI = map(Suppress,"=;")
REGISTRY = Suppress("Registry")
STRING = Suppress("String")

# define some basic building blocks
quotedString.setParseAction(removeQuotes)
ident = quotedString | Word(printables)
value = quotedString
java_path = delimitedList(Word(alphas,alphanums+"_"), '.', combine=True)

# define the config file sections
string_defn = Group(STRING + ident + EQ + value + SEMI)
registry_section = Group(REGISTRY + ident + EQ + Dict(ZeroOrMore(string_defn)))

# special definition for leading java module
java_module = REGISTRY + java_path("path") + EQ

# define the overall config file format
config = java_module("java") + Dict(OneOrMore(registry_section))

Вот тест, использующий ваши данные (считанный из вашего файла данных в config_source):

data = config.parseString(config_source)
print data.dump()
print data["unique-name I search for using re"].version
print data["unique-name I search for using re"].mode
print data["unique-name I search for using re"]["version-major"]

Печать:

['com.name.version', ['unique-name I search for using re', ...
- java: ['com.name.version']
  - path: com.name.version
- path: com.name.version
- unique-name I search for using re: [['name', 'modulename'], ...
  - host: not specified
  - java: not specified
  - mode: release
  - name: modulename
  - scm: not specified
  - scmrevision: not specified
  - system: not specified
  - teamCityBuildNumber: not specified
  - timestamp: not specified
  - user: not specified
  - version: This I want
  - version-major: not specified
  - version-minor: not specified
This I want
release
not specified
1 голос
/ 23 февраля 2010

Если вы ищете только специальный контент, использование регулярных выражений хорошо; если вам нужно все прочитать, лучше постройте себе парсер.

>> s = ''' ... ''' # as above
>> t = re.search( 'Registry "unique-name" =(.*?)\n;', s, re.S ).group( 1 )
>> u = re.findall( '^\s*(\w+) "?(.*?)"? = "(.*?)";\s*$', t, re.M )
>> for x in u:
       print( x )

('String', 'name', 'modulename')
('String', 'timestamp', 'not specified')
('String', 'java', 'not specified')
('String', 'user', 'not specified')
('String', 'host', 'not specified')
('String', 'system', 'not specified')
('String', 'version', 'This I want')
('String', 'version-major', 'not specified')
('String', 'version-minor', 'not specified')
('String', 'scm', 'not specified')
('String', 'scmrevision', 'not specified')
('String', 'mode', 'release')

edit: Хотя вышеупомянутая версия должна работать для нескольких Registry разделов, вот более строгая версия:

t = re.search( 'Registry "unique-name"\s*=\s*((?:\s*\w+ "?[^"=]+"?\s*=\s*"[^"]*?";\s*)+)\s*;', s ).group( 1 )
u = re.findall( '^\s*(\w+) "?([^"=]+)"?\s*=\s*"([^"]*?)";\s*$', t, re.M )
0 голосов
/ 23 февраля 2010

Я думаю, вы должны создать простой парсер, который создает словари разделов со словарями ключей. Что-то вроде:

#!/usr/bin/python

import re

re_section = re.compile('Registry (.*)=', re.IGNORECASE)
re_value = re.compile('\s+String\s+(\S+)\s*=\s*(.*);')

txt = '''
Registry com.name.version =
Registry "unique-name I search for using re" =
        String name = "modulename";
        String timestamp = "not specified";
        String java = "not specified";
        String user = "not specified";
        String host = "not specified";
        String system = "not specified";
        String version = "This I want";
        String "version-major" = "not specified";
        String "version-minor" = "not specified";
        String scm = "not specified";
        String scmrevision = "not specified";
        String mode = "release";
        String teamCityBuildNumber = "not specified";
'''

my_config = {}
section = ''
lines = txt.split('\n')
for l in lines:
    rx = re_section.search(l)
    if rx:
        section = rx.group(1)
        section = section.strip('" ')
        continue
    rx = re_value.search(l)
    if rx:
        (k, v) = (rx.group(1).strip('" '), rx.group(2).strip('" '))
        try:
            my_config[section][k] = v
        except KeyError:
            my_config[section] = {k: v}

Тогда, если вы:

print my_config["unique-name I search for using re"]['version']

будет выводиться:

This I want
0 голосов
/ 23 февраля 2010

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

В вашем случае найдите r'unique-name I search for using re"\s*=\s*', а затем обрежьте после совпадения. Затем найдите r'\n\s*;\s*\n' и вырежьте перед совпадением. Это оставляет вам значения, которые вы можете нарезать, используя другое регулярное выражение.

...