Skip to content

Commit

Permalink
Доработка лексера и парсера контролей для избавления от конфликтов ге…
Browse files Browse the repository at this point in the history
…нерации LARL таблицы
  • Loading branch information
WoolenSweater committed Nov 11, 2022
1 parent 554a648 commit 17087b7
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 97 deletions.
25 changes: 16 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

### [1.3.1] - 2022-11-11
- Небольшая доработка лексера и парсера контролей.
- При генерации правил больше не возникают конфликты, которые до этого решались автоматически ply'ем.
- Исправил ошибку при проверке котроля в формуле которого указан раздел отсутствующий в отчёте и не описанный в самом шаблоне.
- Поправил лицензию, дополнил README и setup.py.


### [1.3.0] - 2022-11-09
- Исправлена ошибка из-за которой при проверках контролей со спецификами стали учитываться строки без специфик.
- Оказалось, что более нет необходимости в проверках на "игнорируемые" специфики при итерации по строкам.
Expand Down Expand Up @@ -78,11 +85,11 @@
### [1.2.0] - 2021-04-13

- Изменение логики итерации по строкам раздела и обработке пустых строк.
- Методы `items` и `get_rows` секции больше не принимают словарь специфик. Фильтрация будет происходить снаружи.
- Методы `items` и `get_rows` класса `Section` больше не принимают словарь специфик. Фильтрация будет происходить снаружи.
- Метод `get_rows` возвращает список с одной пустой строкой "заглушкой" если эта строка отсутствует в отчёте.
- Метод проверки строки на соответствие спицификам перенес из `Section` в `Row` и переименовал в `filter`.
- Из класса `ElemList` удалены методы заполнения строки элементами заглушками если вернулся пустой список, так как `get_rows` теперь всегда вернёт хотя бы одну строку. Колонками её заполнит метод, который обрабатывает не пустой список.
- Исправил ошибку проверки формата отчёта по схемам в которых отсутствуют параметры, определяющие формат проверок для специфики указанной секции и строки.
- Исправил ошибку проверки формата отчёта по схемам в которых отсутствуют параметры, определяющие формат проверок для специфики указанного раздела и строки.
- Сделал хэлпер ограниченно имитирующий `MultiDict` из одноименной библиотеки. Помогает в работе с мультистроками.
- Отрефакторил метод `_zip`. Вынес каждый шаг в отдельный метод для читабельности.
- Перед логической проверкой теперь происходит проверка, что ни один элемент не пустой. Иначе будет возбуждено исключение `NoElemToCompareError`.
Expand Down Expand Up @@ -146,12 +153,12 @@
### [0.7.1] - 2021-02-12

- Теперь на вход передаются строго ElementTree, Element, bytes, file name/path, или file-like объекты.
- Добавил сортировку по возрастанию при итерациям по строкам секции отчёта. Ранее порядок не гарантировался, из-за чего при определенном сочетаний ошибок, результат проверки мог отличаться от запуска к запуску.
- Добавил сортировку по возрастанию при итерациям по строкам раздела отчёта. Ранее порядок не гарантировался, из-за чего при определенном сочетаний ошибок, результат проверки мог отличаться от запуска к запуску.
- Переработал обработку ошибок в "блоке" проверок формата.
- Все ошибки перенесены в отдельный модуль и выполнены в виде Exception'ов.
- Некоторые ошбики общего вида, разделились на несколько самостоятельных. Следовательно, появились новые коды ошибок, а у некоторых старых они изменились.
- При возбуждении какой либо ошибки, проверка прекращается. Другими словами, проверки стали "ленивыми".
- Добавил исключения для ситуаций, когда для проверяемой секции или значения не указаны правила в шаблоне.
- Добавил исключения для ситуаций, когда для проверяемого раздела или значения не указаны правила в шаблоне.
- Выписал все возможные на данный момент коды ошибок в docs.md
- Исправил ошибку в сообщениях ошибок проверки специфик.

Expand Down Expand Up @@ -186,7 +193,7 @@
### [0.6.1] - 2020-09-17

- Реализовал следующие проверки:
- Наличия в отчёте секций в соответствии со схемой.
- Наличия в отчёте раздела в соответствии со схемой.
- Формат ОКПО указанного в отчете в блоке title.
- Формат года указанного в отчете.

Expand All @@ -204,14 +211,14 @@
### [0.5.1] - 2020-09-15

- Исправил суммирование элементов.
- Коды секций/строк/графов схемы теперь тоже проходят трансформацию `строка -> число -> строка`, так как и в схемах замечены коды вида "01".
- Коды раздела/строк/графов схемы теперь тоже проходят трансформацию `строка -> число -> строка`, так как и в схемах замечены коды вида "01".
- Исправил добавление в список ошибок сообщения о непредвиденной ошибке проверки формата ячейки.
- Исправил установку погрешности для условий (condition).

### [0.5.0] - 2020-09-11

- Вновь переработал инициализацию элементов из за проблем возникающих в редких случаях когда в формуле есть проверки между элементами, значения осей которых равно "*".
- Добавил в класс `Schema` параметр с размерностью (dimension), заполняющийся при парсинге схемы. Имеет формат {"1": ["1", "2"]}, где ключ - код секции, значение - список возможных кодов колонок.
- Добавил в класс `Schema` параметр с размерностью (dimension), заполняющийся при парсинге схемы. Имеет формат {"1": ["1", "2"]}, где ключ - код раздела, значение - список возможных кодов колонок.
- Параметр передаётся в `ControlChecker` и учитывается при проверке в случаях когда значение по оси колонок равно - "*".
- В классе `Section` методу items добавил параметр codes и specs, что позволило полностью убрать итерацию по списку кодов внутрь самого класса. В классе `Row` аналогичные изменения.
- Переработал проверку формул в `PeriodClause`. В некоторых схемамх есть формулы по формату отличающиеся от описанного в документации (прим. `(&NP=3 or &NP=6 or &NP=9)`). Теперь для всех формул кроме проверки на вхождение в список единый механизм. Нормализация строки, разбиение на отдельные части, сборка с подстановкой проверяемого значения и выполнение.
Expand Down Expand Up @@ -282,8 +289,8 @@
### 2020-08-20

- Переработал парсинг отчета с учетом мультилайнов.
- Реализовал вспомогательные дата-классы для секции и строки.
- Индексы секций/строк/графов теперь трансформируются сначала в число (если это возможно), затем обратно в строку. Так как есть ключи вида "01", однако в формуле "1".
- Реализовал вспомогательные дата-классы для раздела и строки.
- Индексы разделов/строк/графов теперь трансформируются сначала в число (если это возможно), затем обратно в строку. Так как есть ключи вида "01", однако в формуле "1".
- Изменил формат и тип сообщения о непройденном контроле.
- Доработал инициализацию элементов языка контролей.
- Координаты и специфики теперь в множестве типа set.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 Nikita Ryabinin
Copyright (c) 2020-2022 Nikita Ryabinin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,22 @@ from rosstat.flc import parse_schema, parse_report

# На вход передаются ElementTree, Element, bytes, file name/path, или file-like объекты

schema = parse_schema('xml_schema.xml')
report = parse_report('xml_report.xml', skip_warns=True)
schema = parse_schema('schema.xml')
report = parse_report('report.xml', skip_warns=True)

for result in schema.validate(report):
print(result)

# {'code': '4.30', 'name': 'Проверка контролей', 'description': 'XML Подраздел 2 стр. 201-202 гр.3 = "1" или "2", при хотя бы одной из стр. 105,106,108,109 гр.3 = 1; слева 1.0 <= справа 0.0 разница 1.0', 'level': 1}
# {'code': '4.60', 'name': 'Проверка контролей', 'description': 'XML Подраздел 2 стр. 203 гр. 3 = "1" или "2", или "3", или "4", или "5", или "6", при хотя бы одной из стр. 105,106,108,109 гр.3 = 1; слева 1.0 <= справа 0.0 разница 1.0', 'level': 1}
```
```

Результат проверки всегда список. При успешной проверке список будет пустой.

Если на одном из этапов проверки будут выявлены ошибки, проверка будет прервана и вернутся все ошибки обнаруженные на этом этапе.

С блоками проверок их порядком и описанием ошибок можно ознакомиться [здесь](docs/docs.md).

Флаг `skip_warns` определяет будут ли выводится предупреждения о пропуске контролей с проверками за прошлый период (эти проверки невозможно реализовать не имея доступа к ранее сформированному отчёту).

Поле `level` в результатах означает уровень проверки. 1 - ошибка, 0 - предупреждение.
2 changes: 1 addition & 1 deletion docs/docs.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Коды ошибок [WIP]
# Блоки проверок и описание ошибок.

## 0. Непредвиденная ошибка

Expand Down
11 changes: 10 additions & 1 deletion rosstat/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
Column = namedtuple('Column', ['code', 'value'])


class EmptyIter:

def iter(self, *args):
return []


EMPTY_ITER = EmptyIter()


def max_divider(num, terms):
'''НОД для списка чисел'''
for term_id in terms:
Expand Down Expand Up @@ -180,7 +189,7 @@ def _iter_codes(self, codes):

def get_section(self, code):
'''Возвращает раздел с указанным кодом'''
return self._data.get(code)
return self._data.get(code, EMPTY_ITER)

# ---

Expand Down
7 changes: 7 additions & 0 deletions rosstat/validators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
from .title import TitleValidator
from .format import FormatValidator
from .control import ControlValidator

__all__ = [
'AttrValidator',
'TitleValidator',
'FormatValidator',
'ControlValidator'
]
4 changes: 4 additions & 0 deletions rosstat/validators/control/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
from .control import ControlValidator

__all__ = [
'ControlValidator'
]
8 changes: 4 additions & 4 deletions rosstat/validators/control/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ class RuleExprError(ControlError):
'''Ошибка разбора правила контроля'''


class EvaluationError(ControlError):
'''Ошибка проверки контроля'''
class StopEvaluation(ControlError):
'''Прерывание проверки контроля'''


class NoElemToCompareError(EvaluationError):
class NoElemToCompareError(StopEvaluation):
'''Нет элемента для сравнения'''


class NoFormatForRowError(EvaluationError):
class NoFormatForRowError(StopEvaluation):
'''Нет формата для строки из формулы контроля'''


Expand Down
4 changes: 2 additions & 2 deletions rosstat/validators/control/inspectors/formula.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
ConditionExprError,
RuleExprError,
PrevPeriodNotImpl,
EvaluationError
StopEvaluation
)


Expand All @@ -21,7 +21,7 @@ def wrap_exc(f):
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except EvaluationError:
except StopEvaluation:
return []
return wrapper

Expand Down
45 changes: 17 additions & 28 deletions rosstat/validators/control/parser/lexer.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
from re import IGNORECASE, DOTALL
import ply.lex as lex
from ply.lex import TOKEN

tokens = ('COMMA', 'PLUS', 'MINUS', 'MULTIPLY', 'DIVIDE', 'LPAREN', 'RPAREN',
'ELEM_START', 'ELEM_END', 'CODE', 'SUM', 'ABS', 'FLOOR', 'ROUND',
'ISNULL', 'NULLIF', 'COALESCE', 'LOGIC', 'NUM', 'COMP')
reserved = ['SUM', 'ABS', 'FLOOR', 'ROUND', 'ISNULL', 'NULLIF', 'COALESCE']
literals = [',', '+', '-', '/', '*', '(', ')', '{', '}']
tokens = ['CODE', 'LOGIC', 'NUM', 'COMP'] + reserved

t_ignore = ' \r\t\f'
reserved_map = {r.lower(): r for r in reserved}

t_COMMA = r','
t_PLUS = r'\+'
t_MINUS = r'-'
t_DIVIDE = r'/'
t_MULTIPLY = r'\*'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_ELEM_START = r'{?{'
t_ELEM_END = r'}?}'
t_ignore = ' |\r\t\f'

t_LOGIC = r'and|or'
t_COMP = r'\| *(<|<=|=|>=|>|<>) *\|'

t_SUM = r'SUM'
t_ABS = r'abs'
t_FLOOR = r'floor'
t_ROUND = r'round'
t_ISNULL = r'isnull'
t_NULLIF = r'nullif'
t_COALESCE = r'coalesce'
t_COMP = r'[><=]{1,2}'


def _range(rng):
start, end = rng.split('-')
return (str(i) for i in range(int(start), int(end) + 1))


@TOKEN(r'\[.+?\]')
def t_CODE(t):
r'\[.+?\]'
code = []
for i in map(lambda i: i.strip(), t.value[1:-1].split(',')):
if ('-' in i) and ('.' not in i):
Expand All @@ -47,19 +29,26 @@ def t_CODE(t):
return t


@TOKEN(r'\d+(\.\d+)?')
def t_NUM(t):
r'\d+(\.\d+)?'
t.value = float(t.value)
return t


@TOKEN(r'\n+')
def t_WORD(t):
r'\w+'
t.value = t.value.lower()
t.type = reserved_map.get(t.value, 'LOGIC')
return t


def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)


def t_error(t):
print("Illegal character '%s'" % t.value[0])
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)


Expand Down

0 comments on commit 17087b7

Please sign in to comment.