text-to-map 1.1.15 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,5 +16,54 @@ Text To Map usiluje o lepší, strojově zpracovatelné využití částí vyhl
16
16
 
17
17
  Pro vyzkoušení je možné použít již vyčištěné vyhlášky ze složky *examples*.
18
18
 
19
+ ## Street Markdown
20
+ Street Markdown je formát, který po vzoru značkovacího jazyka [Markdown](https://en.wikipedia.org/wiki/Markdown) má za cíl usnadnit psaní strukturovaných dat bez nutnosti učit se složité formátovací tagy nebo používat speciální znaky. Vznikl na základě [pražské spádové vyhlášky z roku 2021](https://www.praha.eu/file/3251117/vyhlaska_c._4.pdf) a snaží se být co nejkompatibilnější s již používaným formátem.
21
+
22
+ Street Markdown je dále možný rozšiřovat pro další případy použití - například protialkoholová vyhláška (do standardu by byla třeba přidat definice parků), tržní vyhláška, vyhláška o buskingu a další.
23
+
24
+ ### Pravidla Street Markdownu
25
+
26
+ * **prázdné znaky a řádky**
27
+ * veškeré po sobě jdoucí mezery jsou ignorovány a počítají se jako jedna mezera
28
+ * prázdné řádky mají ve formátu speciální význam, několik prázdných řádků za sebou se ignoruje a počítá se jako jeden prázdný řádek
29
+ * **blok oblasti**
30
+ * v případě SMD pro více obcí či městských částí je uvozen blok oblasti pomocí _názvu oblasti_, následuje prázdný řádek a poté libovolný počet _bloků škol_
31
+ * _blok oblasti_ končí řádkem před _nadpisem oblasti_ následujícího _bloku obce_ nebo koncem souboru
32
+ * **název oblasti**
33
+ * začíná znakem "#" následovaný mezerou a textovým řetězcem, který obsahuje přesný název obce
34
+ * příklady:
35
+ * \# Praha 1
36
+ * \# Pelhřimov
37
+ * \# Poděbrady
38
+ * **blok školy**
39
+ * jde o souvislý blok bez vynechaných řádků
40
+ * bloky škol jsou od sebe odděleny jedním či více prázdnými řádky
41
+ * **název školy**
42
+ * první řádek bloku řádků je název školy
43
+ * libovolný neprázdný textový řetězec
44
+ * **definice ulice**
45
+ * definice ulice obsahuje _jméno ulice_ a volitelně _specifikaci orientačních čísel_
46
+ * pokud celá ulice patří do aktuálního bloku školy, pak se specifikace orientačních čísel neuvádí, uvede se pouze název ulice
47
+ * název ulice se uvádí v plně rozvinutém tvaru
48
+ * špatně: nám. Míru, Rašínovo nábř.
49
+ * správně: náměstí Míru, Rašínovo nábřeží
50
+ * jméno ulice a specifikace ulice je odděleno znaménkem minus (a volitelnými mezerami okolo pomlčky) - místo minus jsou přípustné i pomlčka a spojovník
51
+ * **specifikace orientačních čísel**
52
+ * za sebou jdoucí výčet _rozsahů čísel_
53
+ * _rozsahy čísel_ jsou odděleny čárkou a mezerou (", ") nebo spojkou " a "
54
+ * **rozsah čísel**
55
+ * jako rozsah čísel se považuje specifikace, zda jde o _typ čísel_ (všechna, sudá či lichá čísla) a _rozsah_ od-do, a to v následujícím formátu:
56
+ * **typ čísel**
57
+ * všechna = "č."
58
+ * lichá = "lichá č."
59
+ * sudá = "sudá č."
60
+ * čísla popisná = "č. p."
61
+ * **rozsah čísel**
62
+ * může jít o výčet (oddělený ", " nebo " a ") těchto možností:
63
+ * a) <číslo>, např. 7
64
+ * b) <číslo>-<číslo>, např. 2-66 (přípustné jsou pomlčka, spojovník, minus)
65
+ * c) (od) <číslo> (a) výše, např. od 20 výše, 14 a výše, od 7 a výše
66
+ * d) do (č.) <číslo>
67
+ * např. 1-9, 11, od 23 výše
19
68
 
20
69
  [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/E1E5JOMLT)
@@ -36,7 +36,6 @@ function main() {
36
36
  };
37
37
  console.time("downloadAndImportAllLatestAddressPoints");
38
38
  const { municipality } = yield getNewMunicipality("Česká Lípa");
39
- console.log(municipality.founder.schools[0].locations);
40
39
  const addressPoints = yield parseOrdinanceToAddressPoints(lines, {
41
40
  // currentMunicipality: municipality,
42
41
  }, reportErrors, reportWarnings);
@@ -33,27 +33,26 @@ export declare const checkStreetExists: (streetName: string, founder: Founder) =
33
33
  exists: boolean;
34
34
  errors: SmdError[];
35
35
  }>;
36
- export declare enum FindAddressPointsType {
37
- SmdLine = 0,
38
- MunicipalityPart = 1,
39
- WholeMunicipality = 2,
40
- WholeMunicipalityNoStreetName = 3
41
- }
42
36
  export type FindAddressPointsParams = {
43
- type: FindAddressPointsType.SmdLine;
37
+ type: "smdLine";
44
38
  smdLine: SmdLine;
45
39
  municipality: Municipality;
40
+ municipalityPartCode?: number;
46
41
  } | {
47
- type: FindAddressPointsType.MunicipalityPart;
42
+ type: "wholeMunicipalityPart";
48
43
  municipalityPartCode: number;
49
44
  } | {
50
- type: FindAddressPointsType.WholeMunicipality;
45
+ type: "wholeMunicipality";
51
46
  municipality: Municipality;
52
47
  } | {
53
- type: FindAddressPointsType.WholeMunicipalityNoStreetName;
48
+ type: "wholeMunicipalityNoStreetName";
54
49
  municipality: Municipality;
55
50
  };
56
51
  export declare const findAddressPoints: (params: FindAddressPointsParams) => Promise<AddressPoint[]>;
52
+ export declare const filterAddressPoints: (addressPoints: AddressPoint[], params: FindAddressPointsParams) => AddressPoint[];
53
+ export declare const getQueryParams: (params: FindAddressPointsParams) => (string | number)[];
54
+ export declare const getStreetJoinCondition: (params: FindAddressPointsParams) => string;
55
+ export declare const getWhereCondition: (params: FindAddressPointsParams) => string;
57
56
  export declare const isInRange: (number: number | null, letter: string | null, range: RangeSpec) => boolean;
58
57
  export declare const fitsType: (number: number | null, type: SeriesType) => boolean;
59
58
  export declare const equalsFullStreetNumber: (fullStreetNumber: FullStreetNumber, addressPoint: AddressPoint) => boolean;
@@ -11,7 +11,7 @@ import { AddressPointType, createSingleLineAddress } from "czech-address";
11
11
  import { SeriesType, isNegativeSeriesSpec, isRange, isSeriesSpecArray, } from "../street-markdown/types";
12
12
  import { findClosestString } from "../utils/helpers";
13
13
  import jtsk2wgs84 from "../utils/jtsk2wgs84";
14
- import { extractKeyValuesPairs, generate2DPlaceholders, getKnexDb, insertMultipleRows, isMysql, isSqlite, nonEmptyOrNull, } from "./db";
14
+ import { extractKeyValuesPairs, generate2DPlaceholders, getKnexDb, insertMultipleRows, isMysql, isSqlite, nonEmptyOrNull, rawQuery, } from "./db";
15
15
  import { getFounderCityCode } from "./founders";
16
16
  import { MunicipalityType } from "./types";
17
17
  const DescriptiveType = "č.p.";
@@ -80,7 +80,7 @@ export const commitAddressPoints = (buffer) => __awaiter(void 0, void 0, void 0,
80
80
  });
81
81
  const placeHolders = generate2DPlaceholders(columnNames.length, buffer.length);
82
82
  const knex = getKnexDb();
83
- yield knex.raw(`INSERT ${isMysql(knex) ? "IGNORE" : ""} INTO address_point (${columnNames.join(",")}) VALUES ${placeHolders} ${!isMysql(knex) ? "ON CONFLICT (id) DO NOTHING" : ""}`, params);
83
+ yield rawQuery(`INSERT ${isMysql(knex) ? "IGNORE" : ""} INTO address_point (${columnNames.join(",")}) VALUES ${placeHolders} ${!isMysql(knex) ? "ON CONFLICT (id) DO NOTHING" : ""}`, params);
84
84
  return buffer.length;
85
85
  });
86
86
  export const insertCities = (buffer) => __awaiter(void 0, void 0, void 0, function* () {
@@ -129,8 +129,7 @@ const addressPointSelect = `
129
129
  LEFT JOIN prague_district p ON a.prague_district_code = p.code
130
130
  LEFT JOIN municipality_part m ON a.municipality_part_code = m.code`;
131
131
  export const getAddressPointById = (addressPointId) => __awaiter(void 0, void 0, void 0, function* () {
132
- const knex = getKnexDb();
133
- const row = yield knex.raw(`${addressPointSelect}
132
+ const row = yield rawQuery(`${addressPointSelect}
134
133
  WHERE a.id = ?`, [addressPointId]);
135
134
  if (row.length === 0) {
136
135
  return null;
@@ -144,7 +143,7 @@ export const checkStreetExists = (streetName, founder) => __awaiter(void 0, void
144
143
  const errors = [];
145
144
  // we check the whole city
146
145
  const cityCode = yield getFounderCityCode(founder);
147
- const rowList = yield knex.raw(`SELECT name AS street_name
146
+ const rowList = yield rawQuery(`SELECT name AS street_name
148
147
  FROM street
149
148
  WHERE city_code = ? AND name = ? ${isSqlite(knex) ? "COLLATE NOCASE" : ""}`, [cityCode, streetName]);
150
149
  if (rowList.length > 0) {
@@ -184,30 +183,12 @@ const getAllStreets = (cityCode) => __awaiter(void 0, void 0, void 0, function*
184
183
  const knex = getKnexDb();
185
184
  return yield knex.pluck("name").from("street").where("city_code", cityCode);
186
185
  });
187
- export var FindAddressPointsType;
188
- (function (FindAddressPointsType) {
189
- FindAddressPointsType[FindAddressPointsType["SmdLine"] = 0] = "SmdLine";
190
- FindAddressPointsType[FindAddressPointsType["MunicipalityPart"] = 1] = "MunicipalityPart";
191
- FindAddressPointsType[FindAddressPointsType["WholeMunicipality"] = 2] = "WholeMunicipality";
192
- FindAddressPointsType[FindAddressPointsType["WholeMunicipalityNoStreetName"] = 3] = "WholeMunicipalityNoStreetName";
193
- })(FindAddressPointsType || (FindAddressPointsType = {}));
194
186
  export const findAddressPoints = (params) => __awaiter(void 0, void 0, void 0, function* () {
195
187
  const knex = getKnexDb();
196
- const queryParams = params.type === FindAddressPointsType.SmdLine
197
- ? [params.smdLine.street, params.municipality.code]
198
- : params.type === FindAddressPointsType.MunicipalityPart
199
- ? [params.municipalityPartCode]
200
- : [params.municipality.code];
201
- const streetJoinCondition = params.type === FindAddressPointsType.SmdLine
202
- ? `JOIN street s ON a.street_code = s.code AND s.name = ? ${isSqlite(knex) ? "COLLATE NOCASE" : ""}`
203
- : "LEFT JOIN street s ON a.street_code = s.code";
204
- const whereCondition = params.type === FindAddressPointsType.MunicipalityPart
205
- ? "a.municipality_part_code = ?"
206
- : getMunicipalityWhere("a", params.municipality) +
207
- (params.type === FindAddressPointsType.WholeMunicipalityNoStreetName
208
- ? " AND a.street_code IS NULL"
209
- : "");
210
- const queryResult = yield knex.raw(`SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
188
+ const queryParams = getQueryParams(params);
189
+ const streetJoinCondition = getStreetJoinCondition(params);
190
+ const whereCondition = getWhereCondition(params);
191
+ const queryResult = yield rawQuery(`SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
211
192
  a.orientational_number, a.orientational_number_letter, c.name AS city_name,
212
193
  d.name AS district_name, m.name AS municipality_part_name, p.name AS prague_district_name,
213
194
  a.postal_code, a.wgs84_latitude, a.wgs84_longitude
@@ -219,28 +200,32 @@ export const findAddressPoints = (params) => __awaiter(void 0, void 0, void 0, f
219
200
  LEFT JOIN municipality_part m ON a.municipality_part_code = m.code
220
201
  LEFT JOIN prague_district p ON a.prague_district_code = p.code
221
202
  WHERE ${whereCondition}`, queryParams);
222
- const filteredAddressPoints = queryResult.map(rowToAddressPoint);
223
- if (params.type !== FindAddressPointsType.SmdLine) {
224
- return filteredAddressPoints;
203
+ return filterAddressPoints(queryResult.map(rowToAddressPoint), params);
204
+ });
205
+ export const filterAddressPoints = (addressPoints, params) => {
206
+ if (params.type === "wholeMunicipality" ||
207
+ params.type === "wholeMunicipalityNoStreetName" ||
208
+ params.type === "wholeMunicipalityPart") {
209
+ return addressPoints;
225
210
  }
226
211
  const result = [];
227
212
  const numberSpec = params.smdLine.numberSpec;
228
213
  if (isSeriesSpecArray(numberSpec)) {
229
214
  numberSpec.forEach((seriesSpec) => {
230
- result.push(...filterAddressPointsByRanges(filteredAddressPoints, seriesSpec));
215
+ result.push(...filterAddressPointsByRanges(addressPoints, seriesSpec));
231
216
  });
232
217
  // when no number specs are present, we take everything
233
218
  if (numberSpec.length === 0) {
234
- result.push(...filteredAddressPoints);
219
+ result.push(...addressPoints);
235
220
  }
236
221
  }
237
222
  else if (isNegativeSeriesSpec(numberSpec)) {
238
223
  // negative number spec (e.g. odd numbers except 13-17)
239
- const toExclude = filterAddressPointsByRanges(filteredAddressPoints, numberSpec);
240
- result.push(...filteredAddressPoints.filter((addressPoint) => !toExclude.includes(addressPoint)));
224
+ const toExclude = filterAddressPointsByRanges(addressPoints, numberSpec);
225
+ result.push(...addressPoints.filter((addressPoint) => !toExclude.includes(addressPoint)));
241
226
  }
242
227
  return result;
243
- });
228
+ };
244
229
  const filterAddressPointsByRanges = (addressPoints, seriesSpec) => {
245
230
  const result = [];
246
231
  seriesSpec.ranges.forEach((range) => {
@@ -264,6 +249,32 @@ const filterAddressPointsByRanges = (addressPoints, seriesSpec) => {
264
249
  }
265
250
  return result;
266
251
  };
252
+ export const getQueryParams = (params) => {
253
+ if (params.type === "smdLine" && params.smdLine.type === "street") {
254
+ return [params.smdLine.street, params.municipality.code];
255
+ }
256
+ if ((params.type === "smdLine" && params.smdLine.type === "municipalityPart") ||
257
+ params.type === "wholeMunicipalityPart") {
258
+ return [params.municipalityPartCode];
259
+ }
260
+ return [params.municipality.code];
261
+ };
262
+ export const getStreetJoinCondition = (params) => {
263
+ if (params.type === "smdLine" && params.smdLine.type === "street") {
264
+ return `JOIN street s ON a.street_code = s.code AND s.name = ? ${isSqlite(getKnexDb()) ? "COLLATE NOCASE" : ""}`;
265
+ }
266
+ return "LEFT JOIN street s ON a.street_code = s.code";
267
+ };
268
+ export const getWhereCondition = (params) => {
269
+ if ((params.type === "smdLine" && params.smdLine.type === "municipalityPart") ||
270
+ params.type === "wholeMunicipalityPart") {
271
+ return "a.municipality_part_code = ?";
272
+ }
273
+ if (params.type === "wholeMunicipalityNoStreetName") {
274
+ return `${getMunicipalityWhere("a", params.municipality)} AND a.street_code IS NULL`;
275
+ }
276
+ return getMunicipalityWhere("a", params.municipality);
277
+ };
267
278
  export const isInRange = (number, letter, range) => {
268
279
  if (number === null) {
269
280
  return !range.from && !range.to;
package/dist/db/cities.js CHANGED
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import chunk from "lodash/chunk";
11
- import { extractKeyValuesPairs, getKnexDb, insertMultipleRows } from "./db";
11
+ import { extractKeyValuesPairs, insertMultipleRows, rawQuery } from "./db";
12
12
  const citiesColumn = {
13
13
  cityName: 0,
14
14
  cityCode: 1,
@@ -26,11 +26,10 @@ export const insertCityPositions = (data) => __awaiter(void 0, void 0, void 0, f
26
26
  // we need to insert them before updating them with region data
27
27
  changes += yield insertCities(data);
28
28
  const prev = changes;
29
- const knex = getKnexDb();
30
29
  for (const arrayChunk of chunk(data, 1000)) {
31
30
  const queries = [];
32
31
  for (const row of arrayChunk) {
33
- queries.push(knex.raw(`UPDATE city SET wgs84_latitude = ?, wgs84_longitude = ? WHERE code = ?`, [
32
+ queries.push(rawQuery(`UPDATE city SET wgs84_latitude = ?, wgs84_longitude = ? WHERE code = ?`, [
34
33
  row[citiesColumn.latitude],
35
34
  row[citiesColumn.longitude],
36
35
  row[citiesColumn.cityCode],
package/dist/db/db.d.ts CHANGED
@@ -20,6 +20,7 @@ export declare const initDb: (config?: Partial<DbConfig>) => Promise<Knex>;
20
20
  export declare const clearDb: (config?: Partial<DbConfig>) => Promise<Knex>;
21
21
  export declare const disconnectKnex: () => Promise<void>;
22
22
  export declare const nonEmptyOrNull: (value: string) => string | null;
23
+ export declare const rawQuery: (query: string, ...bindings: readonly Knex.RawBinding[]) => Promise<any[]>;
23
24
  /**
24
25
  * Efficiently insert multiple rows. If preventDuplicatesByFirstColumn is true, the first
25
26
  * column should be unique (PK or UNIQUE).
package/dist/db/db.js CHANGED
@@ -57,6 +57,7 @@ export const getKnexDb = (config = {}) => {
57
57
  client: DbClient[SupportedDbType.postgres],
58
58
  connection: _knexDbConfig.pgConnectionString,
59
59
  useNullAsDefault: true,
60
+ debug: _knexDbConfig.verbose,
60
61
  });
61
62
  }
62
63
  else if (_knexDbConfig.dbType === SupportedDbType.mysql) {
@@ -71,6 +72,7 @@ export const getKnexDb = (config = {}) => {
71
72
  database,
72
73
  },
73
74
  useNullAsDefault: true,
75
+ debug: _knexDbConfig.verbose,
74
76
  });
75
77
  }
76
78
  else {
@@ -141,6 +143,11 @@ const getEnvConfig = () => {
141
143
  export const nonEmptyOrNull = (value) => {
142
144
  return value ? value : null;
143
145
  };
146
+ export const rawQuery = (query, ...bindings) => __awaiter(void 0, void 0, void 0, function* () {
147
+ const knex = getKnexDb();
148
+ const result = yield knex.raw(query, ...bindings);
149
+ return isMysql(knex) ? result[0] : result;
150
+ });
144
151
  /**
145
152
  * Efficiently insert multiple rows. If preventDuplicatesByFirstColumn is true, the first
146
153
  * column should be unique (PK or UNIQUE).
@@ -152,7 +159,7 @@ export const insertMultipleRows = (rows, table, columnNames, preventDuplicates =
152
159
  const insertPlaceholders = generate2DPlaceholders(columnNames.length, rows.length);
153
160
  const knex = getKnexDb();
154
161
  const onConfict = preventDuplicates && !isMysql(knex) ? `ON CONFLICT DO NOTHING` : "";
155
- yield knex.raw(`INSERT ${isMysql(knex) ? "IGNORE" : ""} INTO ${table} (${columnNames.join(",")}) VALUES ${insertPlaceholders} ${onConfict}`, rows.flat());
162
+ yield rawQuery(`INSERT ${isMysql(knex) ? "IGNORE" : ""} INTO ${table} (${columnNames.join(",")}) VALUES ${insertPlaceholders} ${onConfict}`, rows.flat());
156
163
  return rows.length;
157
164
  });
158
165
  /**
@@ -1,9 +1,11 @@
1
1
  import { Founder } from "../db/types";
2
2
  import { DbMunicipalityResult, MunicipalityPartResult } from "./types";
3
- export declare const isMunicipalityPart: (line: string) => boolean;
4
- export declare const getMunicipalityPart: (line: string, founder: Founder) => Promise<MunicipalityPartResult>;
5
3
  export declare const isMunicipalitySwitch: (line: string) => boolean;
6
4
  export declare const getSwitchMunicipality: (line: string, founder: Founder) => Promise<DbMunicipalityResult>;
7
5
  export declare const isWholeMunicipality: (line: string) => boolean;
8
- export declare const isNoStreetNameLine: (line: string) => boolean;
6
+ export declare const isRestWithNoStreetNameLine: (line: string) => boolean;
7
+ export declare const isRestOfMunicipalityLine: (line: string) => boolean;
8
+ export declare const isRestOfMunicipalityPartLine: (line: string) => boolean;
9
+ export declare const getRestOfMunicipalityPart: (line: string, founder: Founder) => Promise<MunicipalityPartResult>;
9
10
  export declare const getWholeMunicipality: (line: string, founder: Founder) => Promise<DbMunicipalityResult>;
11
+ export declare const getMunicipalityPartResult: (name: string, line: string, founder: Founder) => Promise<MunicipalityPartResult>;
@@ -9,32 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { findMunicipalityByNameAndType, findMunicipalityPartByName, } from "../db/founders";
11
11
  import { MunicipalityType } from "../db/types";
12
- const municipalityPartPattern = /^část (?<type>obce|města) (?<name>.+)$/;
13
12
  const municipalitySwitchStartPattern = /^navíc ulice /;
14
13
  const municipalitySwitchPattern = /^navíc ulice (z )?(?<type>městské části|městského obvodu|obce|města) (?<name>.+):$/;
15
14
  const wholeMunicipalityStartPattern = /^území /;
16
15
  const wholeMunicipalityPattern = /^území (?<type>městské části|městského obvodu|obce|města) (?<name>.+)$/;
17
- const noStreetNamePattern = /^všechny ostatní budovy bez názvu ulice\s*$/;
18
- export const isMunicipalityPart = (line) => {
19
- return municipalityPartPattern.test(line);
20
- };
21
- export const getMunicipalityPart = (line, founder) => __awaiter(void 0, void 0, void 0, function* () {
22
- const match = municipalityPartPattern.exec(line);
23
- if (match === null) {
24
- return {
25
- errors: [
26
- {
27
- message: "Neplatný zápis pro přidání části obce. Správný zápis je: části <obce/města> <název části obce>. Např.: 'části obce Kladno' (název části obce musí být v 1. pádě).",
28
- startOffset: 0,
29
- endOffset: line.length + 1,
30
- },
31
- ],
32
- municipalityPartCode: null,
33
- };
34
- }
35
- const { name } = match.groups;
36
- return yield getMunicipalityPartResult(cleanName(name), line, founder);
37
- });
16
+ const restWithNoStreetNamePattern = /^všechny ostatní budovy bez názvu ulice\s*$/;
17
+ const restOfMunicipalityPattern = /^zbytek (?<type>městské části|městského obvodu|obce|města)\s*$/;
18
+ const restOfMunicipalityPartPattern = /^zbytek části (obce|města) (?<name>.+)\s*$/;
38
19
  export const isMunicipalitySwitch = (line) => {
39
20
  return municipalitySwitchStartPattern.test(line);
40
21
  };
@@ -58,9 +39,32 @@ export const getSwitchMunicipality = (line, founder) => __awaiter(void 0, void 0
58
39
  export const isWholeMunicipality = (line) => {
59
40
  return wholeMunicipalityStartPattern.test(line);
60
41
  };
61
- export const isNoStreetNameLine = (line) => {
62
- return noStreetNamePattern.test(line);
42
+ export const isRestWithNoStreetNameLine = (line) => {
43
+ return restWithNoStreetNamePattern.test(line);
44
+ };
45
+ export const isRestOfMunicipalityLine = (line) => {
46
+ return restOfMunicipalityPattern.test(line);
63
47
  };
48
+ export const isRestOfMunicipalityPartLine = (line) => {
49
+ return restOfMunicipalityPartPattern.test(line);
50
+ };
51
+ export const getRestOfMunicipalityPart = (line, founder) => __awaiter(void 0, void 0, void 0, function* () {
52
+ const match = restOfMunicipalityPartPattern.exec(line);
53
+ if (match === null) {
54
+ return {
55
+ errors: [
56
+ {
57
+ message: "Neplatný zápis pro přidání zbytku části obce. Správný zápis je: zbytek části <typ> <název obce>. Např.: 'zbytek části obce Malšovice' (název části obce musí být v 1. pádě).",
58
+ startOffset: 0,
59
+ endOffset: line.length + 1,
60
+ },
61
+ ],
62
+ municipalityPartCode: null,
63
+ };
64
+ }
65
+ const { name } = match.groups;
66
+ return yield getMunicipalityPartResult(name, line, founder);
67
+ });
64
68
  export const getWholeMunicipality = (line, founder) => __awaiter(void 0, void 0, void 0, function* () {
65
69
  const match = wholeMunicipalityPattern.exec(line);
66
70
  if (match === null) {
@@ -89,7 +93,7 @@ const getMunicipalityResult = (type, name, line, founder) => __awaiter(void 0, v
89
93
  endOffset }))),
90
94
  };
91
95
  });
92
- const getMunicipalityPartResult = (name, line, founder) => __awaiter(void 0, void 0, void 0, function* () {
96
+ export const getMunicipalityPartResult = (name, line, founder) => __awaiter(void 0, void 0, void 0, function* () {
93
97
  const { municipalityPartCode, errors } = yield findMunicipalityPartByName(name, founder);
94
98
  const startOffset = line.indexOf(name);
95
99
  const endOffset = startOffset + name.length + 1;
@@ -4,6 +4,7 @@ export declare class SmdParser extends EmbeddedActionsParser {
4
4
  constructor(tokenVocabulary: TokenVocabulary);
5
5
  street: import("chevrotain").ParserMethod<[], SmdLine[]>;
6
6
  private streetNameAndNumbersSpecs;
7
+ private municipalityPartNameAndNumbersSpecs;
7
8
  private numberSpecs;
8
9
  private seriesSpecs;
9
10
  private _gatePostfixTypeSeriesSpec;
@@ -19,3 +20,4 @@ export declare class SmdParser extends EmbeddedActionsParser {
19
20
  private fullStreetNumber;
20
21
  }
21
22
  export declare const parseRichNumber: (number: string) => RichNumber;
23
+ export declare const parseMunicipalityPartName: (name: string) => string;
@@ -1,6 +1,6 @@
1
1
  import { EmbeddedActionsParser, EOF } from "chevrotain";
2
2
  import { splitStreetViaRomanNumerals } from "./roman-numerals";
3
- import { MainSeparator, Separator, OddType, EvenType, DescriptiveType, AllType, Number, Hyphen, StreetName, From, To, AndAbove, AndBelow, Slash, Without, } from "./token-definition";
3
+ import { AllType, AndAbove, AndBelow, DescriptiveType, EvenType, From, Hyphen, MainSeparator, MunicipalityPartName, Number, OddType, Separator, Slash, StreetName, To, Without, } from "./token-definition";
4
4
  import { SeriesType } from "./types";
5
5
  export class SmdParser extends EmbeddedActionsParser {
6
6
  constructor(tokenVocabulary) {
@@ -15,14 +15,39 @@ export class SmdParser extends EmbeddedActionsParser {
15
15
  },
16
16
  {
17
17
  ALT: () => {
18
- result = { street: this.CONSUME(StreetName).image, numberSpec: [] };
18
+ result = {
19
+ type: "street",
20
+ street: this.CONSUME(StreetName).image,
21
+ numberSpec: [],
22
+ };
23
+ },
24
+ },
25
+ {
26
+ ALT: () => {
27
+ result = this.SUBRULE(this.municipalityPartNameAndNumbersSpecs);
28
+ },
29
+ },
30
+ {
31
+ ALT: () => {
32
+ const municipalityPart = parseMunicipalityPartName(this.CONSUME(MunicipalityPartName).image);
33
+ result = {
34
+ type: "municipalityPart",
35
+ municipalityPart,
36
+ numberSpec: [],
37
+ };
19
38
  },
20
39
  },
21
40
  ]);
22
- return splitStreetViaRomanNumerals(result.street).map((street) => ({
23
- street,
24
- numberSpec: result.numberSpec,
25
- }));
41
+ if (result.type === "street") {
42
+ return splitStreetViaRomanNumerals(result.street).map((street) => ({
43
+ type: "street",
44
+ street,
45
+ numberSpec: result.numberSpec,
46
+ }));
47
+ }
48
+ else {
49
+ return [result];
50
+ }
26
51
  });
27
52
  this.streetNameAndNumbersSpecs = this.RULE("streetNameAndNumbersSpecs", () => {
28
53
  const street = this.CONSUME(StreetName).image;
@@ -30,10 +55,23 @@ export class SmdParser extends EmbeddedActionsParser {
30
55
  this.CONSUME(MainSeparator);
31
56
  });
32
57
  const numberSpec = this.SUBRULE(this.numberSpecs);
33
- return { street, numberSpec };
58
+ return { type: "street", street, numberSpec };
59
+ });
60
+ this.municipalityPartNameAndNumbersSpecs = this.RULE("municipalityPartNameAndNumbersSpecs", () => {
61
+ const municipalityPart = parseMunicipalityPartName(this.CONSUME(MunicipalityPartName).image);
62
+ this.OPTION(() => {
63
+ this.CONSUME(MainSeparator);
64
+ });
65
+ const numberSpec = this.SUBRULE(this.numberSpecs);
66
+ return {
67
+ type: "municipalityPart",
68
+ municipalityPart,
69
+ numberSpec,
70
+ };
34
71
  });
35
72
  this.numberSpecs = this.RULE("numberSpecs", () => {
36
- const result = [];
73
+ const positiveResult = [];
74
+ let negativeResult;
37
75
  this.OR([
38
76
  {
39
77
  GATE: () => this.LA(2).tokenType === Without,
@@ -55,14 +93,14 @@ export class SmdParser extends EmbeddedActionsParser {
55
93
  GATE: this._gatePostfixTypeSeriesSpec,
56
94
  ALT: () => {
57
95
  const { type, ranges } = this.SUBRULE(this.postfixTypeSeriesSpec);
58
- return { negative: true, type, ranges };
96
+ negativeResult = { negative: true, type, ranges };
59
97
  },
60
98
  },
61
99
  {
62
100
  ALT: () => {
63
101
  const type = this.SUBRULE2(this.seriesType);
64
102
  const ranges = this.SUBRULE2(this.rangeList);
65
- return { negative: true, type, ranges };
103
+ negativeResult = { negative: true, type, ranges };
66
104
  },
67
105
  },
68
106
  ]);
@@ -73,13 +111,16 @@ export class SmdParser extends EmbeddedActionsParser {
73
111
  this.AT_LEAST_ONE_SEP({
74
112
  SEP: Separator,
75
113
  DEF: () => {
76
- result.push(this.SUBRULE(this.seriesSpecs));
114
+ positiveResult.push(this.SUBRULE(this.seriesSpecs));
77
115
  },
78
116
  });
79
117
  },
80
118
  },
81
119
  ]);
82
- return result;
120
+ if (negativeResult) {
121
+ return negativeResult;
122
+ }
123
+ return positiveResult;
83
124
  });
84
125
  this.seriesSpecs = this.RULE("seriesSpecs", () => {
85
126
  let ranges = [];
@@ -321,7 +362,10 @@ export class SmdParser extends EmbeddedActionsParser {
321
362
  const descriptiveNumber = parseRichNumber(this.CONSUME(Number).image);
322
363
  this.CONSUME(Slash);
323
364
  const orientationNumber = parseRichNumber(this.CONSUME2(Number).image);
324
- return { descriptionNumber: descriptiveNumber, orientationalNumber: orientationNumber };
365
+ return {
366
+ descriptionNumber: descriptiveNumber,
367
+ orientationalNumber: orientationNumber,
368
+ };
325
369
  });
326
370
  this.performSelfAnalysis();
327
371
  }
@@ -341,3 +385,9 @@ export const parseRichNumber = (number) => {
341
385
  };
342
386
  }
343
387
  };
388
+ const municipalityPartPattern = /^část (?<type>obce|města) (?<name>.+)$/;
389
+ export const parseMunicipalityPartName = (name) => {
390
+ var _a, _b;
391
+ const match = municipalityPartPattern.exec(name);
392
+ return (_b = (_a = match === null || match === void 0 ? void 0 : match.groups.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
393
+ };
@@ -7,17 +7,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { FindAddressPointsType, checkStreetExists, findAddressPoints, getAddressPointById, } from "../db/address-points";
10
+ import { checkStreetExists, findAddressPoints, getAddressPointById, } from "../db/address-points";
11
11
  import { disconnectKnex } from "../db/db";
12
12
  import { findFounder } from "../db/founders";
13
13
  import { findSchool } from "../db/schools";
14
14
  import { founderToMunicipality } from "../db/types";
15
- import { getMunicipalityPart, getSwitchMunicipality, getWholeMunicipality, isMunicipalityPart, isMunicipalitySwitch, isNoStreetNameLine, isWholeMunicipality, } from "./municipality";
15
+ import { getMunicipalityPartResult, getRestOfMunicipalityPart, getSwitchMunicipality, getWholeMunicipality, isMunicipalitySwitch, isRestOfMunicipalityLine, isRestOfMunicipalityPartLine, isRestWithNoStreetNameLine, isWholeMunicipality, } from "./municipality";
16
16
  import { parseLine } from "./smd-line-parser";
17
17
  import { isAddressPoint, } from "./types";
18
18
  export const parseOrdinanceToAddressPoints = (lines, initialState = {}, onError = () => { }, onWarning = () => { }, onProcessedLine = () => { }) => __awaiter(void 0, void 0, void 0, function* () {
19
19
  try {
20
- const state = Object.assign({ currentMunicipality: null, currentFilterMunicipality: null, currentSchool: null, noStreetNameSchoolIzo: null, municipalities: [] }, initialState);
20
+ const state = Object.assign({ currentMunicipality: null, currentFilterMunicipality: null, currentSchool: null, rests: {
21
+ noStreetNameSchoolIzo: null,
22
+ municipalityParts: [],
23
+ wholeMunicipalitySchoolIzo: null,
24
+ }, municipalities: [] }, initialState);
21
25
  let lineNumber = 1;
22
26
  for (const rawLine of lines) {
23
27
  const line = cleanLine(rawLine);
@@ -37,12 +41,7 @@ export const parseOrdinanceToAddressPoints = (lines, initialState = {}, onError
37
41
  }
38
42
  state.currentMunicipality.schools.push(mapSchoolForExport(state.currentSchool));
39
43
  }
40
- if (state.currentMunicipality != null) {
41
- if (state.noStreetNameSchoolIzo) {
42
- yield addNoStreetNameToSchool(state);
43
- }
44
- state.municipalities.push(convertMunicipality(state.currentMunicipality));
45
- }
44
+ yield completeCurrentMunicipality(state);
46
45
  return state.municipalities;
47
46
  }
48
47
  catch (error) {
@@ -82,17 +81,29 @@ const processOneLine = (params) => __awaiter(void 0, void 0, void 0, function* (
82
81
  yield processSchoolLine(params);
83
82
  return;
84
83
  }
85
- if (isMunicipalityPart(line)) {
86
- yield processMunicipalityPartLine(params);
87
- }
88
- else if (isMunicipalitySwitch(line)) {
84
+ if (isMunicipalitySwitch(line)) {
89
85
  processMunicipalitySwitchLine(params);
90
86
  }
91
87
  else if (isWholeMunicipality(line)) {
92
88
  yield processWholeMunicipalityLine(params);
93
89
  }
94
- else if (isNoStreetNameLine(line)) {
95
- state.noStreetNameSchoolIzo = state.currentSchool.izo;
90
+ else if (isRestWithNoStreetNameLine(line)) {
91
+ state.rests.noStreetNameSchoolIzo = state.currentSchool.izo;
92
+ }
93
+ else if (isRestOfMunicipalityLine(line)) {
94
+ state.rests.wholeMunicipalitySchoolIzo = state.currentSchool.izo;
95
+ }
96
+ else if (isRestOfMunicipalityPartLine(line)) {
97
+ const { municipalityPartCode, errors } = yield getRestOfMunicipalityPart(line, state.currentMunicipality.founder);
98
+ if (errors.length > 0) {
99
+ params.onError({ lineNumber: params.lineNumber, line, errors });
100
+ }
101
+ else {
102
+ state.rests.municipalityParts.push({
103
+ municipalityPartCode,
104
+ schoolIzo: state.currentSchool.izo,
105
+ });
106
+ }
96
107
  }
97
108
  else {
98
109
  yield processAddressPointLine(params);
@@ -103,12 +114,7 @@ const processMunicipalityLine = ({ line, lineNumber, state, onError, }) => __awa
103
114
  state.currentMunicipality.schools.push(state.currentSchool);
104
115
  state.currentSchool = null;
105
116
  }
106
- if (state.currentMunicipality !== null) {
107
- if (state.noStreetNameSchoolIzo) {
108
- yield addNoStreetNameToSchool(state);
109
- }
110
- state.municipalities.push(convertMunicipality(state.currentMunicipality));
111
- }
117
+ yield completeCurrentMunicipality(state);
112
118
  const { municipality, errors } = yield getNewMunicipality(line);
113
119
  if (errors.length > 0) {
114
120
  onError({ lineNumber, line, errors });
@@ -117,6 +123,12 @@ const processMunicipalityLine = ({ line, lineNumber, state, onError, }) => __awa
117
123
  state.currentFilterMunicipality = founderToMunicipality(state.currentMunicipality.founder);
118
124
  state.currentSchool = null;
119
125
  });
126
+ const completeCurrentMunicipality = (state) => __awaiter(void 0, void 0, void 0, function* () {
127
+ if (state.currentMunicipality !== null) {
128
+ yield addRests(state);
129
+ state.municipalities.push(convertMunicipality(state.currentMunicipality));
130
+ }
131
+ });
120
132
  const processEmptyLine = ({ state }) => {
121
133
  if (state.currentSchool !== null) {
122
134
  state.currentMunicipality.schools.push(state.currentSchool);
@@ -137,19 +149,6 @@ const processSchoolLine = ({ line, lineNumber, state, onError, }) => __awaiter(v
137
149
  state.currentSchool = yield getNewSchool(line, state.currentMunicipality.founder, lineNumber, onError);
138
150
  state.currentFilterMunicipality = founderToMunicipality(state.currentMunicipality.founder);
139
151
  });
140
- const processMunicipalityPartLine = ({ line, state, lineNumber, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
141
- const { municipalityPartCode, errors } = yield getMunicipalityPart(line, state.currentMunicipality.founder);
142
- if (errors.length > 0) {
143
- onError({ lineNumber, line, errors });
144
- }
145
- else {
146
- const addressPoints = yield findAddressPoints({
147
- type: FindAddressPointsType.MunicipalityPart,
148
- municipalityPartCode,
149
- });
150
- state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
151
- }
152
- });
153
152
  const processMunicipalitySwitchLine = ({ line, state, lineNumber, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
154
153
  const { municipality, errors } = yield getSwitchMunicipality(line, state.currentMunicipality.founder);
155
154
  if (errors.length > 0) {
@@ -166,7 +165,7 @@ const processWholeMunicipalityLine = ({ line, state, lineNumber, onError, }) =>
166
165
  }
167
166
  else {
168
167
  const addressPoints = yield findAddressPoints({
169
- type: FindAddressPointsType.WholeMunicipality,
168
+ type: "wholeMunicipality",
170
169
  municipality,
171
170
  });
172
171
  state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
@@ -179,37 +178,89 @@ const processAddressPointLine = ({ line, state, lineNumber, onError, onWarning,
179
178
  }
180
179
  else {
181
180
  for (const smdLine of smdLines) {
182
- const { exists, errors } = yield checkStreetExists(smdLine.street, state.currentMunicipality.founder);
183
- if (errors.length > 0) {
184
- onWarning({ lineNumber, line, errors });
181
+ if (smdLine.type === "street") {
182
+ const { exists, errors } = yield checkStreetExists(smdLine.street, state.currentMunicipality.founder);
183
+ if (errors.length > 0) {
184
+ onWarning({ lineNumber, line, errors });
185
+ }
186
+ if (exists) {
187
+ const addressPoints = yield findAddressPoints({
188
+ type: "smdLine",
189
+ smdLine,
190
+ municipality: state.currentFilterMunicipality,
191
+ });
192
+ state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
193
+ }
185
194
  }
186
- if (exists) {
187
- let addressPoints = yield findAddressPoints({
188
- type: FindAddressPointsType.SmdLine,
189
- smdLine,
190
- municipality: state.currentFilterMunicipality,
191
- });
192
- state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
195
+ else if (smdLine.type === "municipalityPart") {
196
+ const { municipalityPartCode, errors } = yield getMunicipalityPartResult(smdLine.municipalityPart, line, state.currentMunicipality.founder);
197
+ if (errors.length > 0) {
198
+ onWarning({ lineNumber, line, errors });
199
+ }
200
+ else {
201
+ const addressPoints = yield findAddressPoints({
202
+ type: "smdLine",
203
+ smdLine,
204
+ municipality: state.currentFilterMunicipality,
205
+ municipalityPartCode,
206
+ });
207
+ state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
208
+ }
193
209
  }
194
210
  }
195
211
  }
196
212
  });
197
- const addNoStreetNameToSchool = (state) => __awaiter(void 0, void 0, void 0, function* () {
213
+ const addRestToSchool = (restPoints, schoolIzo, state) => __awaiter(void 0, void 0, void 0, function* () {
198
214
  const addressPoints = state.currentMunicipality.schools.flatMap((school) => school.addresses);
215
+ // filter out address points already present
216
+ const remainingPoints = restPoints.filter((point) => !addressPoints.some((ap) => ap.address === point.address));
217
+ // find the right school and add the remaining address points
218
+ const schoolIndex = state.currentMunicipality.schools.findIndex((school) => school.izo === schoolIzo);
219
+ state.currentMunicipality.schools[schoolIndex].addresses.push(...remainingPoints);
220
+ });
221
+ const addRests = (state) => __awaiter(void 0, void 0, void 0, function* () {
222
+ if (state.rests.noStreetNameSchoolIzo) {
223
+ yield addRestWithNoStreetNameToSchool(state);
224
+ }
225
+ if (state.rests.wholeMunicipalitySchoolIzo) {
226
+ yield addRestOfMunicipality(state);
227
+ }
228
+ for (const rest of state.rests.municipalityParts) {
229
+ yield addRestOfMunicipalityPart(state, rest.municipalityPartCode, rest.schoolIzo);
230
+ }
231
+ state.rests.noStreetNameSchoolIzo = null;
232
+ state.rests.wholeMunicipalitySchoolIzo = null;
233
+ state.rests.municipalityParts = [];
234
+ });
235
+ const addRestWithNoStreetNameToSchool = (state) => __awaiter(void 0, void 0, void 0, function* () {
199
236
  // get all address points without street name for current municipality
200
237
  const pointsNoStreetName = yield findAddressPoints({
201
- type: FindAddressPointsType.WholeMunicipalityNoStreetName,
238
+ type: "wholeMunicipalityNoStreetName",
202
239
  municipality: {
203
240
  code: state.currentMunicipality.founder.cityOrDistrictCode,
204
241
  type: state.currentMunicipality.founder.municipalityType,
205
242
  },
206
243
  });
207
- // filter out address points already present
208
- const remainingPoints = pointsNoStreetName.filter((point) => !addressPoints.some((ap) => ap.address === point.address));
209
- // find the right school and add the remaining address points
210
- const schoolIndex = state.currentMunicipality.schools.findIndex((school) => school.izo === state.noStreetNameSchoolIzo);
211
- state.currentMunicipality.schools[schoolIndex].addresses.push(...remainingPoints);
212
- state.noStreetNameSchoolIzo = null;
244
+ yield addRestToSchool(pointsNoStreetName, state.rests.noStreetNameSchoolIzo, state);
245
+ });
246
+ const addRestOfMunicipality = (state) => __awaiter(void 0, void 0, void 0, function* () {
247
+ // get all address points for current municipality
248
+ const allPoints = yield findAddressPoints({
249
+ type: "wholeMunicipality",
250
+ municipality: {
251
+ code: state.currentMunicipality.founder.cityOrDistrictCode,
252
+ type: state.currentMunicipality.founder.municipalityType,
253
+ },
254
+ });
255
+ yield addRestToSchool(allPoints, state.rests.wholeMunicipalitySchoolIzo, state);
256
+ });
257
+ const addRestOfMunicipalityPart = (state, municipalityPartCode, schoolIzo) => __awaiter(void 0, void 0, void 0, function* () {
258
+ // get all address points for current municipality
259
+ const allPoints = yield findAddressPoints({
260
+ type: "wholeMunicipalityPart",
261
+ municipalityPartCode,
262
+ });
263
+ yield addRestToSchool(allPoints, schoolIzo, state);
213
264
  });
214
265
  export const getNewMunicipality = (name) => __awaiter(void 0, void 0, void 0, function* () {
215
266
  const { founder, errors } = yield findFounder(name);
@@ -1,6 +1,7 @@
1
1
  import { TokenVocabulary } from "chevrotain";
2
2
  export declare const resetTokenState: () => void;
3
3
  export declare const prepareLine: (text: string) => string;
4
+ export declare const nameMatcherFactory: (pattern: RegExp) => (text: string, startOffset: number) => RegExpExecArray | [string] | null;
4
5
  export declare const streetNameMatcher: (text: string, startOffset: number) => RegExpExecArray | [string] | null;
5
6
  declare const MainSeparator: import("chevrotain").TokenType;
6
7
  declare const From: import("chevrotain").TokenType;
@@ -13,6 +14,7 @@ declare const AllType: import("chevrotain").TokenType;
13
14
  declare const OddType: import("chevrotain").TokenType;
14
15
  declare const EvenType: import("chevrotain").TokenType;
15
16
  declare const Without: import("chevrotain").TokenType;
17
+ declare const MunicipalityPartName: import("chevrotain").TokenType;
16
18
  declare const StreetName: import("chevrotain").TokenType;
17
19
  declare const Number: import("chevrotain").TokenType;
18
20
  declare const Hyphen: import("chevrotain").TokenType;
@@ -20,4 +22,4 @@ declare const Slash: import("chevrotain").TokenType;
20
22
  declare const Space: import("chevrotain").TokenType;
21
23
  declare const smdTokens: import("chevrotain").TokenType[];
22
24
  declare const tokenVocabulary: TokenVocabulary;
23
- export { MainSeparator, Separator, OddType, EvenType, DescriptiveType, AllType, Number, From, To, AndAbove, AndBelow, Without, Hyphen, Slash, Space, StreetName, smdTokens, tokenVocabulary, };
25
+ export { MainSeparator, Separator, OddType, EvenType, DescriptiveType, AllType, Number, From, To, AndAbove, AndBelow, Without, Hyphen, Slash, Space, StreetName, MunicipalityPartName, smdTokens, tokenVocabulary, };
@@ -1,7 +1,7 @@
1
1
  import { createToken, Lexer } from "chevrotain";
2
- let streetNameWasMatched = false;
2
+ let nameWasMatched = false;
3
3
  export const resetTokenState = () => {
4
- streetNameWasMatched = false;
4
+ nameWasMatched = false;
5
5
  };
6
6
  export const prepareLine = (text) => {
7
7
  return text
@@ -9,10 +9,12 @@ export const prepareLine = (text) => {
9
9
  .replace(/;/g, ",")
10
10
  .replace(/,$/, "");
11
11
  };
12
+ // this function prevents numbers that make part of the street name to be matched before the
13
+ // street/municipality part name is completely matched
12
14
  const tokenBlockerFactory = (pattern) => {
13
15
  const stickyPattern = new RegExp(pattern, "y");
14
16
  return (text, startOffset) => {
15
- if (streetNameWasMatched) {
17
+ if (nameWasMatched) {
16
18
  stickyPattern.lastIndex = startOffset;
17
19
  return stickyPattern.exec(text);
18
20
  }
@@ -35,26 +37,32 @@ const interruptPatterns = [
35
37
  "s výjimkou",
36
38
  "kromě",
37
39
  ];
40
+ // this pattern allows a single space or a single hyphen (without an adjacent space) between words
38
41
  const streetNamePattern = /[^ -]+([ -]?[^ -]+)*/;
39
- export const streetNameMatcher = (text, startOffset) => {
40
- if (streetNameWasMatched || startOffset > 0) {
41
- return null;
42
- }
43
- else {
44
- const result = streetNamePattern.exec(text);
45
- if (result !== null) {
46
- streetNameWasMatched = true;
47
- const firstIndex = interruptPatterns.reduce((currentMin, pattern) => {
48
- const index = text.search(new RegExp(` ${pattern}( |$)`));
49
- return index >= 0 ? Math.min(index, currentMin) : currentMin;
50
- }, Infinity);
51
- if (firstIndex < Infinity) {
52
- return [result[0].substring(0, firstIndex).trim()];
42
+ // same but with a prefix of municipality part
43
+ const municipalityPartNamePattern = /část (obce|města) [^ -]+([ -]?[^ -]+)*/;
44
+ export const nameMatcherFactory = (pattern) => {
45
+ return (text, startOffset) => {
46
+ if (nameWasMatched || startOffset > 0) {
47
+ return null;
48
+ }
49
+ else {
50
+ const result = pattern.exec(text);
51
+ if (result !== null) {
52
+ nameWasMatched = true;
53
+ const firstIndex = interruptPatterns.reduce((currentMin, pattern) => {
54
+ const index = text.search(new RegExp(` ${pattern}( |$)`));
55
+ return index >= 0 ? Math.min(index, currentMin) : currentMin;
56
+ }, Infinity);
57
+ if (firstIndex < Infinity) {
58
+ return [result[0].substring(0, firstIndex).trim()];
59
+ }
53
60
  }
61
+ return result;
54
62
  }
55
- return result;
56
- }
63
+ };
57
64
  };
65
+ export const streetNameMatcher = nameMatcherFactory(streetNamePattern);
58
66
  const MainSeparator = createToken({ name: "MainSeparator", pattern: / - / });
59
67
  const From = createToken({ name: "From", pattern: /od/ });
60
68
  const To = createToken({ name: "To", pattern: /do/ });
@@ -93,9 +101,13 @@ const Without = createToken({
93
101
  name: "Without",
94
102
  pattern: /bez|vyjma|mimo|s výjimkou|kromě/,
95
103
  });
104
+ const MunicipalityPartName = createToken({
105
+ name: "MunicipalityPartName",
106
+ pattern: nameMatcherFactory(municipalityPartNamePattern),
107
+ });
96
108
  const StreetName = createToken({
97
109
  name: "StreetName",
98
- pattern: streetNameMatcher,
110
+ pattern: nameMatcherFactory(streetNamePattern),
99
111
  });
100
112
  const Number = createToken({
101
113
  name: "Number",
@@ -131,6 +143,7 @@ const smdTokens = [
131
143
  Slash,
132
144
  Space,
133
145
  // Colon,
146
+ MunicipalityPartName,
134
147
  StreetName,
135
148
  ];
136
149
  MainSeparator.LABEL = "' - '";
@@ -151,4 +164,4 @@ const tokenVocabulary = {};
151
164
  smdTokens.forEach((tokenType) => {
152
165
  tokenVocabulary[tokenType.name] = tokenType;
153
166
  });
154
- export { MainSeparator, Separator, OddType, EvenType, DescriptiveType, AllType, Number, From, To, AndAbove, AndBelow, Without, Hyphen, Slash, Space, StreetName, smdTokens, tokenVocabulary, };
167
+ export { MainSeparator, Separator, OddType, EvenType, DescriptiveType, AllType, Number, From, To, AndAbove, AndBelow, Without, Hyphen, Slash, Space, StreetName, MunicipalityPartName, smdTokens, tokenVocabulary, };
@@ -4,10 +4,16 @@ export interface ProcessedSmdLines {
4
4
  smdLines: SmdLine[];
5
5
  errors: SmdError[];
6
6
  }
7
- export interface SmdLine {
7
+ export type SmdLineType = "street" | "wholeMunicipalityLine" | "municipalitySwitch" | "municipalityPart";
8
+ export type SmdLine = {
9
+ type: "street";
8
10
  street: string;
9
11
  numberSpec: SeriesSpec[] | NegativeSeriesSpec;
10
- }
12
+ } | {
13
+ type: "municipalityPart";
14
+ municipalityPart: string;
15
+ numberSpec: SeriesSpec[] | NegativeSeriesSpec;
16
+ };
11
17
  export interface SeriesSpec {
12
18
  type: SeriesType;
13
19
  ranges: (RangeSpec | FullStreetNumber)[];
@@ -74,7 +80,14 @@ export interface SmdState {
74
80
  currentMunicipality: MunicipalityWithFounder;
75
81
  currentFilterMunicipality: DbMunicipality;
76
82
  currentSchool: School;
77
- noStreetNameSchoolIzo: string;
83
+ rests: {
84
+ noStreetNameSchoolIzo: string;
85
+ municipalityParts: {
86
+ municipalityPartCode: number;
87
+ schoolIzo: string;
88
+ }[];
89
+ wholeMunicipalitySchoolIzo: string;
90
+ };
78
91
  municipalities: Municipality[];
79
92
  }
80
93
  export interface ProcessLineCallbackParams {
package/package.json CHANGED
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "name": "text-to-map",
59
59
  "description": "Text To Map usiluje o lepší, strojově zpracovatelné využití částí vyhlášek s výčtem ulic a dalších lokací. Jde o rozšiřitelnou sadu konceptů a nástrojů, které zajistí hladký převod výčtu ulic a jejich rozsahů v lidsky srozumitelném jazyce do strojově zpracovatelného, uchopitelného formátu.",
60
- "version": "1.1.15",
60
+ "version": "1.1.17",
61
61
  "main": "dist/index.js",
62
62
  "types": "dist/index.d.ts",
63
63
  "files": [