text-to-map 1.1.16 → 1.1.18

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)
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { readFileSync, writeFileSync } from "fs";
11
- import { getNewMunicipality, parseOrdinanceToAddressPoints, } from "../street-markdown/smd";
11
+ import { getNewMunicipalityByName, parseOrdinanceToAddressPoints, } from "../street-markdown/smd";
12
12
  function main() {
13
13
  return __awaiter(this, void 0, void 0, function* () {
14
14
  // take first node argument as a file name
@@ -35,7 +35,7 @@ function main() {
35
35
  warningCount++;
36
36
  };
37
37
  console.time("downloadAndImportAllLatestAddressPoints");
38
- const { municipality } = yield getNewMunicipality("Česká Lípa");
38
+ const { municipality } = yield getNewMunicipalityByName("Česká Lípa");
39
39
  const addressPoints = yield parseOrdinanceToAddressPoints(lines, {
40
40
  // currentMunicipality: municipality,
41
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;
@@ -183,29 +183,11 @@ const getAllStreets = (cityCode) => __awaiter(void 0, void 0, void 0, function*
183
183
  const knex = getKnexDb();
184
184
  return yield knex.pluck("name").from("street").where("city_code", cityCode);
185
185
  });
186
- export var FindAddressPointsType;
187
- (function (FindAddressPointsType) {
188
- FindAddressPointsType[FindAddressPointsType["SmdLine"] = 0] = "SmdLine";
189
- FindAddressPointsType[FindAddressPointsType["MunicipalityPart"] = 1] = "MunicipalityPart";
190
- FindAddressPointsType[FindAddressPointsType["WholeMunicipality"] = 2] = "WholeMunicipality";
191
- FindAddressPointsType[FindAddressPointsType["WholeMunicipalityNoStreetName"] = 3] = "WholeMunicipalityNoStreetName";
192
- })(FindAddressPointsType || (FindAddressPointsType = {}));
193
186
  export const findAddressPoints = (params) => __awaiter(void 0, void 0, void 0, function* () {
194
187
  const knex = getKnexDb();
195
- const queryParams = params.type === FindAddressPointsType.SmdLine
196
- ? [params.smdLine.street, params.municipality.code]
197
- : params.type === FindAddressPointsType.MunicipalityPart
198
- ? [params.municipalityPartCode]
199
- : [params.municipality.code];
200
- const streetJoinCondition = params.type === FindAddressPointsType.SmdLine
201
- ? `JOIN street s ON a.street_code = s.code AND s.name = ? ${isSqlite(knex) ? "COLLATE NOCASE" : ""}`
202
- : "LEFT JOIN street s ON a.street_code = s.code";
203
- const whereCondition = params.type === FindAddressPointsType.MunicipalityPart
204
- ? "a.municipality_part_code = ?"
205
- : getMunicipalityWhere("a", params.municipality) +
206
- (params.type === FindAddressPointsType.WholeMunicipalityNoStreetName
207
- ? " AND a.street_code IS NULL"
208
- : "");
188
+ const queryParams = getQueryParams(params);
189
+ const streetJoinCondition = getStreetJoinCondition(params);
190
+ const whereCondition = getWhereCondition(params);
209
191
  const queryResult = yield rawQuery(`SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
210
192
  a.orientational_number, a.orientational_number_letter, c.name AS city_name,
211
193
  d.name AS district_name, m.name AS municipality_part_name, p.name AS prague_district_name,
@@ -218,28 +200,32 @@ export const findAddressPoints = (params) => __awaiter(void 0, void 0, void 0, f
218
200
  LEFT JOIN municipality_part m ON a.municipality_part_code = m.code
219
201
  LEFT JOIN prague_district p ON a.prague_district_code = p.code
220
202
  WHERE ${whereCondition}`, queryParams);
221
- const filteredAddressPoints = queryResult.map(rowToAddressPoint);
222
- if (params.type !== FindAddressPointsType.SmdLine) {
223
- 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;
224
210
  }
225
211
  const result = [];
226
212
  const numberSpec = params.smdLine.numberSpec;
227
213
  if (isSeriesSpecArray(numberSpec)) {
228
214
  numberSpec.forEach((seriesSpec) => {
229
- result.push(...filterAddressPointsByRanges(filteredAddressPoints, seriesSpec));
215
+ result.push(...filterAddressPointsByRanges(addressPoints, seriesSpec));
230
216
  });
231
217
  // when no number specs are present, we take everything
232
218
  if (numberSpec.length === 0) {
233
- result.push(...filteredAddressPoints);
219
+ result.push(...addressPoints);
234
220
  }
235
221
  }
236
222
  else if (isNegativeSeriesSpec(numberSpec)) {
237
223
  // negative number spec (e.g. odd numbers except 13-17)
238
- const toExclude = filterAddressPointsByRanges(filteredAddressPoints, numberSpec);
239
- result.push(...filteredAddressPoints.filter((addressPoint) => !toExclude.includes(addressPoint)));
224
+ const toExclude = filterAddressPointsByRanges(addressPoints, numberSpec);
225
+ result.push(...addressPoints.filter((addressPoint) => !toExclude.includes(addressPoint)));
240
226
  }
241
227
  return result;
242
- });
228
+ };
243
229
  const filterAddressPointsByRanges = (addressPoints, seriesSpec) => {
244
230
  const result = [];
245
231
  seriesSpec.ranges.forEach((range) => {
@@ -263,6 +249,32 @@ const filterAddressPointsByRanges = (addressPoints, seriesSpec) => {
263
249
  }
264
250
  return result;
265
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
+ };
266
278
  export const isInRange = (number, letter, range) => {
267
279
  if (number === null) {
268
280
  return !range.from && !range.to;
@@ -1,6 +1,10 @@
1
1
  import { DbMunicipalityResult, MunicipalityPartResult, SmdError } from "../street-markdown/types";
2
2
  import { Founder, MunicipalityType } from "./types";
3
3
  export declare const insertFounders: (founders: Founder[]) => Promise<number>;
4
+ export declare const getFounderById: (id: number) => Promise<{
5
+ founder: Founder;
6
+ errors: SmdError[];
7
+ }>;
4
8
  export declare const findFounder: (nameWithHashtag: string) => Promise<{
5
9
  founder: Founder;
6
10
  errors: SmdError[];
@@ -223,14 +223,27 @@ const extractFounderName = (line) => {
223
223
  return line.trim();
224
224
  }
225
225
  };
226
- export const findFounder = (nameWithHashtag) => __awaiter(void 0, void 0, void 0, function* () {
227
- const errors = [];
228
- const name = extractFounderName(nameWithHashtag);
229
- const result = yield getKnexDb()
226
+ const getBaseFounderQuery = () => {
227
+ return getKnexDb()
230
228
  .select("f.id", "f.name", "f.ico", "f.founder_type_code", "f.city_code", "f.city_district_code")
231
229
  .from("founder as f")
232
230
  .leftJoin("city as c", "c.code", "f.city_code")
233
- .leftJoin("city_district as d", "d.code", "f.city_district_code")
231
+ .leftJoin("city_district as d", "d.code", "f.city_district_code");
232
+ };
233
+ export const getFounderById = (id) => __awaiter(void 0, void 0, void 0, function* () {
234
+ const result = yield getBaseFounderQuery().where("f.id", id).first();
235
+ const founder = yield resultToFounder(result);
236
+ return {
237
+ founder: founder !== null && founder !== void 0 ? founder : null,
238
+ errors: founder
239
+ ? []
240
+ : [wholeLineError(`Zřizovatel s id ${id} neexistuje.`, "")],
241
+ };
242
+ });
243
+ export const findFounder = (nameWithHashtag) => __awaiter(void 0, void 0, void 0, function* () {
244
+ const errors = [];
245
+ const name = extractFounderName(nameWithHashtag);
246
+ const result = yield getBaseFounderQuery()
234
247
  .where("f.name", name)
235
248
  .orWhere("c.name", name)
236
249
  .orWhere("d.name", name)
@@ -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
+ };
@@ -2,7 +2,11 @@ import { Founder } from "../db/types";
2
2
  import { ErrorCallbackParams, Municipality, MunicipalityWithFounder, ProcessLineCallbackParams, School, SmdError, SmdState } from "./types";
3
3
  export declare const parseOrdinanceToAddressPoints: (lines: string[], initialState?: Partial<SmdState>, onError?: (params: ErrorCallbackParams) => void, onWarning?: (params: ErrorCallbackParams) => void, onProcessedLine?: (params: ProcessLineCallbackParams) => void) => Promise<Municipality[]>;
4
4
  export declare const convertMunicipality: (municipality: MunicipalityWithFounder) => Municipality;
5
- export declare const getNewMunicipality: (name: string) => Promise<{
5
+ export declare const getNewMunicipalityByName: (name: string) => Promise<{
6
+ municipality: MunicipalityWithFounder;
7
+ errors: SmdError[];
8
+ }>;
9
+ export declare const getNewMunicipalityByFounderId: (founderId: number) => Promise<{
6
10
  municipality: MunicipalityWithFounder;
7
11
  errors: SmdError[];
8
12
  }>;
@@ -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
- import { findFounder } from "../db/founders";
12
+ import { findFounder, getFounderById } 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,13 +114,8 @@ 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
- }
112
- const { municipality, errors } = yield getNewMunicipality(line);
117
+ yield completeCurrentMunicipality(state);
118
+ const { municipality, errors } = yield getNewMunicipalityByName(line);
113
119
  if (errors.length > 0) {
114
120
  onError({ lineNumber, line, errors });
115
121
  }
@@ -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,48 +178,105 @@ 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);
213
245
  });
214
- export const getNewMunicipality = (name) => __awaiter(void 0, void 0, void 0, function* () {
215
- const { founder, errors } = yield findFounder(name);
216
- return {
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",
217
250
  municipality: {
218
- municipalityName: founder ? founder.name : "Neznámá obec",
219
- founder,
220
- schools: [],
251
+ code: state.currentMunicipality.founder.cityOrDistrictCode,
252
+ type: state.currentMunicipality.founder.municipalityType,
221
253
  },
222
- errors,
223
- };
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);
264
+ });
265
+ export const getNewMunicipalityByName = (name) => __awaiter(void 0, void 0, void 0, function* () {
266
+ const { founder, errors } = yield findFounder(name);
267
+ return getNewMunicipality(founder, errors);
268
+ });
269
+ export const getNewMunicipalityByFounderId = (founderId) => __awaiter(void 0, void 0, void 0, function* () {
270
+ const { founder, errors } = yield getFounderById(founderId);
271
+ return getNewMunicipality(founder, errors);
272
+ });
273
+ const getNewMunicipality = (founder, errors) => ({
274
+ municipality: {
275
+ municipalityName: founder ? founder.name : "Neznámá obec",
276
+ founder,
277
+ schools: [],
278
+ },
279
+ errors,
224
280
  });
225
281
  export const getNewSchool = (name, founder, lineNumber, onError) => __awaiter(void 0, void 0, void 0, function* () {
226
282
  let exportSchool = {
@@ -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.16",
60
+ "version": "1.1.18",
61
61
  "main": "dist/index.js",
62
62
  "types": "dist/index.d.ts",
63
63
  "files": [