japanese_address_parser 3.1.0 → 3.1.2

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.
@@ -78,6 +78,7 @@ function __generator(thisArg, body) {
78
78
 
79
79
  var gh_pages_endpoint = 'https://geolonia.github.io/japanese-addresses/api/ja';
80
80
  var currentConfig = {
81
+ interfaceVersion: 1,
81
82
  japaneseAddressesApi: gh_pages_endpoint,
82
83
  townCacheSize: 1000,
83
84
  };
@@ -85,8 +86,13 @@ var currentConfig = {
85
86
  var kan2num = function (string) {
86
87
  var kanjiNumbers = japaneseNumeral.findKanjiNumbers(string);
87
88
  for (var i = 0; i < kanjiNumbers.length; i++) {
88
- // @ts-ignore
89
- string = string.replace(kanjiNumbers[i], japaneseNumeral.kanji2number(kanjiNumbers[i]));
89
+ try {
90
+ // @ts-ignore
91
+ string = string.replace(kanjiNumbers[i], japaneseNumeral.kanji2number(kanjiNumbers[i]));
92
+ }
93
+ catch (error) {
94
+ // ignore
95
+ }
90
96
  }
91
97
  return string;
92
98
  };
@@ -191,6 +197,7 @@ var cachedPrefectures = undefined;
191
197
  var cachedTowns = {};
192
198
  var cachedGaikuListItem = {};
193
199
  var cachedResidentials = {};
200
+ var cachedAddrs = {}; // TODO: use LRU
194
201
  var cachedSameNamedPrefectureCityRegexPatterns = undefined;
195
202
  var getPrefectures = function () { return __awaiter(void 0, void 0, void 0, function () {
196
203
  var prefsResp, data;
@@ -200,7 +207,7 @@ var getPrefectures = function () { return __awaiter(void 0, void 0, void 0, func
200
207
  if (typeof cachedPrefectures !== 'undefined') {
201
208
  return [2 /*return*/, cachedPrefectures];
202
209
  }
203
- return [4 /*yield*/, __internals.fetch('.json')]; // ja.json
210
+ return [4 /*yield*/, __internals.fetch('.json', { level: 1 })]; // ja.json
204
211
  case 1:
205
212
  prefsResp = _a.sent() // ja.json
206
213
  ;
@@ -254,7 +261,7 @@ var getTowns = function (pref, city) { return __awaiter(void 0, void 0, void 0,
254
261
  if (typeof cachedTown !== 'undefined') {
255
262
  return [2 /*return*/, cachedTown];
256
263
  }
257
- return [4 /*yield*/, __internals.fetch(['', encodeURI(pref), encodeURI(city) + '.json'].join('/'))];
264
+ return [4 /*yield*/, __internals.fetch(['', encodeURI(pref), encodeURI(city) + '.json'].join('/'), { level: 3, pref: pref, city: city })];
258
265
  case 1:
259
266
  townsResp = _a.sent();
260
267
  return [4 /*yield*/, townsResp.json()];
@@ -269,7 +276,10 @@ var getGaikuList = function (pref, city, town) { return __awaiter(void 0, void 0
269
276
  return __generator(this, function (_b) {
270
277
  switch (_b.label) {
271
278
  case 0:
272
- cacheKey = pref + "-" + city + "-" + town;
279
+ if (currentConfig.interfaceVersion > 1) {
280
+ throw new Error("Invalid config.interfaceVersion: " + currentConfig.interfaceVersion + "'}. Please set config.interfaceVersion to 1.");
281
+ }
282
+ cacheKey = pref + "-" + city + "-" + town + "-v" + currentConfig.interfaceVersion;
273
283
  cache = cachedGaikuListItem[cacheKey];
274
284
  if (typeof cache !== 'undefined') {
275
285
  return [2 /*return*/, cache];
@@ -297,7 +307,10 @@ var getResidentials = function (pref, city, town) { return __awaiter(void 0, voi
297
307
  return __generator(this, function (_b) {
298
308
  switch (_b.label) {
299
309
  case 0:
300
- cacheKey = pref + "-" + city + "-" + town;
310
+ if (currentConfig.interfaceVersion > 1) {
311
+ throw new Error("Invalid config.interfaceVersion: " + currentConfig.interfaceVersion + "'}. Please set config.interfaceVersion to 1.");
312
+ }
313
+ cacheKey = pref + "-" + city + "-" + town + "-v" + currentConfig.interfaceVersion;
301
314
  cache = cachedResidentials[cacheKey];
302
315
  if (typeof cache !== 'undefined') {
303
316
  return [2 /*return*/, cache];
@@ -331,6 +344,39 @@ var getResidentials = function (pref, city, town) { return __awaiter(void 0, voi
331
344
  }
332
345
  });
333
346
  }); };
347
+ var getAddrs = function (pref, city, town) { return __awaiter(void 0, void 0, void 0, function () {
348
+ var cacheKey, cache, addrsResp, addrs;
349
+ return __generator(this, function (_b) {
350
+ switch (_b.label) {
351
+ case 0:
352
+ if (currentConfig.interfaceVersion < 2) {
353
+ throw new Error("Invalid config.interfaceVersion: " + currentConfig.interfaceVersion + "'}. Please set config.interfaceVersion to 2 or higher");
354
+ }
355
+ cacheKey = pref + "-" + city + "-" + town + "-v" + currentConfig.interfaceVersion;
356
+ cache = cachedAddrs[cacheKey];
357
+ if (typeof cache !== 'undefined') {
358
+ return [2 /*return*/, cache];
359
+ }
360
+ return [4 /*yield*/, __internals.fetch(['', encodeURI(pref), encodeURI(city), encodeURI(town) + '.json'].join('/'), { level: 8, pref: pref, city: city, town: town })];
361
+ case 1:
362
+ addrsResp = _b.sent();
363
+ _b.label = 2;
364
+ case 2:
365
+ _b.trys.push([2, 4, , 5]);
366
+ return [4 /*yield*/, addrsResp.json()];
367
+ case 3:
368
+ addrs = (_b.sent());
369
+ return [3 /*break*/, 5];
370
+ case 4:
371
+ _b.sent();
372
+ addrs = [];
373
+ return [3 /*break*/, 5];
374
+ case 5:
375
+ addrs.sort(function (res1, res2) { return res1.addr.length - res2.addr.length; });
376
+ return [2 /*return*/, (cachedAddrs[cacheKey] = addrs)];
377
+ }
378
+ });
379
+ }); };
334
380
  // 十六町 のように漢数字と町が連結しているか
335
381
  var isKanjiNumberFollewedByCho = function (targetTownName) {
336
382
  var xCho = targetTownName.match(/.町/g);
@@ -551,10 +597,32 @@ var normalizeResidentialPart = function (addr, pref, city, town) { return __awai
551
597
  }
552
598
  });
553
599
  }); };
600
+ var normalizeAddrPart = function (addr, pref, city, town) { return __awaiter(void 0, void 0, void 0, function () {
601
+ var addrListItem, addrItem, other;
602
+ return __generator(this, function (_a) {
603
+ switch (_a.label) {
604
+ case 0: return [4 /*yield*/, getAddrs(pref, city, town)
605
+ // 住居表示住所、および地番住所が見つからなかった
606
+ ];
607
+ case 1:
608
+ addrListItem = _a.sent();
609
+ // 住居表示住所、および地番住所が見つからなかった
610
+ if (addrListItem.length === 0) {
611
+ return [2 /*return*/, null];
612
+ }
613
+ addrItem = addrListItem.find(function (item) { return addr.startsWith(item.addr); });
614
+ if (addrItem) {
615
+ other = addr.replace(addrItem.addr, '').trim();
616
+ return [2 /*return*/, { addr: addrItem.addr, other: other, lat: addrItem.lat, lng: addrItem.lng }];
617
+ }
618
+ return [2 /*return*/, null];
619
+ }
620
+ });
621
+ }); };
554
622
  var normalize$1 = function (address, _option) {
555
623
  if (_option === void 0) { _option = defaultOption; }
556
624
  return __awaiter(void 0, void 0, void 0, function () {
557
- var option, addr, pref, city, town, lat, lng, level, normalized, prefectures, prefs, prefPatterns, sameNamedPrefectureCityRegexPatterns, i, _a, prefectureCity, reg, match, i, _b, _pref, pattern, match, matched, _pref, cities, cityPatterns, i, _c, _city, pattern, match, i, normalized_1, cities, cityPatterns, i, _d, _city, pattern, match, result;
625
+ var option, addr, pref, city, town, lat, lng, level, normalized, prefectures, prefs, prefPatterns, sameNamedPrefectureCityRegexPatterns, i, _a, prefectureCity, reg, match, i, _b, _pref, pattern, match, matched, _pref, cities, cityPatterns, i, _c, _city, pattern, match, i, normalized_1, cities, cityPatterns, i, _d, _city, pattern, match, normalizedAddrPart, other, result, result;
558
626
  return __generator(this, function (_e) {
559
627
  switch (_e.label) {
560
628
  case 0:
@@ -714,7 +782,7 @@ var normalize$1 = function (address, _option) {
714
782
  // `-1` のようなケース
715
783
  return kan2num(s);
716
784
  })
717
- .replace(/-[^0-9]+([0-9〇一二三四五六七八九十百千]+)/, function (s) {
785
+ .replace(/-[^0-9]([0-9〇一二三四五六七八九十百千]+)/, function (s) {
718
786
  // `-あ1` のようなケース
719
787
  return kan2num(zen2han(s));
720
788
  })
@@ -727,12 +795,52 @@ var normalize$1 = function (address, _option) {
727
795
  _e.label = 8;
728
796
  case 8:
729
797
  addr = patchAddr(pref, city, town, addr);
730
- if (!(option.level > 3 && normalized && town)) return [3 /*break*/, 10];
731
- return [4 /*yield*/, normalizeResidentialPart(addr, pref, city, town)];
798
+ if (pref)
799
+ level = level + 1;
800
+ if (city)
801
+ level = level + 1;
802
+ if (town)
803
+ level = level + 1;
804
+ if (option.level <= 3 || level < 3) {
805
+ return [2 /*return*/, { pref: pref, city: city, town: town, addr: addr, level: level, lat: lat, lng: lng }];
806
+ }
807
+ if (!(currentConfig.interfaceVersion === 2)) return [3 /*break*/, 10];
808
+ return [4 /*yield*/, normalizeAddrPart(addr, pref, city, town)];
732
809
  case 9:
733
- normalized = _e.sent();
734
- _e.label = 10;
810
+ normalizedAddrPart = _e.sent();
811
+ other = undefined;
812
+ if (normalizedAddrPart) {
813
+ addr = normalizedAddrPart.addr;
814
+ if (normalizedAddrPart.other) {
815
+ other = normalizedAddrPart.other;
816
+ }
817
+ if (normalizedAddrPart.lat !== null)
818
+ lat = parseFloat(normalizedAddrPart.lat);
819
+ if (normalizedAddrPart.lng !== null)
820
+ lng = parseFloat(normalizedAddrPart.lng);
821
+ level = 8;
822
+ }
823
+ result = {
824
+ pref: pref,
825
+ city: city,
826
+ town: town,
827
+ addr: addr,
828
+ level: level,
829
+ lat: lat,
830
+ lng: lng,
831
+ };
832
+ if (other) {
833
+ result.other = other;
834
+ }
835
+ return [2 /*return*/, result];
735
836
  case 10:
837
+ if (!(currentConfig.interfaceVersion === 1)) return [3 /*break*/, 13];
838
+ if (!(option.level > 3 && normalized && town)) return [3 /*break*/, 12];
839
+ return [4 /*yield*/, normalizeResidentialPart(addr, pref, city, town)];
840
+ case 11:
841
+ normalized = _e.sent();
842
+ _e.label = 12;
843
+ case 12:
736
844
  if (normalized) {
737
845
  lat = parseFloat(normalized.lat);
738
846
  lng = parseFloat(normalized.lng);
@@ -741,12 +849,6 @@ var normalize$1 = function (address, _option) {
741
849
  lat = null;
742
850
  lng = null;
743
851
  }
744
- if (pref)
745
- level = level + 1;
746
- if (city)
747
- level = level + 1;
748
- if (town)
749
- level = level + 1;
750
852
  result = {
751
853
  pref: pref,
752
854
  city: city,
@@ -766,45 +868,79 @@ var normalize$1 = function (address, _option) {
766
868
  result.level = 8;
767
869
  }
768
870
  return [2 /*return*/, result];
871
+ case 13: throw new Error('invalid interfaceVersion');
769
872
  }
770
873
  });
771
874
  });
772
875
  };
773
876
 
774
- var fetchOrReadFile = function (input) { return __awaiter(void 0, void 0, void 0, function () {
775
- var fileURL, filePath_1;
776
- return __generator(this, function (_a) {
777
- fileURL = new URL("" + config$1.japaneseAddressesApi + input);
778
- if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
779
- if (config$1.geoloniaApiKey) {
780
- fileURL.search = "?geolonia-api-key=" + config$1.geoloniaApiKey;
781
- }
782
- return [2 /*return*/, unfetch__default['default'](fileURL.toString())];
783
- }
784
- else if (fileURL.protocol === 'file:') {
785
- filePath_1 = process.platform === 'win32' ? decodeURI(fileURL.pathname).substr(1) : decodeURI(fileURL.pathname);
786
- return [2 /*return*/, {
787
- json: function () { return __awaiter(void 0, void 0, void 0, function () {
788
- var contents;
789
- return __generator(this, function (_a) {
790
- switch (_a.label) {
791
- case 0: return [4 /*yield*/, fs.promises.readFile(filePath_1)];
792
- case 1:
793
- contents = _a.sent();
794
- return [2 /*return*/, JSON.parse(contents.toString('utf-8'))];
795
- }
796
- });
797
- }); },
798
- }];
799
- }
800
- else {
801
- throw new Error("Unknown URL schema: " + fileURL.protocol);
877
+ var requestHandlers = {
878
+ file: function (fileURL) {
879
+ var filePath = process.platform === 'win32'
880
+ ? decodeURI(fileURL.pathname).substr(1)
881
+ : decodeURI(fileURL.pathname);
882
+ return {
883
+ json: function () { return __awaiter(void 0, void 0, void 0, function () {
884
+ var contents;
885
+ return __generator(this, function (_a) {
886
+ switch (_a.label) {
887
+ case 0: return [4 /*yield*/, fs.promises.readFile(filePath)];
888
+ case 1:
889
+ contents = _a.sent();
890
+ return [2 /*return*/, JSON.parse(contents.toString('utf-8'))];
891
+ }
892
+ });
893
+ }); },
894
+ };
895
+ },
896
+ http: function (fileURL) {
897
+ if (config$1.geoloniaApiKey) {
898
+ fileURL.search = "?geolonia-api-key=" + config$1.geoloniaApiKey;
802
899
  }
900
+ return unfetch__default['default'](fileURL.toString());
901
+ },
902
+ };
903
+ /**
904
+ * 正規化のためのデータを取得する
905
+ * @param input - Path part like '東京都/文京区.json'
906
+ * @param requestOptions - input を構造化したデータ
907
+ */
908
+ var fetchOrReadFile = function (input, requestOptions) {
909
+ if (requestOptions === void 0) { requestOptions = { level: -1 }; }
910
+ return __awaiter(void 0, void 0, void 0, function () {
911
+ var fileURL, result_1;
912
+ return __generator(this, function (_a) {
913
+ switch (_a.label) {
914
+ case 0:
915
+ fileURL = new URL("" + config$1.japaneseAddressesApi + input);
916
+ if (!(config$1.transformRequest && requestOptions.level !== -1)) return [3 /*break*/, 2];
917
+ return [4 /*yield*/, config$1.transformRequest(fileURL, requestOptions)];
918
+ case 1:
919
+ result_1 = _a.sent();
920
+ return [2 /*return*/, {
921
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
922
+ return [2 /*return*/, Promise.resolve(result_1)];
923
+ }); }); },
924
+ }];
925
+ case 2:
926
+ if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
927
+ return [2 /*return*/, requestHandlers.http(fileURL)];
928
+ }
929
+ else if (fileURL.protocol === 'file:') {
930
+ return [2 /*return*/, requestHandlers.file(fileURL)];
931
+ }
932
+ else {
933
+ throw new Error("Unknown URL schema: " + fileURL.protocol);
934
+ }
935
+ case 3: return [2 /*return*/];
936
+ }
937
+ });
803
938
  });
804
- }); };
939
+ };
805
940
  __internals.fetch = fetchOrReadFile;
806
941
  var config = config$1;
807
942
  var normalize = normalize$1;
808
943
 
809
944
  exports.config = config;
810
945
  exports.normalize = normalize;
946
+ exports.requestHandlers = requestHandlers;
@@ -1,18 +1,35 @@
1
+ import { PrefectureList, TownList, AddrList } from './lib/cacheRegexes';
2
+ declare type TransformRequestResponse = null | PrefectureList | TownList | AddrList;
3
+ export declare type TransformRequestQuery = {
4
+ level: number;
5
+ pref?: string;
6
+ city?: string;
7
+ town?: string;
8
+ };
9
+ export declare type TransformRequestFunction = (url: URL, query: TransformRequestQuery) => TransformRequestResponse | Promise<TransformRequestResponse>;
1
10
  /**
2
11
  * normalize {@link Normalizer} の動作オプション。
3
12
  */
4
13
  export interface Config {
14
+ /**
15
+ * レスポンス型のバージョン。デフォルト 1
16
+ * 1 の場合は jyukyo: string, gaiku: string
17
+ * 2 の場合は addr: string, other: string
18
+ */
19
+ interfaceVersion: number;
5
20
  /** 住所データを URL 形式で指定。 file:// 形式で指定するとローカルファイルを参照できます。 */
6
21
  japaneseAddressesApi: string;
7
22
  /** 町丁目のデータを何件までキャッシュするか。デフォルト 1,000 */
8
23
  townCacheSize: number;
24
+ /** 住所データへのリクエストを変形するオプション。 interfaceVersion === 2 で有効 */
25
+ transformRequest?: TransformRequestFunction;
9
26
  geoloniaApiKey?: string;
10
27
  }
11
28
  export declare const config: Config;
12
29
  /**
13
30
  * 住所の正規化結果として戻されるオブジェクト
14
31
  */
15
- export interface NormalizeResult {
32
+ interface NormalizeResult_v1 {
16
33
  /** 都道府県 */
17
34
  pref: string;
18
35
  /** 市区町村 */
@@ -40,6 +57,32 @@ export interface NormalizeResult {
40
57
  */
41
58
  level: number;
42
59
  }
60
+ declare type NormalizeResult_v2 = {
61
+ /** 都道府県 */
62
+ pref: string;
63
+ /** 市区町村 */
64
+ city: string;
65
+ /** 町丁目 */
66
+ town: string;
67
+ /** 住居表示または地番 */
68
+ addr: string;
69
+ /** 正規化後の住所文字列 */
70
+ other?: string;
71
+ /** 緯度。データが存在しない場合は null */
72
+ lat: number | null;
73
+ /** 経度。データが存在しない場合は null */
74
+ lng: number | null;
75
+ /**
76
+ * 住所文字列をどこまで判別できたかを表す正規化レベル
77
+ * - 0 - 都道府県も判別できなかった。
78
+ * - 1 - 都道府県まで判別できた。
79
+ * - 2 - 市区町村まで判別できた。
80
+ * - 3 - 町丁目まで判別できた。
81
+ * - 8 - 住居表示住所の街区符号・住居番号までの判別または地番住所の判別ができた。
82
+ */
83
+ level: number;
84
+ };
85
+ export declare type NormalizeResult = NormalizeResult_v1 | NormalizeResult_v2;
43
86
  /**
44
87
  * 正規化関数の {@link normalize} のオプション
45
88
  */
@@ -64,7 +107,7 @@ export interface Option {
64
107
  * @see https://github.com/geolonia/normalize-japanese-addresses#normalizeaddress-string
65
108
  */
66
109
  export declare type Normalizer = (input: string, option?: Option) => Promise<NormalizeResult>;
67
- export declare type FetchLike = (input: string) => Promise<Response | {
110
+ export declare type FetchLike = (input: string, requestQuery?: TransformRequestQuery) => Promise<Response | {
68
111
  json: () => Promise<unknown>;
69
112
  }>;
70
113
  /**
@@ -74,3 +117,4 @@ export declare const __internals: {
74
117
  fetch: FetchLike;
75
118
  };
76
119
  export declare const normalize: Normalizer;
120
+ export {};
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@geolonia/normalize-japanese-addresses",
3
- "version": "2.8.0",
3
+ "version": "2.9.2",
4
4
  "description": "",
5
5
  "browser": "./dist/main-browser.js",
6
6
  "main": "./dist/main-node.js",
7
7
  "types": "./dist/normalize.d.ts",
8
8
  "scripts": {
9
- "test": "npm run test:main && npm run test:addresses && npm run test:node:platform && npm run test:residential",
9
+ "test": "npm run test:main && npm run test:addresses && npm run test:node:platform && npm run test:advanced",
10
10
  "test:node:platform": "npm run test:node-win || npm run test:node",
11
11
  "test:main": "jest test/main.test.ts",
12
12
  "test:addresses": "jest test/addresses.test.ts",
13
- "test:residential": "jest test/residential.test.ts --runInBand",
13
+ "test:advanced": "jest test/advanced --runInBand",
14
+ "test:util": "jest test/util.test.ts",
14
15
  "test:node": "curl -sL https://github.com/geolonia/japanese-addresses/archive/refs/heads/master.tar.gz | tar xvfz - -C ./test > nul 2>&1 && jest test/fs.test.ts",
15
16
  "test:node-win": "curl -sL https://github.com/geolonia/japanese-addresses/archive/refs/heads/master.tar.gz -o master.tar.gz && 7z x master.tar.gz -y -o./ && 7z x ./master.tar -y -o./test && del master.tar.gz master.tar && jest test/fs.test.ts",
16
17
  "test:generate-test-data": "npx ts-node -O '{\"module\":\"commonjs\"}' test/build-test-data.ts > test/addresses.csv",
@@ -4,6 +4,7 @@ export const gh_pages_endpoint =
4
4
  'https://geolonia.github.io/japanese-addresses/api/ja'
5
5
 
6
6
  export const currentConfig: Config = {
7
+ interfaceVersion: 1,
7
8
  japaneseAddressesApi: gh_pages_endpoint,
8
9
  townCacheSize: 1_000,
9
10
  }
@@ -5,7 +5,7 @@ import { currentConfig } from '../config'
5
5
  import { __internals } from '../normalize'
6
6
  import { findKanjiNumbers } from '@geolonia/japanese-numeral'
7
7
 
8
- type PrefectureList = { [key: string]: string[] }
8
+ export type PrefectureList = { [key: string]: string[] }
9
9
  interface SingleTown {
10
10
  town: string
11
11
  originalTown?: string
@@ -13,8 +13,13 @@ interface SingleTown {
13
13
  lat: string
14
14
  lng: string
15
15
  }
16
- type TownList = SingleTown[]
17
-
16
+ export type TownList = SingleTown[]
17
+ interface SingleAddr {
18
+ addr: string
19
+ lat: string | null
20
+ lng: string | null
21
+ }
22
+ export type AddrList = SingleAddr[]
18
23
  interface GaikuListItem {
19
24
  gaiku: string
20
25
  lat: string
@@ -40,16 +45,16 @@ let cachedPrefectures: PrefectureList | undefined = undefined
40
45
  const cachedTowns: { [key: string]: TownList } = {}
41
46
  const cachedGaikuListItem: { [key: string]: GaikuListItem[] } = {}
42
47
  const cachedResidentials: { [key: string]: ResidentialList } = {}
43
- let cachedSameNamedPrefectureCityRegexPatterns:
44
- | [string, string][]
45
- | undefined = undefined
48
+ const cachedAddrs: { [key: string]: AddrList } = {} // TODO: use LRU
49
+ let cachedSameNamedPrefectureCityRegexPatterns: [string, string][] | undefined =
50
+ undefined
46
51
 
47
52
  export const getPrefectures = async () => {
48
53
  if (typeof cachedPrefectures !== 'undefined') {
49
54
  return cachedPrefectures
50
55
  }
51
56
 
52
- const prefsResp = await __internals.fetch('.json') // ja.json
57
+ const prefsResp = await __internals.fetch('.json', { level: 1 }) // ja.json
53
58
  const data = (await prefsResp.json()) as PrefectureList
54
59
  return cachePrefectures(data)
55
60
  }
@@ -104,6 +109,7 @@ export const getTowns = async (pref: string, city: string) => {
104
109
 
105
110
  const townsResp = await __internals.fetch(
106
111
  ['', encodeURI(pref), encodeURI(city) + '.json'].join('/'),
112
+ { level: 3, pref, city },
107
113
  )
108
114
  const towns = (await townsResp.json()) as TownList
109
115
  return (cachedTowns[cacheKey] = towns)
@@ -114,7 +120,13 @@ export const getGaikuList = async (
114
120
  city: string,
115
121
  town: string,
116
122
  ) => {
117
- const cacheKey = `${pref}-${city}-${town}`
123
+ if (currentConfig.interfaceVersion > 1) {
124
+ throw new Error(
125
+ `Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 1.`,
126
+ )
127
+ }
128
+
129
+ const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
118
130
  const cache = cachedGaikuListItem[cacheKey]
119
131
  if (typeof cache !== 'undefined') {
120
132
  return cache
@@ -136,7 +148,13 @@ export const getResidentials = async (
136
148
  city: string,
137
149
  town: string,
138
150
  ) => {
139
- const cacheKey = `${pref}-${city}-${town}`
151
+ if (currentConfig.interfaceVersion > 1) {
152
+ throw new Error(
153
+ `Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 1.`,
154
+ )
155
+ }
156
+
157
+ const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
140
158
  const cache = cachedResidentials[cacheKey]
141
159
  if (typeof cache !== 'undefined') {
142
160
  return cache
@@ -166,6 +184,34 @@ export const getResidentials = async (
166
184
  return (cachedResidentials[cacheKey] = residentials)
167
185
  }
168
186
 
187
+ export const getAddrs = async (pref: string, city: string, town: string) => {
188
+ if (currentConfig.interfaceVersion < 2) {
189
+ throw new Error(
190
+ `Invalid config.interfaceVersion: ${currentConfig.interfaceVersion}'}. Please set config.interfaceVersion to 2 or higher`,
191
+ )
192
+ }
193
+
194
+ const cacheKey = `${pref}-${city}-${town}-v${currentConfig.interfaceVersion}`
195
+ const cache = cachedAddrs[cacheKey]
196
+ if (typeof cache !== 'undefined') {
197
+ return cache
198
+ }
199
+
200
+ const addrsResp = await __internals.fetch(
201
+ ['', encodeURI(pref), encodeURI(city), encodeURI(town) + '.json'].join('/'),
202
+ { level: 8, pref, city, town },
203
+ )
204
+ let addrs: AddrList
205
+ try {
206
+ addrs = (await addrsResp.json()) as AddrList
207
+ } catch {
208
+ addrs = []
209
+ }
210
+
211
+ addrs.sort((res1, res2) => res1.addr.length - res2.addr.length)
212
+ return (cachedAddrs[cacheKey] = addrs)
213
+ }
214
+
169
215
  // 十六町 のように漢数字と町が連結しているか
170
216
  const isKanjiNumberFollewedByCho = (targetTownName: string) => {
171
217
  const xCho = targetTownName.match(/.町/g)
@@ -3,8 +3,12 @@ import { kanji2number, findKanjiNumbers } from '@geolonia/japanese-numeral'
3
3
  export const kan2num = (string: string) => {
4
4
  const kanjiNumbers = findKanjiNumbers(string)
5
5
  for (let i = 0; i < kanjiNumbers.length; i++) {
6
- // @ts-ignore
7
- string = string.replace(kanjiNumbers[i], kanji2number(kanjiNumbers[i]))
6
+ try {
7
+ // @ts-ignore
8
+ string = string.replace(kanjiNumbers[i], kanji2number(kanjiNumbers[i]));
9
+ } catch (error) {
10
+ // ignore
11
+ }
8
12
  }
9
13
 
10
14
  return string
@@ -1,26 +1,55 @@
1
1
  import * as Normalize from './normalize'
2
2
  import { promises as fs } from 'fs'
3
3
  import unfetch from 'isomorphic-unfetch'
4
+ import { TransformRequestQuery } from './normalize'
4
5
 
5
- const fetchOrReadFile = async (
6
- input: string,
7
- ): Promise<Response | { json: () => Promise<unknown> }> => {
8
- const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`)
9
- if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
10
- if (Normalize.config.geoloniaApiKey) {
11
- fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}`
12
- }
13
- return unfetch(fileURL.toString())
14
- } else if (fileURL.protocol === 'file:') {
15
- const filePath = process.platform === 'win32' ? decodeURI(fileURL.pathname).substr(1) : decodeURI(fileURL.pathname);
6
+ export const requestHandlers = {
7
+ file: (fileURL: URL) => {
8
+ const filePath =
9
+ process.platform === 'win32'
10
+ ? decodeURI(fileURL.pathname).substr(1)
11
+ : decodeURI(fileURL.pathname)
16
12
  return {
17
13
  json: async () => {
18
14
  const contents = await fs.readFile(filePath)
19
15
  return JSON.parse(contents.toString('utf-8'))
20
16
  },
21
17
  }
18
+ },
19
+ http: (fileURL: URL) => {
20
+ if (Normalize.config.geoloniaApiKey) {
21
+ fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}`
22
+ }
23
+ return unfetch(fileURL.toString())
24
+ },
25
+ }
26
+
27
+ /**
28
+ * 正規化のためのデータを取得する
29
+ * @param input - Path part like '東京都/文京区.json'
30
+ * @param requestOptions - input を構造化したデータ
31
+ */
32
+ const fetchOrReadFile = async (
33
+ input: string,
34
+ requestOptions: TransformRequestQuery = { level: -1 },
35
+ ): Promise<Response | { json: () => Promise<unknown> }> => {
36
+ const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`)
37
+ if (Normalize.config.transformRequest && requestOptions.level !== -1) {
38
+ const result = await Normalize.config.transformRequest(
39
+ fileURL,
40
+ requestOptions,
41
+ )
42
+ return {
43
+ json: async () => Promise.resolve(result),
44
+ }
22
45
  } else {
23
- throw new Error(`Unknown URL schema: ${fileURL.protocol}`)
46
+ if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') {
47
+ return requestHandlers.http(fileURL)
48
+ } else if (fileURL.protocol === 'file:') {
49
+ return requestHandlers.file(fileURL)
50
+ } else {
51
+ throw new Error(`Unknown URL schema: ${fileURL.protocol}`)
52
+ }
24
53
  }
25
54
  }
26
55