diff --git a/Cargo.toml b/Cargo.toml index 51b2d99c..491ceea6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.12" +version = "0.1.13" edition = "2021" description = "A Rust Library to parse japanese addresses." repository = "https://github.com/YuukiToriyama/japanese-address-parser" diff --git a/core/src/parser/adapter/orthographical_variant_adapter.rs b/core/src/parser/adapter/orthographical_variant_adapter.rs index 6cd327aa..5fbb7276 100644 --- a/core/src/parser/adapter/orthographical_variant_adapter.rs +++ b/core/src/parser/adapter/orthographical_variant_adapter.rs @@ -24,7 +24,6 @@ pub trait OrthographicalVariants { const 恵: Variant; const 穂: Variant; const 梼: Variant; - const 葛: Variant; const 蛍: Variant; const 與: Variant; const 瀧: Variant; @@ -53,7 +52,6 @@ impl OrthographicalVariants for Variant { const 恵: Variant = &["恵", "惠"]; const 穂: Variant = &["穂", "穗"]; const 梼: Variant = &["梼", "檮"]; - const 葛: Variant = &["葛󠄀", "葛"]; const 蛍: Variant = &["蛍", "螢"]; const 與: Variant = &["與", "与"]; const 瀧: Variant = &["瀧", "滝"]; diff --git a/core/src/tokenizer/read_city.rs b/core/src/tokenizer/read_city.rs index 4c865f14..18e80ecf 100644 --- a/core/src/tokenizer/read_city.rs +++ b/core/src/tokenizer/read_city.rs @@ -39,14 +39,10 @@ impl Tokenizer { } "東京都" => { variant_list.push(Variant::檜); - variant_list.push(Variant::葛); } "兵庫県" => { variant_list.push(Variant::塚); } - "奈良県" => { - variant_list.push(Variant::葛); - } "高知県" => { variant_list.push(Variant::梼); } diff --git a/core/src/tokenizer/read_prefecture.rs b/core/src/tokenizer/read_prefecture.rs index b793b9a4..d811f320 100644 --- a/core/src/tokenizer/read_prefecture.rs +++ b/core/src/tokenizer/read_prefecture.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use crate::tokenizer::{End, Init, PrefectureNameFound, Tokenizer}; +use crate::util::extension::StrExt; const PREFECTURE_NAME_LIST: [&str; 47] = [ "北海道", @@ -59,21 +60,21 @@ impl Tokenizer { prefecture_name: None, city_name: None, town_name: None, - rest: input.to_string(), + rest: input.strip_variation_selectors(), _state: PhantomData, } } pub(crate) fn read_prefecture(&self) -> Result, Tokenizer> { for prefecture_name in PREFECTURE_NAME_LIST { - if self.input.starts_with(prefecture_name) { + if self.rest.starts_with(prefecture_name) { return Ok(Tokenizer { input: self.input.clone(), prefecture_name: Some(prefecture_name.to_string()), city_name: None, town_name: None, rest: self - .input + .rest .chars() .skip(prefecture_name.chars().count()) .collect::(), @@ -106,6 +107,16 @@ mod tests { assert_eq!(tokenizer.rest, "東京都港区芝公園4丁目2-8"); } + #[test] + fn new_異字体セレクタ除去() { + let tokenizer = Tokenizer::new("東京都葛\u{E0100}飾区立石5-13-1"); + assert_eq!(tokenizer.input, "東京都葛\u{E0100}飾区立石5-13-1"); + assert_eq!(tokenizer.prefecture_name, None); + assert_eq!(tokenizer.city_name, None); + assert_eq!(tokenizer.town_name, None); + assert_eq!(tokenizer.rest, "東京都葛飾区立石5-13-1") + } + #[test] fn read_prefecture_成功() { let tokenizer = Tokenizer::new("東京都港区芝公園4丁目2-8"); diff --git a/core/src/util.rs b/core/src/util.rs index 09d7245b..62ca0efa 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -1,3 +1,4 @@ pub mod converter; +pub(crate) mod extension; pub mod sequence_matcher; mod trimmer; diff --git a/core/src/util/extension.rs b/core/src/util/extension.rs new file mode 100644 index 00000000..f7a81cf3 --- /dev/null +++ b/core/src/util/extension.rs @@ -0,0 +1,62 @@ +pub(crate) trait CharExt { + fn is_variation_selector(&self) -> bool; +} + +impl CharExt for char { + /// 異字体セレクタかどうかを判別します + fn is_variation_selector(&self) -> bool { + matches!(self, '\u{FE00}'..='\u{FE0F}' | '\u{E0100}'..='\u{E01EF}') + } +} + +pub(crate) trait StrExt { + fn strip_variation_selectors(&self) -> String; +} + +impl StrExt for str { + /// 文字列から異字体セレクタを取り除きます + fn strip_variation_selectors(&self) -> String { + self.chars() + .filter(|c| !c.is_variation_selector()) + .collect() + } +} + +#[cfg(test)] +mod tests { + use crate::util::extension::{CharExt, StrExt}; + + #[test] + fn is_variation_selector() { + assert_eq!('あ'.is_variation_selector(), false); + assert_eq!('亜'.is_variation_selector(), false); + + assert_eq!('\u{FDFF}'.is_variation_selector(), false); + assert_eq!('\u{FE00}'.is_variation_selector(), true); + + assert_eq!('\u{FE0F}'.is_variation_selector(), true); + assert_eq!('\u{FE10}'.is_variation_selector(), false); + + assert_eq!('\u{E00FF}'.is_variation_selector(), false); + assert_eq!('\u{E0100}'.is_variation_selector(), true); + + assert_eq!('\u{E01EF}'.is_variation_selector(), true); + assert_eq!('\u{E01F0}'.is_variation_selector(), false); + } + + #[test] + fn strip_variation_selectors_逢坂() { + let normal = "\u{9022}\u{5742}"; // 逢坂 + let variant = "\u{9022}\u{E0101}\u{5742}"; // 逢󠄁坂 + assert_ne!(normal, variant); + assert_eq!(normal, variant.strip_variation_selectors()); + } + + #[test] + fn strip_variation_selectors_茨城() { + let normal = "\u{8328}\u{57CE}"; + let variant = "\u{8328}\u{E0100}\u{57CE}"; + assert_ne!(normal, variant); + assert_eq!(normal, variant.strip_variation_selectors()); + } +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index cf17cf6c..a19bd227 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -43,3 +43,8 @@ async fn 郡が省略されている場合への対応テスト() { async fn 郡名と町名が一致している場合() { run_data_driven_tests("./test_data/郡名と町名が一致している場合.csv").await } + +#[tokio::test] +async fn 異字体セレクタを含む場合への対応() { + run_data_driven_tests("./test_data/異字体セレクタを含む場合への対応.csv").await +} diff --git "a/tests/test_data/\345\270\202\345\214\272\347\224\272\346\235\221\345\220\215\343\203\254\343\203\231\343\203\253\343\201\247\343\201\256\350\241\250\350\250\230\343\202\206\343\202\214.csv" "b/tests/test_data/\345\270\202\345\214\272\347\224\272\346\235\221\345\220\215\343\203\254\343\203\231\343\203\253\343\201\247\343\201\256\350\241\250\350\250\230\343\202\206\343\202\214.csv" index 12d1755b..5b526087 100644 --- "a/tests/test_data/\345\270\202\345\214\272\347\224\272\346\235\221\345\220\215\343\203\254\343\203\231\343\203\253\343\201\247\343\201\256\350\241\250\350\250\230\343\202\206\343\202\214.csv" +++ "b/tests/test_data/\345\270\202\345\214\272\347\224\272\346\235\221\345\220\215\343\203\254\343\203\231\343\203\253\343\201\247\343\201\256\350\241\250\350\250\230\343\202\206\343\202\214.csv" @@ -5,14 +5,8 @@ address,prefecture,city,town,rest # 茨城県 茨城県鹿嶋市大字平井1187-1,茨城県,鹿嶋市,大字平井,1187-1 茨城県鹿島市大字平井1187-1,茨城県,鹿嶋市,大字平井,1187-1 -# 東京都 -東京都葛飾区立石5-13-1,東京都,葛飾区,立石五丁目,13-1 -東京都葛󠄀飾区立石5-13-1,東京都,葛飾区,立石五丁目,13-1 # 兵庫県 兵庫県宝塚市売布東の町8-19,兵庫県,宝塚市,売布東の町,8-19 兵庫県宝塚市売布東の町8-19,兵庫県,宝塚市,売布東の町,8-19 兵庫県宝塚市武庫川町1-1,兵庫県,宝塚市,武庫川町,1-1 兵庫県宝塚市武庫川町1-1,兵庫県,宝塚市,武庫川町,1-1 -# 奈良県 -奈良県葛󠄀城市柿本166番地,奈良県,葛城市,柿本,166番地 -奈良県葛城市柿本166番地,奈良県,葛城市,柿本,166番地 diff --git "a/tests/test_data/\347\225\260\345\255\227\344\275\223\343\202\273\343\203\254\343\202\257\343\202\277\343\202\222\345\220\253\343\202\200\345\240\264\345\220\210\343\201\270\343\201\256\345\257\276\345\277\234.csv" "b/tests/test_data/\347\225\260\345\255\227\344\275\223\343\202\273\343\203\254\343\202\257\343\202\277\343\202\222\345\220\253\343\202\200\345\240\264\345\220\210\343\201\270\343\201\256\345\257\276\345\277\234.csv" new file mode 100644 index 00000000..950cca88 --- /dev/null +++ "b/tests/test_data/\347\225\260\345\255\227\344\275\223\343\202\273\343\203\254\343\202\257\343\202\277\343\202\222\345\220\253\343\202\200\345\240\264\345\220\210\343\201\270\343\201\256\345\257\276\345\277\234.csv" @@ -0,0 +1,7 @@ +address,prefecture,city,town,rest +東京都葛飾区立石5-13-1,東京都,葛飾区,立石五丁目,13-1 +東京都葛󠄀飾区立石5-13-1,東京都,葛飾区,立石五丁目,13-1 +奈良県葛城市柿本166番地,奈良県,葛城市,柿本,166番地 +奈良県葛󠄀城市柿本166番地,奈良県,葛城市,柿本,166番地 +鹿児島県薩摩川内市上甑町中甑250-1,鹿児島県,薩摩川内市,上甑町中甑,250-1 +鹿児島県薩摩川内市上甑󠄀町中甑󠄀250-1,鹿児島県,薩摩川内市,上甑町中甑,250-1