Слияние с 685ec97 (третья ступень NER для юридических реквизитов),
код приведён к google-java-format, добавлены юнит-тесты
AdaptiveConcurrencyLimiter/SystemsConfig/PayloadCipher.
119 lines
6.2 KiB
Java
119 lines
6.2 KiB
Java
package ru.pdguard.detect;
|
|
|
|
import java.util.List;
|
|
import java.util.function.Predicate;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Одно правило детекции персональных данных.
|
|
*
|
|
* <p>Добавление нового типа ПД — это добавление одного {@code Rule} в {@link RuleRegistry}; менять
|
|
* остальной код не требуется.
|
|
*
|
|
* @param type тип ПД, который распознаёт правило
|
|
* @param pattern регулярное выражение
|
|
* @param priority приоритет при разрешении перекрытий
|
|
* @param groups номера групп, которые маскируются; {@code 0} — всё совпадение целиком. Несколько
|
|
* групп нужны, когда значение разорвано словами: «серия 4509 номер 123456»
|
|
* @param validator дополнительная проверка значения (контрольная сумма, диапазон дат); {@code null}
|
|
* — проверка не нужна
|
|
* @param veto шаблон окружения, при котором совпадение персональными данными не считается: адрес
|
|
* отделения банка не является ПД, хотя выглядит как адрес
|
|
* @param context шаблон окружения, который обязан присутствовать рядом. Нужен там, где форма
|
|
* совпадения сама по себе слишком общая: «Невский проспект» это адрес рядом с домом и индексом
|
|
* и просто топоним в рассказе о городе
|
|
* @param anchors строчные подстроки, одна из которых обязана встретиться в тексте. Проверка через
|
|
* {@code indexOf} на порядок дешевле запуска регулярного выражения и отсекает большинство
|
|
* правил на коротком запросе. Пустой список — правило запускается всегда
|
|
*/
|
|
public record Rule(
|
|
String type,
|
|
Pattern pattern,
|
|
int priority,
|
|
List<Integer> groups,
|
|
Predicate<String> validator,
|
|
Pattern veto,
|
|
Pattern context,
|
|
List<String> anchors) {
|
|
|
|
public Rule {
|
|
groups = List.copyOf(groups);
|
|
anchors = List.copyOf(anchors);
|
|
}
|
|
|
|
/**
|
|
* Флаги компиляции для всех правил.
|
|
*
|
|
* <p>{@code UNICODE_CHARACTER_CLASS} обязателен: без него {@code \w}, {@code \W} и {@code \b} в
|
|
* Java охватывают только латиницу, и якорные слова вроде «водительское удостоверение» не
|
|
* находятся. {@code UNICODE_CASE} делает {@code (?i)} корректным для кириллицы.
|
|
*/
|
|
private static final int FLAGS = Pattern.UNICODE_CHARACTER_CLASS | Pattern.UNICODE_CASE;
|
|
|
|
/**
|
|
* Сколько символов слева и справа от совпадения просматривает вето-шаблон.
|
|
*
|
|
* <p>150, не 80: на реальных адресах отделений из реестра ЦБ (регион, город, улица, дом — в одном
|
|
* предложении) расстояние от «отделение» до номера дома часто превышает 80 знаков за счёт
|
|
* длинного названия региона («Ханты-Мансийский автономный округ», «Кабардино-Балкарская
|
|
* Республика»). Найдено нагрузочным тестом на 60 реальных адресах из официального реестра — с
|
|
* окном в 80 знаков вето не срабатывало на части из них.
|
|
*/
|
|
public static final int VETO_LOOKBEHIND = 150;
|
|
|
|
public static final int VETO_LOOKAHEAD = 40;
|
|
|
|
/** Правило без проверок, маскируется всё совпадение. */
|
|
public static Rule of(String type, String regex, int priority) {
|
|
return new Rule(
|
|
type, Pattern.compile(regex, FLAGS), priority, List.of(0), null, null, null, List.of());
|
|
}
|
|
|
|
/** Маскировать только перечисленные группы, а не всё совпадение. */
|
|
public Rule groups(Integer... indexes) {
|
|
return new Rule(type, pattern, priority, List.of(indexes), validator, veto, context, anchors);
|
|
}
|
|
|
|
/** Принять совпадение, только если значение прошло проверку. */
|
|
public Rule validatedBy(Predicate<String> check) {
|
|
return new Rule(type, pattern, priority, groups, check, veto, context, anchors);
|
|
}
|
|
|
|
/** Запускать правило, только если в тексте есть одна из подстрок (в нижнем регистре). */
|
|
public Rule anchoredBy(String... required) {
|
|
return new Rule(type, pattern, priority, groups, validator, veto, context, List.of(required));
|
|
}
|
|
|
|
/** Есть ли в тексте хоть один из якорей правила. */
|
|
public boolean mayMatch(String lowercasedText) {
|
|
if (anchors.isEmpty()) {
|
|
return true;
|
|
}
|
|
for (String anchor : anchors) {
|
|
if (lowercasedText.contains(anchor)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Принять совпадение, только если рядом встретилось указанное слово. */
|
|
public Rule requiringNear(String regex) {
|
|
return new Rule(
|
|
type, pattern, priority, groups, validator, veto, Pattern.compile(regex, FLAGS), anchors);
|
|
}
|
|
|
|
/** Отбросить совпадение, если рядом встретилось указанное слово. */
|
|
public Rule vetoedBy(String regex) {
|
|
return new Rule(
|
|
type,
|
|
pattern,
|
|
priority,
|
|
groups,
|
|
validator,
|
|
Pattern.compile(regex, FLAGS),
|
|
context,
|
|
anchors);
|
|
}
|
|
}
|