├── src ├── bnf_approach │ └── mod.rs ├── regex_approach │ ├── mod.rs │ ├── condition.rs │ ├── layered_query.rs │ └── query.rs └── lib.rs ├── rustfmt.toml ├── fmt_check_test.sh ├── .gitignore ├── .github └── workflows │ └── build_and_test.yml ├── Cargo.toml └── README.md /src/bnf_approach/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | fn_args_layout = "Compressed" 3 | -------------------------------------------------------------------------------- /fmt_check_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # cargo fmt && RUSTFLAGS=-Awarnings cargo check && RUSTFLAGS=-Awarnings cargo test 4 | cargo fmt && cargo check && cargo test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .idea -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: build_and_test 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /src/regex_approach/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::regex_approach::query::Query; 2 | use regex::Match; 3 | 4 | mod condition; 5 | pub(crate) mod layered_query; 6 | pub(crate) mod query; 7 | 8 | pub(crate) fn regex_match_not_blank_query(regex_match: Option) -> Option { 9 | regex_match 10 | .map(|m| Query::new(m.as_str().into())) 11 | .filter(|q| q.is_not_blank()) 12 | } 13 | 14 | pub(crate) fn regex_match_number Option, R>( 15 | regex_match: Option, call_back: F, 16 | ) -> Option { 17 | regex_match 18 | .map(|m| m.as_str().parse::()) 19 | .map(|index| index.map(|i| call_back(i)).unwrap_or(None)) 20 | .flatten() 21 | } 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "search-query-parser" 3 | version = "0.1.4" 4 | edition = "2021" 5 | authors = ["dimmy82 "] 6 | description = "parse complex search query into layered search conditions, so it will be easy to construct Elasticsearch query DSL or something else." 7 | homepage = "https://github.com/dimmy82/search-query-parser" 8 | repository = "https://github.com/dimmy82/search-query-parser" 9 | documentation = "https://docs.rs/search-query-parser" 10 | readme = "README.md" 11 | license = "MIT" 12 | 13 | [lib] 14 | name = "search_query_parser" 15 | path = "src/lib.rs" 16 | test = true 17 | doctest = false 18 | edition = "2021" 19 | crate-type = ["lib"] 20 | 21 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 22 | 23 | [dependencies] 24 | regex = "1.6.0" 25 | eyre = "0.6.8" 26 | serde = { version = "1.0", features = ["derive"] } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # search-query-parser 2 | 3 | [![crates.io](https://img.shields.io/crates/v/search-query-parser.svg)](https://crates.io/crates/search-query-parser) 4 | [![docs.rs](https://docs.rs/search-query-parser/badge.svg)](https://docs.rs/search-query-parser) 5 | [![build](https://github.com/dimmy82/search-query-parser/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/dimmy82/search-query-parser/actions) 6 | 7 | ## what is this library for 8 | 9 | search-query-parser is made to parse complex search query into layered search conditions, so it will be easy to construct Elasticsearch query DSL or something else. 10 | 11 | the complex search query like this: ↓↓↓ 12 | 13 | `(word1 and -word2) or (("phrase word 1" or -"phrase word 2") and -(" a long phrase word " or word3))` 14 | 15 | will be parsed into layered search conditions like this: ↓↓↓ 16 | 17 | ```Rust 18 | Condition::Operator( 19 | Operator::Or, 20 | vec![ 21 | Condition::Operator( 22 | Operator::And, 23 | vec![ 24 | Condition::Keyword("word1".into()), 25 | Condition::Not(Box::new(Condition::Keyword("word2".into()))), 26 | ] 27 | ), 28 | Condition::Operator( 29 | Operator::And, 30 | vec![ 31 | Condition::Operator( 32 | Operator::Or, 33 | vec![ 34 | Condition::PhraseKeyword("phrase word 1".into()), 35 | Condition::Not(Box::new(Condition::PhraseKeyword( 36 | "phrase word 2".into() 37 | ))) 38 | ] 39 | ), 40 | Condition::Not(Box::new(Condition::Operator( 41 | Operator::Or, 42 | vec![ 43 | Condition::PhraseKeyword(" a long phrase word ".into()), 44 | Condition::Keyword("word3".into()) 45 | ] 46 | ))) 47 | ] 48 | ), 49 | ] 50 | ) 51 | ``` 52 | 53 | the conditions are constructed by the `enum Condition` and `enum Operator`. 54 | 55 | ```Rust 56 | #[derive(Debug, Clone, Eq, PartialEq)] 57 | pub enum Condition { 58 | None, 59 | Keyword(String), 60 | PhraseKeyword(String), 61 | Not(Box), 62 | Operator(Operator, Vec), 63 | } 64 | 65 | #[derive(Debug, Clone, Eq, PartialEq)] 66 | pub enum Operator { 67 | And, 68 | Or, 69 | } 70 | ``` 71 | 72 | ## usage 73 | 74 | ### 1. for Rust project 75 | 76 | ```toml 77 | [dependencies] 78 | search-query-parser = "0.1.4" 79 | ``` 80 | 81 | ```Rust 82 | use search_query_parser::parse_query_to_condition; 83 | 84 | let condition = parse_query_to_condition("any query string you like")?; 85 | ``` 86 | 87 | ### 2. for REST Api 88 | 89 | [refer to search-query-parser-api repository](https://github.com/dimmy82/search-query-parser-api) 90 | 91 | ### 3. for JVM language via JNI 92 | 93 | [refer to search-query-parser-cdylib repository](https://github.com/dimmy82/search-query-parser-cdylib) 94 | 95 | ## parse rules 96 | 97 | ### 1. space {\u0020} or full width space {\u3000} are identified as `AND` operator 98 | 99 | ```Rust 100 | fn test_keywords_concat_with_spaces() { 101 | let actual = parse_query_to_condition("word1 word2").unwrap(); 102 | assert_eq!( 103 | actual, 104 | Condition::Operator( 105 | Operator::And, 106 | vec![ 107 | Condition::Keyword("word1".into()), 108 | Condition::Keyword("word2".into()) 109 | ] 110 | ) 111 | ) 112 | } 113 | ``` 114 | 115 | ### 2. `AND` operator has higher priority than `OR` operator 116 | 117 | ```Rust 118 | fn test_keywords_concat_with_and_or() { 119 | let actual = 120 | parse_query_to_condition("word1 OR word2 AND word3").unwrap(); 121 | assert_eq!( 122 | actual, 123 | Condition::Operator( 124 | Operator::Or, 125 | vec![ 126 | Condition::Keyword("word1".into()), 127 | Condition::Operator( 128 | Operator::And, 129 | vec![ 130 | Condition::Keyword("word2".into()), 131 | Condition::Keyword("word3".into()), 132 | ] 133 | ) 134 | ] 135 | ) 136 | ) 137 | } 138 | ``` 139 | 140 | ### 3. conditions in brackets have higher priority 141 | 142 | ```Rust 143 | fn test_brackets() { 144 | let actual = 145 | parse_query_to_condition("word1 AND (word2 OR word3)") 146 | .unwrap(); 147 | assert_eq!( 148 | actual, 149 | Condition::Operator( 150 | Operator::And, 151 | vec![ 152 | Condition::Keyword("word1".into()), 153 | Condition::Operator( 154 | Operator::Or, 155 | vec![ 156 | Condition::Keyword("word2".into()), 157 | Condition::Keyword("word3".into()), 158 | ] 159 | ) 160 | ] 161 | ) 162 | ) 163 | } 164 | ``` 165 | 166 | ### 4. double quote will be parsed for phrase keyword 167 | 168 | ```Rust 169 | fn test_double_quote() { 170 | let actual = parse_query_to_condition( 171 | "\"word1 AND (word2 OR word3)\" word4", 172 | ) 173 | .unwrap(); 174 | assert_eq!( 175 | actual, 176 | Condition::Operator( 177 | Operator::And, 178 | vec![ 179 | Condition::PhraseKeyword( 180 | "word1 AND (word2 OR word3)".into() 181 | ), 182 | Condition::Keyword("word4".into()), 183 | ] 184 | ) 185 | ) 186 | } 187 | ``` 188 | 189 | ### 5. minus(hyphen) will be parsed for negative condition 190 | ※ it can be used before keyword, phrase keyword or brackets 191 | 192 | ```Rust 193 | fn test_minus() { 194 | let actual = parse_query_to_condition( 195 | "-word1 -\"word2\" -(word3 OR word4)", 196 | ) 197 | .unwrap(); 198 | assert_eq!( 199 | actual, 200 | Condition::Operator( 201 | Operator::And, 202 | vec![ 203 | Condition::Not(Box::new(Condition::Keyword("word1".into()))), 204 | Condition::Not(Box::new(Condition::PhraseKeyword("word2".into()))), 205 | Condition::Not(Box::new(Condition::Operator( 206 | Operator::Or, 207 | vec![ 208 | Condition::Keyword("word3".into()), 209 | Condition::Keyword("word4".into()) 210 | ] 211 | ))), 212 | ] 213 | ) 214 | ) 215 | } 216 | ``` 217 | 218 | ### 6. correcting incorrect search query 219 | 1. empty brackets 220 | ```Rust 221 | fn test_empty_brackets() { 222 | let actual = parse_query_to_condition("A AND () AND B").unwrap(); 223 | assert_eq!( 224 | actual, 225 | Condition::Operator( 226 | Operator::And, 227 | vec![ 228 | Condition::Keyword("A".into()), 229 | Condition::Keyword("B".into()), 230 | ] 231 | ) 232 | ) 233 | } 234 | ``` 235 | 236 | 2. reversed brackets 237 | ```Rust 238 | fn test_reverse_brackets() { 239 | let actual = parse_query_to_condition("A OR B) AND (C OR D").unwrap(); 240 | assert_eq!( 241 | actual, 242 | Condition::Operator( 243 | Operator::Or, 244 | vec![ 245 | Condition::Keyword("A".into()), 246 | Condition::Operator( 247 | Operator::And, 248 | vec![ 249 | Condition::Keyword("B".into()), 250 | Condition::Keyword("C".into()), 251 | ] 252 | ), 253 | Condition::Keyword("D".into()), 254 | ] 255 | ) 256 | ) 257 | } 258 | ``` 259 | 260 | 3. wrong number of brackets 261 | ```Rust 262 | fn test_missing_brackets() { 263 | let actual = parse_query_to_condition("(A OR B) AND (C").unwrap(); 264 | assert_eq!( 265 | actual, 266 | Condition::Operator( 267 | Operator::And, 268 | vec![ 269 | Condition::Operator( 270 | Operator::Or, 271 | vec![ 272 | Condition::Keyword("A".into()), 273 | Condition::Keyword("B".into()), 274 | ] 275 | ), 276 | Condition::Keyword("C".into()), 277 | ] 278 | ) 279 | ) 280 | } 281 | ``` 282 | 283 | 4. empty phrase keyword 284 | ```Rust 285 | fn test_empty_phrase_keywords() { 286 | let actual = parse_query_to_condition("A AND \"\" AND B").unwrap(); 287 | assert_eq!( 288 | actual, 289 | Condition::Operator( 290 | Operator::And, 291 | vec![ 292 | Condition::Keyword("A".into()), 293 | Condition::Keyword("B".into()), 294 | ] 295 | ) 296 | ) 297 | } 298 | ``` 299 | 300 | 5. wrong number or double quote 301 | ```Rust 302 | fn test_invalid_double_quote() { 303 | let actual = parse_query_to_condition("\"A\" OR \"B OR C").unwrap(); 304 | assert_eq!( 305 | actual, 306 | Condition::Operator( 307 | Operator::Or, 308 | vec![ 309 | Condition::PhraseKeyword("A".into()), 310 | Condition::Keyword("B".into()), 311 | Condition::Keyword("C".into()), 312 | ] 313 | ) 314 | ) 315 | } 316 | ``` 317 | 318 | 6. and or are next to each other 319 | ```Rust 320 | fn test_invalid_and_or() { 321 | let actual = parse_query_to_condition("A AND OR B").unwrap(); 322 | assert_eq!( 323 | actual, 324 | Condition::Operator( 325 | Operator::Or, 326 | vec![ 327 | Condition::Keyword("A".into()), 328 | Condition::Keyword("B".into()), 329 | ] 330 | ) 331 | ) 332 | } 333 | ``` 334 | 335 | ### 7. search query optimization 336 | ```Rust 337 | fn test_unnecessary_nest_brackets() { 338 | let actual = parse_query_to_condition("(A OR (B OR C)) AND D").unwrap(); 339 | assert_eq!( 340 | actual, 341 | Condition::Operator( 342 | Operator::And, 343 | vec![ 344 | Condition::Operator( 345 | Operator::Or, 346 | vec![ 347 | Condition::Keyword("A".into()), 348 | Condition::Keyword("B".into()), 349 | Condition::Keyword("C".into()), 350 | ] 351 | ), 352 | Condition::Keyword("D".into()), 353 | ] 354 | ) 355 | ) 356 | } 357 | ``` 358 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod bnf_approach; 2 | mod regex_approach; 3 | 4 | use crate::regex_approach::layered_query::LayeredQueries; 5 | use crate::regex_approach::query::Query; 6 | use eyre::Result; 7 | use serde::Serialize; 8 | 9 | pub fn parse_query_to_condition(query: &str) -> Result { 10 | LayeredQueries::parse(Query::new(query.into()))?.to_condition() 11 | } 12 | 13 | #[derive(Debug, Clone, Eq, PartialEq, Serialize)] 14 | pub enum Condition { 15 | None, 16 | Keyword(String), 17 | PhraseKeyword(String), 18 | Not(Box), 19 | Operator(Operator, Vec), 20 | } 21 | 22 | #[derive(Debug, Clone, PartialEq, Serialize)] 23 | pub enum ConditionOnTarget { 24 | None, 25 | Keyword { 26 | condition: String, 27 | target: Option, 28 | }, 29 | PhraseKeyword { 30 | condition: String, 31 | target: Option, 32 | }, 33 | Not { 34 | condition: Box, 35 | target: Option, 36 | }, 37 | Operator { 38 | operator: Operator, 39 | conditions: Vec, 40 | target: Option, 41 | }, 42 | } 43 | 44 | #[derive(Debug, Clone, PartialEq, Serialize)] 45 | pub struct Target { 46 | name: String, 47 | weight: Option, 48 | } 49 | 50 | #[derive(Debug, Clone, Eq, PartialEq, Serialize)] 51 | pub enum Operator { 52 | And, 53 | Or, 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | use crate::{Condition, Operator}; 60 | 61 | mod normal_query { 62 | use super::*; 63 | 64 | #[test] 65 | fn test_keywords_concat_with_spaces() { 66 | let actual = parse_query_to_condition("word1 word2").unwrap(); 67 | assert_eq!( 68 | actual, 69 | Condition::Operator( 70 | Operator::And, 71 | vec![ 72 | Condition::Keyword("word1".into()), 73 | Condition::Keyword("word2".into()) 74 | ] 75 | ) 76 | ) 77 | } 78 | 79 | #[test] 80 | fn test_keywords_concat_with_and_or() { 81 | let actual = parse_query_to_condition("word1 OR word2 AND word3").unwrap(); 82 | assert_eq!( 83 | actual, 84 | Condition::Operator( 85 | Operator::Or, 86 | vec![ 87 | Condition::Keyword("word1".into()), 88 | Condition::Operator( 89 | Operator::And, 90 | vec![ 91 | Condition::Keyword("word2".into()), 92 | Condition::Keyword("word3".into()), 93 | ] 94 | ) 95 | ] 96 | ) 97 | ) 98 | } 99 | 100 | #[test] 101 | fn test_brackets() { 102 | let actual = parse_query_to_condition("word1 AND (word2 OR word3)").unwrap(); 103 | assert_eq!( 104 | actual, 105 | Condition::Operator( 106 | Operator::And, 107 | vec![ 108 | Condition::Keyword("word1".into()), 109 | Condition::Operator( 110 | Operator::Or, 111 | vec![ 112 | Condition::Keyword("word2".into()), 113 | Condition::Keyword("word3".into()), 114 | ] 115 | ) 116 | ] 117 | ) 118 | ) 119 | } 120 | 121 | #[test] 122 | fn test_double_quote() { 123 | let actual = parse_query_to_condition("\"word1 AND (word2 OR word3)\" word4").unwrap(); 124 | assert_eq!( 125 | actual, 126 | Condition::Operator( 127 | Operator::And, 128 | vec![ 129 | Condition::PhraseKeyword("word1 AND (word2 OR word3)".into()), 130 | Condition::Keyword("word4".into()), 131 | ] 132 | ) 133 | ) 134 | } 135 | 136 | #[test] 137 | fn test_minus() { 138 | let actual = parse_query_to_condition("-word1 -\"word2\" -(word3 OR word4)").unwrap(); 139 | assert_eq!( 140 | actual, 141 | Condition::Operator( 142 | Operator::And, 143 | vec![ 144 | Condition::Not(Box::new(Condition::Keyword("word1".into()))), 145 | Condition::Not(Box::new(Condition::PhraseKeyword("word2".into()))), 146 | Condition::Not(Box::new(Condition::Operator( 147 | Operator::Or, 148 | vec![ 149 | Condition::Keyword("word3".into()), 150 | Condition::Keyword("word4".into()) 151 | ] 152 | ))), 153 | ] 154 | ) 155 | ) 156 | } 157 | 158 | #[test] 159 | fn test_full_pattern() { 160 | let actual = parse_query_to_condition( 161 | "(word1 and -word2) or ((\"phrase word 1\" or -\"phrase word 2\") and -(\" a long phrase word \" or word3))", 162 | ) 163 | .unwrap(); 164 | assert_eq!( 165 | actual, 166 | Condition::Operator( 167 | Operator::Or, 168 | vec![ 169 | Condition::Operator( 170 | Operator::And, 171 | vec![ 172 | Condition::Keyword("word1".into()), 173 | Condition::Not(Box::new(Condition::Keyword("word2".into()))), 174 | ] 175 | ), 176 | Condition::Operator( 177 | Operator::And, 178 | vec![ 179 | Condition::Operator( 180 | Operator::Or, 181 | vec![ 182 | Condition::PhraseKeyword("phrase word 1".into()), 183 | Condition::Not(Box::new(Condition::PhraseKeyword( 184 | "phrase word 2".into() 185 | ))) 186 | ] 187 | ), 188 | Condition::Not(Box::new(Condition::Operator( 189 | Operator::Or, 190 | vec![ 191 | Condition::PhraseKeyword(" a long phrase word ".into()), 192 | Condition::Keyword("word3".into()) 193 | ] 194 | ))) 195 | ] 196 | ), 197 | ] 198 | ) 199 | ) 200 | } 201 | } 202 | 203 | mod invalid_query { 204 | use super::*; 205 | 206 | #[test] 207 | fn test_empty_brackets() { 208 | let actual = parse_query_to_condition("A AND () AND B").unwrap(); 209 | assert_eq!( 210 | actual, 211 | Condition::Operator( 212 | Operator::And, 213 | vec![ 214 | Condition::Keyword("A".into()), 215 | Condition::Keyword("B".into()), 216 | ] 217 | ) 218 | ) 219 | } 220 | 221 | #[test] 222 | fn test_reverse_brackets() { 223 | let actual = parse_query_to_condition("A OR B) AND (C OR D").unwrap(); 224 | assert_eq!( 225 | actual, 226 | Condition::Operator( 227 | Operator::Or, 228 | vec![ 229 | Condition::Keyword("A".into()), 230 | Condition::Operator( 231 | Operator::And, 232 | vec![ 233 | Condition::Keyword("B".into()), 234 | Condition::Keyword("C".into()), 235 | ] 236 | ), 237 | Condition::Keyword("D".into()), 238 | ] 239 | ) 240 | ) 241 | } 242 | 243 | #[test] 244 | fn test_missing_brackets() { 245 | let actual = parse_query_to_condition("(A OR B) AND (C").unwrap(); 246 | assert_eq!( 247 | actual, 248 | Condition::Operator( 249 | Operator::And, 250 | vec![ 251 | Condition::Operator( 252 | Operator::Or, 253 | vec![ 254 | Condition::Keyword("A".into()), 255 | Condition::Keyword("B".into()), 256 | ] 257 | ), 258 | Condition::Keyword("C".into()), 259 | ] 260 | ) 261 | ) 262 | } 263 | 264 | #[test] 265 | fn test_invalid_nest_brackets() { 266 | let actual = parse_query_to_condition("(((A OR B)) AND C").unwrap(); 267 | assert_eq!( 268 | actual, 269 | Condition::Operator( 270 | Operator::And, 271 | vec![ 272 | Condition::Operator( 273 | Operator::Or, 274 | vec![ 275 | Condition::Keyword("A".into()), 276 | Condition::Keyword("B".into()), 277 | ] 278 | ), 279 | Condition::Keyword("C".into()), 280 | ] 281 | ) 282 | ) 283 | } 284 | 285 | #[test] 286 | fn test_no_keyword_in_brackets() { 287 | let actual = parse_query_to_condition("A AND (\"\" OR \"\") AND B").unwrap(); 288 | assert_eq!( 289 | actual, 290 | Condition::Operator( 291 | Operator::And, 292 | vec![ 293 | Condition::Keyword("A".into()), 294 | Condition::Keyword("B".into()), 295 | ] 296 | ) 297 | ) 298 | } 299 | 300 | #[test] 301 | fn test_empty_phrase_keywords() { 302 | let actual = parse_query_to_condition("A AND \"\" AND B").unwrap(); 303 | assert_eq!( 304 | actual, 305 | Condition::Operator( 306 | Operator::And, 307 | vec![ 308 | Condition::Keyword("A".into()), 309 | Condition::Keyword("B".into()), 310 | ] 311 | ) 312 | ) 313 | } 314 | 315 | #[test] 316 | fn test_invalid_double_quote() { 317 | let actual = parse_query_to_condition("\"A\" OR \"B OR C").unwrap(); 318 | assert_eq!( 319 | actual, 320 | Condition::Operator( 321 | Operator::Or, 322 | vec![ 323 | Condition::PhraseKeyword("A".into()), 324 | Condition::Keyword("B".into()), 325 | Condition::Keyword("C".into()), 326 | ] 327 | ) 328 | ) 329 | } 330 | 331 | #[test] 332 | fn test_invalid_and_or() { 333 | let actual = parse_query_to_condition("A AND OR B").unwrap(); 334 | assert_eq!( 335 | actual, 336 | Condition::Operator( 337 | Operator::Or, 338 | vec![ 339 | Condition::Keyword("A".into()), 340 | Condition::Keyword("B".into()), 341 | ] 342 | ) 343 | ) 344 | } 345 | } 346 | 347 | mod effective_query { 348 | use super::*; 349 | 350 | #[test] 351 | fn test_unnecessary_nest_brackets() { 352 | let actual = parse_query_to_condition("(A OR (B OR C)) AND D").unwrap(); 353 | assert_eq!( 354 | actual, 355 | Condition::Operator( 356 | Operator::And, 357 | vec![ 358 | Condition::Operator( 359 | Operator::Or, 360 | vec![ 361 | Condition::Keyword("A".into()), 362 | Condition::Keyword("B".into()), 363 | Condition::Keyword("C".into()), 364 | ] 365 | ), 366 | Condition::Keyword("D".into()), 367 | ] 368 | ) 369 | ) 370 | } 371 | 372 | #[test] 373 | fn test_concat_brackets_without_space() { 374 | let actual = parse_query_to_condition("A(B OR C)D").unwrap(); 375 | assert_eq!( 376 | actual, 377 | Condition::Operator( 378 | Operator::And, 379 | vec![ 380 | Condition::Keyword("A".into()), 381 | Condition::Operator( 382 | Operator::Or, 383 | vec![ 384 | Condition::Keyword("B".into()), 385 | Condition::Keyword("C".into()), 386 | ] 387 | ), 388 | Condition::Keyword("D".into()), 389 | ] 390 | ) 391 | ) 392 | } 393 | 394 | #[test] 395 | fn test_concat_phrase_keywords_without_space() { 396 | let actual = parse_query_to_condition("A\"B\"C").unwrap(); 397 | assert_eq!( 398 | actual, 399 | Condition::Operator( 400 | Operator::And, 401 | vec![ 402 | Condition::Keyword("A".into()), 403 | Condition::PhraseKeyword("B".into()), 404 | Condition::Keyword("C".into()), 405 | ] 406 | ) 407 | ) 408 | } 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/regex_approach/condition.rs: -------------------------------------------------------------------------------- 1 | use crate::regex_approach::query::Query; 2 | use crate::Condition::Not; 3 | use crate::{Condition, ConditionOnTarget, Target}; 4 | 5 | impl Condition { 6 | pub(crate) fn simplify(self) -> Self { 7 | match self { 8 | Not(condition) => match condition.simplify() { 9 | Condition::None => Condition::None, 10 | Not(condition) => condition.as_ref().clone(), 11 | condition => Not(Box::new(condition)), 12 | }, 13 | Condition::Operator(operator, conditions) => { 14 | let conditions = conditions 15 | .into_iter() 16 | .filter_map(|condition| match condition.simplify() { 17 | Condition::None => Option::None, 18 | condition => Option::Some(condition), 19 | }) 20 | .collect::>(); 21 | match conditions.len() { 22 | 0 => Condition::None, 23 | // when only one child, remove self's operator layer 24 | 1 => conditions 25 | .get(0) 26 | .map(|condition| condition.clone()) 27 | .unwrap_or(Condition::None), 28 | // when child is also a operator, and child's operator is equal to self's operator, remove child's operator layer 29 | _ => Condition::Operator( 30 | operator.clone(), 31 | conditions 32 | .into_iter() 33 | .flat_map(|condition| match &condition { 34 | Condition::Operator(inner_operator, inner_conditions) => { 35 | if &operator == inner_operator { 36 | inner_conditions.clone() 37 | } else { 38 | vec![condition] 39 | } 40 | } 41 | _ => vec![condition], 42 | }) 43 | .collect(), 44 | ), 45 | } 46 | } 47 | Condition::PhraseKeyword(k) => { 48 | if Query::new(k.clone()).is_not_blank() { 49 | Condition::PhraseKeyword(k) 50 | } else { 51 | Condition::None 52 | } 53 | } 54 | Condition::Keyword(k) => { 55 | if Query::new(k.clone()).is_not_blank() { 56 | Condition::Keyword(k) 57 | } else { 58 | Condition::None 59 | } 60 | } 61 | _ => self, 62 | } 63 | } 64 | 65 | pub(crate) fn identify_target(self) -> ConditionOnTarget { 66 | match self { 67 | Condition::None => ConditionOnTarget::None, 68 | Condition::Keyword(keyword) => { 69 | let target_keyword = keyword 70 | .split(&[':', ':']) 71 | .filter(|it| it.is_empty() == false) 72 | .collect::>(); 73 | match target_keyword.len() { 74 | 2 => ConditionOnTarget::Keyword { 75 | condition: target_keyword.get(1).unwrap().to_string(), 76 | target: Some(parse_target(target_keyword.get(0).unwrap().to_string())), 77 | }, 78 | _ => ConditionOnTarget::Keyword { 79 | condition: keyword, 80 | target: None, 81 | }, 82 | } 83 | } 84 | Condition::PhraseKeyword(phrase_keyword) => { 85 | todo!() 86 | } 87 | Not(condition) => { 88 | todo!() 89 | } 90 | Condition::Operator(operator, condition) => { 91 | todo!() 92 | } 93 | } 94 | } 95 | } 96 | 97 | fn parse_target(target_str: String) -> Target { 98 | let target_weight = target_str.split("^").collect::>(); 99 | match target_weight.len() { 100 | 2 => Target { 101 | name: target_weight.get(0).unwrap().to_string(), 102 | weight: target_weight 103 | .get(1) 104 | .unwrap() 105 | .to_string() 106 | .parse::() 107 | .map_or(None, |it| Some(it)), 108 | }, 109 | _ => Target { 110 | name: target_str, 111 | weight: None, 112 | }, 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::*; 119 | 120 | mod test_condition_simplify { 121 | use super::*; 122 | use crate::Operator; 123 | 124 | #[test] 125 | fn test_simplify_none() { 126 | assert_eq!(Condition::None.simplify(), Condition::None) 127 | } 128 | 129 | #[test] 130 | fn test_simplify_keyword() { 131 | assert_eq!( 132 | Condition::Keyword("keyword".into()).simplify(), 133 | Condition::Keyword("keyword".into()) 134 | ) 135 | } 136 | 137 | #[test] 138 | fn test_simplify_empty_keyword() { 139 | assert_eq!(Condition::Keyword("".into()).simplify(), Condition::None) 140 | } 141 | 142 | #[test] 143 | fn test_simplify_blank_keyword() { 144 | assert_eq!( 145 | Condition::Keyword("    ".into()).simplify(), 146 | Condition::None 147 | ) 148 | } 149 | 150 | #[test] 151 | fn test_simplify_phrase_keyword() { 152 | assert_eq!( 153 | Condition::PhraseKeyword("phrase keyword".into()).simplify(), 154 | Condition::PhraseKeyword("phrase keyword".into()) 155 | ) 156 | } 157 | 158 | #[test] 159 | fn test_simplify_empty_phrase_keyword() { 160 | assert_eq!( 161 | Condition::PhraseKeyword("".into()).simplify(), 162 | Condition::None 163 | ) 164 | } 165 | 166 | #[test] 167 | fn test_simplify_blank_phrase_keyword() { 168 | assert_eq!( 169 | Condition::PhraseKeyword("    ".into()).simplify(), 170 | Condition::None 171 | ) 172 | } 173 | 174 | #[test] 175 | fn test_simplify_negative_none() { 176 | assert_eq!( 177 | Condition::Not(Box::new(Condition::None)).simplify(), 178 | Condition::None 179 | ) 180 | } 181 | 182 | #[test] 183 | fn test_simplify_negative_keyword() { 184 | assert_eq!( 185 | Condition::Not(Box::new(Condition::Keyword("keyword".into()))).simplify(), 186 | Condition::Not(Box::new(Condition::Keyword("keyword".into()))) 187 | ) 188 | } 189 | 190 | #[test] 191 | fn test_simplify_negative_empty_keyword() { 192 | assert_eq!( 193 | Condition::Not(Box::new(Condition::Keyword("".into()))).simplify(), 194 | Condition::None 195 | ) 196 | } 197 | 198 | #[test] 199 | fn test_simplify_negative_phrase_keyword() { 200 | assert_eq!( 201 | Condition::Not(Box::new(Condition::PhraseKeyword("phrase keyword".into()))) 202 | .simplify(), 203 | Condition::Not(Box::new(Condition::PhraseKeyword("phrase keyword".into()))) 204 | ) 205 | } 206 | 207 | #[test] 208 | fn test_simplify_negative_empty_phrase_keyword() { 209 | assert_eq!( 210 | Condition::Not(Box::new(Condition::PhraseKeyword("".into()))).simplify(), 211 | Condition::None 212 | ) 213 | } 214 | 215 | #[test] 216 | fn test_simplify_negative_negative() { 217 | assert_eq!( 218 | Condition::Not(Box::new(Condition::Not(Box::new(Condition::Keyword( 219 | "keyword".into() 220 | ))))) 221 | .simplify(), 222 | Condition::Keyword("keyword".into()) 223 | ) 224 | } 225 | 226 | #[test] 227 | fn test_simplify_negative_negative_negative() { 228 | assert_eq!( 229 | Condition::Not(Box::new(Condition::Not(Box::new(Condition::Not( 230 | Box::new(Condition::Keyword("keyword".into())) 231 | ))))) 232 | .simplify(), 233 | Condition::Not(Box::new(Condition::Keyword("keyword".into()))) 234 | ) 235 | } 236 | 237 | #[test] 238 | fn test_simplify_negative_operator_and() { 239 | assert_eq!( 240 | Condition::Not(Box::new(Condition::Operator( 241 | Operator::And, 242 | vec![ 243 | Condition::Keyword("keyword".into()), 244 | Condition::Not(Box::new(Condition::PhraseKeyword("phrase keyword".into()))) 245 | ] 246 | ))) 247 | .simplify(), 248 | Condition::Not(Box::new(Condition::Operator( 249 | Operator::And, 250 | vec![ 251 | Condition::Keyword("keyword".into()), 252 | Condition::Not(Box::new(Condition::PhraseKeyword("phrase keyword".into()))) 253 | ] 254 | ))) 255 | ) 256 | } 257 | 258 | #[test] 259 | fn test_simplify_negative_operator_or() { 260 | assert_eq!( 261 | Condition::Not(Box::new(Condition::Operator( 262 | Operator::Or, 263 | vec![ 264 | Condition::Keyword("keyword".into()), 265 | Condition::Not(Box::new(Condition::PhraseKeyword("phrase keyword".into()))) 266 | ] 267 | ))) 268 | .simplify(), 269 | Condition::Not(Box::new(Condition::Operator( 270 | Operator::Or, 271 | vec![ 272 | Condition::Keyword("keyword".into()), 273 | Condition::Not(Box::new(Condition::PhraseKeyword("phrase keyword".into()))) 274 | ] 275 | ))) 276 | ) 277 | } 278 | 279 | #[test] 280 | fn test_simplify_operator_and_empty() { 281 | assert_eq!( 282 | Condition::Operator(Operator::And, vec![]).simplify(), 283 | Condition::None 284 | ) 285 | } 286 | 287 | #[test] 288 | fn test_simplify_operator_and_with_only_none() { 289 | assert_eq!( 290 | Condition::Operator(Operator::And, vec![Condition::None]).simplify(), 291 | Condition::None 292 | ) 293 | } 294 | 295 | #[test] 296 | fn test_simplify_operator_and_with_only_keyword() { 297 | assert_eq!( 298 | Condition::Operator(Operator::And, vec![Condition::Keyword("keyword".into())]) 299 | .simplify(), 300 | Condition::Keyword("keyword".into()) 301 | ) 302 | } 303 | 304 | #[test] 305 | fn test_simplify_operator_and_with_only_phrase_keyword() { 306 | assert_eq!( 307 | Condition::Operator( 308 | Operator::And, 309 | vec![Condition::PhraseKeyword("phrase keyword".into())] 310 | ) 311 | .simplify(), 312 | Condition::PhraseKeyword("phrase keyword".into()) 313 | ) 314 | } 315 | 316 | #[test] 317 | fn test_simplify_operator_and_with_only_negative() { 318 | assert_eq!( 319 | Condition::Operator( 320 | Operator::And, 321 | vec![Condition::Not(Box::new(Condition::Keyword("not".into())))] 322 | ) 323 | .simplify(), 324 | Condition::Not(Box::new(Condition::Keyword("not".into()))) 325 | ) 326 | } 327 | 328 | #[test] 329 | fn test_simplify_operator_and_with_only_operator() { 330 | assert_eq!( 331 | Condition::Operator( 332 | Operator::And, 333 | vec![Condition::Operator( 334 | Operator::Or, 335 | vec![ 336 | Condition::Keyword("keyword".into()), 337 | Condition::PhraseKeyword("phrase keyword".into()), 338 | ] 339 | )] 340 | ) 341 | .simplify(), 342 | Condition::Operator( 343 | Operator::Or, 344 | vec![ 345 | Condition::Keyword("keyword".into()), 346 | Condition::PhraseKeyword("phrase keyword".into()), 347 | ] 348 | ) 349 | ) 350 | } 351 | 352 | #[test] 353 | fn test_simplify_operator_and_with_only_operator_recursively() { 354 | assert_eq!( 355 | Condition::Operator( 356 | Operator::And, 357 | vec![Condition::Operator( 358 | Operator::Or, 359 | vec![Condition::Keyword("keyword".into()),] 360 | )] 361 | ) 362 | .simplify(), 363 | Condition::Keyword("keyword".into()) 364 | ) 365 | } 366 | 367 | #[test] 368 | fn test_simplify_operator_and_with_all_conditions() { 369 | assert_eq!( 370 | Condition::Operator( 371 | Operator::And, 372 | vec![ 373 | Condition::Keyword("keyword".into()), 374 | Condition::None, 375 | Condition::PhraseKeyword("phrase keyword".into()), 376 | Condition::None, 377 | Condition::Not(Box::new(Condition::Keyword("not".into()))), 378 | Condition::Operator( 379 | Operator::Or, 380 | vec![ 381 | Condition::Keyword("keyword".into()), 382 | Condition::PhraseKeyword("phrase keyword".into()), 383 | ] 384 | ) 385 | ] 386 | ) 387 | .simplify(), 388 | Condition::Operator( 389 | Operator::And, 390 | vec![ 391 | Condition::Keyword("keyword".into()), 392 | Condition::PhraseKeyword("phrase keyword".into()), 393 | Condition::Not(Box::new(Condition::Keyword("not".into()))), 394 | Condition::Operator( 395 | Operator::Or, 396 | vec![ 397 | Condition::Keyword("keyword".into()), 398 | Condition::PhraseKeyword("phrase keyword".into()), 399 | ] 400 | ) 401 | ] 402 | ) 403 | ) 404 | } 405 | 406 | #[test] 407 | fn test_simplify_operator_or_empty() { 408 | assert_eq!( 409 | Condition::Operator(Operator::Or, vec![]).simplify(), 410 | Condition::None 411 | ) 412 | } 413 | 414 | #[test] 415 | fn test_simplify_operator_or_with_only_none() { 416 | assert_eq!( 417 | Condition::Operator(Operator::Or, vec![Condition::None]).simplify(), 418 | Condition::None 419 | ) 420 | } 421 | 422 | #[test] 423 | fn test_simplify_operator_or_with_only_keyword() { 424 | assert_eq!( 425 | Condition::Operator(Operator::Or, vec![Condition::Keyword("keyword".into())]) 426 | .simplify(), 427 | Condition::Keyword("keyword".into()) 428 | ) 429 | } 430 | 431 | #[test] 432 | fn test_simplify_operator_or_with_only_phrase_keyword() { 433 | assert_eq!( 434 | Condition::Operator( 435 | Operator::Or, 436 | vec![Condition::PhraseKeyword("phrase keyword".into())] 437 | ) 438 | .simplify(), 439 | Condition::PhraseKeyword("phrase keyword".into()) 440 | ) 441 | } 442 | 443 | #[test] 444 | fn test_simplify_operator_or_with_only_negative() { 445 | assert_eq!( 446 | Condition::Operator( 447 | Operator::Or, 448 | vec![Condition::Not(Box::new(Condition::Keyword("not".into())))] 449 | ) 450 | .simplify(), 451 | Condition::Not(Box::new(Condition::Keyword("not".into()))) 452 | ) 453 | } 454 | 455 | #[test] 456 | fn test_simplify_operator_or_with_only_operator() { 457 | assert_eq!( 458 | Condition::Operator( 459 | Operator::Or, 460 | vec![Condition::Operator( 461 | Operator::And, 462 | vec![ 463 | Condition::Keyword("keyword".into()), 464 | Condition::PhraseKeyword("phrase keyword".into()), 465 | ] 466 | )] 467 | ) 468 | .simplify(), 469 | Condition::Operator( 470 | Operator::And, 471 | vec![ 472 | Condition::Keyword("keyword".into()), 473 | Condition::PhraseKeyword("phrase keyword".into()), 474 | ] 475 | ) 476 | ) 477 | } 478 | 479 | #[test] 480 | fn test_simplify_operator_or_with_only_operator_recursively() { 481 | assert_eq!( 482 | Condition::Operator( 483 | Operator::Or, 484 | vec![Condition::Operator( 485 | Operator::And, 486 | vec![Condition::Keyword("keyword".into()),] 487 | )] 488 | ) 489 | .simplify(), 490 | Condition::Keyword("keyword".into()), 491 | ) 492 | } 493 | 494 | #[test] 495 | fn test_simplify_remove_same_operator_or_layer() { 496 | assert_eq!( 497 | Condition::Operator( 498 | Operator::Or, 499 | vec![ 500 | Condition::Operator( 501 | Operator::And, 502 | vec![ 503 | Condition::Keyword("keyword1".into()), 504 | Condition::Keyword("keyword2".into()), 505 | ] 506 | ), 507 | Condition::PhraseKeyword("keyword3".into()), 508 | Condition::Operator( 509 | Operator::Or, 510 | vec![ 511 | Condition::Keyword("keyword4".into()), 512 | Condition::Keyword("keyword5".into()), 513 | ] 514 | ), 515 | Condition::Keyword("keyword6".into()), 516 | Condition::Not(Box::new(Condition::Keyword("keyword7".into()))) 517 | ] 518 | ) 519 | .simplify(), 520 | Condition::Operator( 521 | Operator::Or, 522 | vec![ 523 | Condition::Operator( 524 | Operator::And, 525 | vec![ 526 | Condition::Keyword("keyword1".into()), 527 | Condition::Keyword("keyword2".into()), 528 | ] 529 | ), 530 | Condition::PhraseKeyword("keyword3".into()), 531 | Condition::Keyword("keyword4".into()), 532 | Condition::Keyword("keyword5".into()), 533 | Condition::Keyword("keyword6".into()), 534 | Condition::Not(Box::new(Condition::Keyword("keyword7".into()))) 535 | ] 536 | ), 537 | ) 538 | } 539 | 540 | #[test] 541 | fn test_simplify_remove_same_operator_and_layer() { 542 | assert_eq!( 543 | Condition::Operator( 544 | Operator::And, 545 | vec![ 546 | Condition::Operator( 547 | Operator::Or, 548 | vec![ 549 | Condition::Keyword("keyword1".into()), 550 | Condition::Keyword("keyword2".into()), 551 | ] 552 | ), 553 | Condition::PhraseKeyword("keyword3".into()), 554 | Condition::Operator( 555 | Operator::And, 556 | vec![ 557 | Condition::Keyword("keyword4".into()), 558 | Condition::Keyword("keyword5".into()), 559 | ] 560 | ), 561 | Condition::Keyword("keyword6".into()), 562 | Condition::Not(Box::new(Condition::Keyword("keyword7".into()))) 563 | ] 564 | ) 565 | .simplify(), 566 | Condition::Operator( 567 | Operator::And, 568 | vec![ 569 | Condition::Operator( 570 | Operator::Or, 571 | vec![ 572 | Condition::Keyword("keyword1".into()), 573 | Condition::Keyword("keyword2".into()), 574 | ] 575 | ), 576 | Condition::PhraseKeyword("keyword3".into()), 577 | Condition::Keyword("keyword4".into()), 578 | Condition::Keyword("keyword5".into()), 579 | Condition::Keyword("keyword6".into()), 580 | Condition::Not(Box::new(Condition::Keyword("keyword7".into()))) 581 | ] 582 | ), 583 | ) 584 | } 585 | 586 | #[test] 587 | fn test_simplify_operator_or_with_all_conditions() { 588 | assert_eq!( 589 | Condition::Operator( 590 | Operator::Or, 591 | vec![ 592 | Condition::Keyword("keyword".into()), 593 | Condition::None, 594 | Condition::PhraseKeyword("phrase keyword".into()), 595 | Condition::None, 596 | Condition::Not(Box::new(Condition::Keyword("not".into()))), 597 | Condition::None, 598 | Condition::Operator( 599 | Operator::And, 600 | vec![ 601 | Condition::Keyword("keyword 1".into()), 602 | Condition::PhraseKeyword("phrase keyword 1".into()), 603 | ] 604 | ) 605 | ] 606 | ) 607 | .simplify(), 608 | Condition::Operator( 609 | Operator::Or, 610 | vec![ 611 | Condition::Keyword("keyword".into()), 612 | Condition::PhraseKeyword("phrase keyword".into()), 613 | Condition::Not(Box::new(Condition::Keyword("not".into()))), 614 | Condition::Operator( 615 | Operator::And, 616 | vec![ 617 | Condition::Keyword("keyword 1".into()), 618 | Condition::PhraseKeyword("phrase keyword 1".into()), 619 | ] 620 | ) 621 | ] 622 | ) 623 | ) 624 | } 625 | } 626 | 627 | mod test_identify_target { 628 | use crate::{Condition, ConditionOnTarget, Target}; 629 | 630 | #[test] 631 | fn test_identify_target_on_none_condition() { 632 | assert_eq!(Condition::None.identify_target(), ConditionOnTarget::None) 633 | } 634 | 635 | #[test] 636 | fn test_no_identify_target_on_keyword_condition() { 637 | assert_eq!( 638 | Condition::Keyword("hoge".to_string()).identify_target(), 639 | ConditionOnTarget::Keyword { 640 | condition: "hoge".to_string(), 641 | target: None 642 | } 643 | ) 644 | } 645 | 646 | #[test] 647 | fn test_identify_target_on_keyword_condition() { 648 | assert_eq!( 649 | Condition::Keyword("hoge:fuga".to_string()).identify_target(), 650 | ConditionOnTarget::Keyword { 651 | condition: "fuga".to_string(), 652 | target: Some(Target { 653 | name: "hoge".to_string(), 654 | weight: None 655 | }) 656 | } 657 | ) 658 | } 659 | 660 | #[test] 661 | fn test_identify_target_on_keyword_condition_full_width() { 662 | assert_eq!( 663 | Condition::Keyword("hoge:fuga".to_string()).identify_target(), 664 | ConditionOnTarget::Keyword { 665 | condition: "fuga".to_string(), 666 | target: Some(Target { 667 | name: "hoge".to_string(), 668 | weight: None 669 | }) 670 | } 671 | ) 672 | } 673 | 674 | #[test] 675 | fn test_identify_target_with_i32_weight_on_keyword_condition() { 676 | assert_eq!( 677 | Condition::Keyword("hoge^2:fuga".to_string()).identify_target(), 678 | ConditionOnTarget::Keyword { 679 | condition: "fuga".to_string(), 680 | target: Some(Target { 681 | name: "hoge".to_string(), 682 | weight: Some(2.0) 683 | }) 684 | } 685 | ) 686 | } 687 | 688 | #[test] 689 | fn test_identify_target_with_f32_weight_on_keyword_condition() { 690 | assert_eq!( 691 | Condition::Keyword("hoge^0.2:fuga".to_string()).identify_target(), 692 | ConditionOnTarget::Keyword { 693 | condition: "fuga".to_string(), 694 | target: Some(Target { 695 | name: "hoge".to_string(), 696 | weight: Some(0.2) 697 | }) 698 | } 699 | ) 700 | } 701 | 702 | #[test] 703 | fn test_identify_target_with_nan_weight_on_keyword_condition() { 704 | assert_eq!( 705 | Condition::Keyword("hoge^2a:fuga".to_string()).identify_target(), 706 | ConditionOnTarget::Keyword { 707 | condition: "fuga".to_string(), 708 | target: Some(Target { 709 | name: "hoge".to_string(), 710 | weight: None 711 | }) 712 | } 713 | ) 714 | } 715 | 716 | #[test] 717 | fn test_identify_target_on_keyword_condition_invalid() { 718 | assert_eq!( 719 | Condition::Keyword("hoge:".to_string()).identify_target(), 720 | ConditionOnTarget::Keyword { 721 | condition: "hoge:".to_string(), 722 | target: None 723 | } 724 | ); 725 | assert_eq!( 726 | Condition::Keyword(":hoge".to_string()).identify_target(), 727 | ConditionOnTarget::Keyword { 728 | condition: ":hoge".to_string(), 729 | target: None 730 | } 731 | ); 732 | assert_eq!( 733 | Condition::Keyword("hoge:fuga:pico".to_string()).identify_target(), 734 | ConditionOnTarget::Keyword { 735 | condition: "hoge:fuga:pico".to_string(), 736 | target: None 737 | } 738 | ) 739 | } 740 | } 741 | } 742 | -------------------------------------------------------------------------------- /src/regex_approach/layered_query.rs: -------------------------------------------------------------------------------- 1 | use crate::regex_approach::query::Query; 2 | use crate::regex_approach::{regex_match_not_blank_query, regex_match_number}; 3 | use crate::{Condition, Operator}; 4 | use eyre::Result; 5 | use regex::{Captures, Regex}; 6 | 7 | #[derive(Debug, Clone, Eq, PartialEq)] 8 | pub(crate) enum LayeredQuery { 9 | Query(Query), 10 | Bracket(LayeredQueries), 11 | NegativeBracket(LayeredQueries), 12 | } 13 | 14 | #[derive(Debug, Clone, Eq, PartialEq)] 15 | pub(crate) struct LayeredQueries(Vec); 16 | 17 | impl LayeredQueries { 18 | pub(crate) fn parse(query: Query) -> Result { 19 | let (query, negative_phrase_keywords, phrase_keywords) = query 20 | .normalize_double_quotation() 21 | .extract_phrase_keywords()?; 22 | let query = query.normalize_symbols_except_double_quotation(); 23 | let mut bracket_queries = Vec::::new(); 24 | let all_brackets_picked_query = Self::pick_layer_by_bracket(query, &mut bracket_queries)?; 25 | Ok(Self::combine_layered_query( 26 | all_brackets_picked_query, 27 | &bracket_queries, 28 | &negative_phrase_keywords, 29 | &phrase_keywords, 30 | )?) 31 | } 32 | 33 | fn pick_layer_by_bracket(query: Query, bracket_queries: &mut Vec) -> Result { 34 | let regex_bracket = Regex::new(r"\(([^()]*)\)")?; 35 | let innermost_bracket_removed_query = Query::new( 36 | regex_bracket 37 | .replace_all(query.value_ref(), |captures: &Captures| { 38 | match regex_match_not_blank_query(captures.get(1)) { 39 | Some(q) => { 40 | bracket_queries.push(q); 41 | format!("({})", bracket_queries.len()) 42 | } 43 | None => String::from(""), 44 | } 45 | }) 46 | .into(), 47 | ); 48 | match query == innermost_bracket_removed_query { 49 | false => Self::pick_layer_by_bracket(innermost_bracket_removed_query, bracket_queries), 50 | true => Ok(query.remove_bracket()), 51 | } 52 | } 53 | 54 | fn combine_layered_query( 55 | query: Query, bracket_queries: &Vec, negative_phrase_keywords: &Vec, 56 | phrase_keywords: &Vec, 57 | ) -> Result { 58 | let regex_layered_by_bracket = Regex::new(r"([^()]*)((\d+))")?; 59 | let mut layered_queries = Vec::::new(); 60 | let the_last_query_after_all_brackets = regex_layered_by_bracket 61 | .replace_all(query.value_ref(), |captures: &Captures| { 62 | let mut is_negative_bracket = false; 63 | regex_match_not_blank_query(captures.get(1)).map(|mut q| { 64 | if q.value_ref().ends_with("-") { 65 | is_negative_bracket = true; 66 | q = Query::new(String::from(&q.value_ref()[0..q.value_ref().len() - 1])) 67 | } 68 | let _ = q 69 | .combine_phrase_keywords(negative_phrase_keywords, phrase_keywords) 70 | .map(|q| { 71 | if q.is_not_blank() { 72 | layered_queries.push(LayeredQuery::Query(q)) 73 | } 74 | }); 75 | }); 76 | regex_match_number(captures.get(2), |i| { 77 | bracket_queries.get(i - 1).map(|q| { 78 | Self::combine_layered_query( 79 | q.clone(), 80 | bracket_queries, 81 | negative_phrase_keywords, 82 | phrase_keywords, 83 | ) 84 | .map(|lqs| { 85 | layered_queries.push(if is_negative_bracket { 86 | LayeredQuery::NegativeBracket(lqs) 87 | } else { 88 | LayeredQuery::Bracket(lqs) 89 | }) 90 | }) 91 | }) 92 | }); 93 | String::from("") 94 | }) 95 | .to_string(); 96 | let the_last_query = Query::new(the_last_query_after_all_brackets) 97 | .combine_phrase_keywords(negative_phrase_keywords, phrase_keywords)?; 98 | if the_last_query.is_not_blank() { 99 | layered_queries.push(LayeredQuery::Query(the_last_query)) 100 | } 101 | Ok(Self(layered_queries)) 102 | } 103 | 104 | pub(crate) fn to_condition(self) -> Result { 105 | let mut query_string = String::new(); 106 | let mut conditions = Vec::::new(); 107 | 108 | for layered_query in self.0 { 109 | match layered_query { 110 | LayeredQuery::Query(query) => { 111 | let (is_start_with_or, condition, is_end_with_or) = query.to_condition()?; 112 | query_string.push_str( 113 | format!( 114 | " {} {} {} ", 115 | if is_start_with_or { "or" } else { "and" }, 116 | conditions.len(), 117 | if is_end_with_or { "or" } else { "and" } 118 | ) 119 | .as_str(), 120 | ); 121 | conditions.push(condition); 122 | } 123 | LayeredQuery::Bracket(layered_queries) => { 124 | let condition = layered_queries.to_condition()?; 125 | query_string.push_str(format!(" {} ", conditions.len()).as_str()); 126 | conditions.push(condition); 127 | } 128 | LayeredQuery::NegativeBracket(layered_queries) => { 129 | let condition = layered_queries.to_condition()?; 130 | query_string.push_str(format!(" {} ", conditions.len()).as_str()); 131 | conditions.push(Condition::Not(Box::new(condition))); 132 | } 133 | } 134 | } 135 | 136 | let query = Query::new(query_string); 137 | let (_, condition, _) = query.to_condition()?; 138 | let condition = match condition { 139 | Condition::Keyword(index) => Self::get_condition(index, &conditions)?, 140 | Condition::Operator(operator, layer1_conditions) => { 141 | let mut real_layer1_conditions = Vec::::new(); 142 | for condition in layer1_conditions { 143 | real_layer1_conditions.push(match condition { 144 | Condition::Keyword(index) => Self::get_condition(index, &conditions)?, 145 | Condition::Operator(Operator::And, layer2_conditions) => { 146 | let mut real_layer2_conditions = Vec::::new(); 147 | for condition in layer2_conditions { 148 | real_layer2_conditions.push(match condition { 149 | Condition::Keyword(index) => { 150 | Self::get_condition(index, &conditions)? 151 | } 152 | _ => Condition::None, 153 | }) 154 | } 155 | Condition::Operator(Operator::And, real_layer2_conditions) 156 | } 157 | _ => Condition::None, 158 | }) 159 | } 160 | Condition::Operator(operator, real_layer1_conditions) 161 | } 162 | _ => Condition::None, 163 | }; 164 | Ok(condition.simplify()) 165 | } 166 | 167 | fn get_condition(index: String, conditions: &Vec) -> Result { 168 | Ok(conditions 169 | .get(index.parse::()?) 170 | .map(|condition: &Condition| condition.clone()) 171 | .unwrap_or(Condition::None)) 172 | } 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use super::*; 178 | 179 | mod test_parse { 180 | use super::*; 181 | 182 | #[test] 183 | fn test_parse_without_bracket() { 184 | let query = Query::new( 185 | " AAA ”111 CCC” -DDD or エエエ and FFF -”あああ いいい”" 186 | .into(), 187 | ); 188 | assert_eq!( 189 | LayeredQueries::parse(query).unwrap(), 190 | LayeredQueries(vec![LayeredQuery::Query(Query::new( 191 | " AAA \"111 CCC\" -DDD or エエエ and FFF -\"あああ いいい\" " 192 | .into() 193 | ))]) 194 | ) 195 | } 196 | 197 | #[test] 198 | fn test_parse_with_bracket() { 199 | let query = Query::new( 200 | " AAA ”111 CCC” (-DDD or エエエ) and FFF -”あああ いいい”" 201 | .into(), 202 | ); 203 | assert_eq!( 204 | LayeredQueries::parse(query).unwrap(), 205 | LayeredQueries(vec![ 206 | LayeredQuery::Query(Query::new(" AAA \"111 CCC\" ".into())), 207 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 208 | "-DDD or エエエ".into() 209 | ))])), 210 | LayeredQuery::Query(Query::new(" and FFF -\"あああ いいい\" ".into())) 211 | ]) 212 | ) 213 | } 214 | 215 | #[test] 216 | fn test_parse_with_negative_bracket() { 217 | let query = Query::new( 218 | " AAA ”111 CCC” -(DDD or エエエ) and FFF -”あああ いいい”" 219 | .into(), 220 | ); 221 | assert_eq!( 222 | LayeredQueries::parse(query).unwrap(), 223 | LayeredQueries(vec![ 224 | LayeredQuery::Query(Query::new(" AAA \"111 CCC\" ".into())), 225 | LayeredQuery::NegativeBracket(LayeredQueries(vec![LayeredQuery::Query( 226 | Query::new("DDD or エエエ".into()) 227 | )])), 228 | LayeredQuery::Query(Query::new(" and FFF -\"あああ いいい\" ".into())) 229 | ]) 230 | ) 231 | } 232 | 233 | #[test] 234 | fn test_parse_with_multi_brackets() { 235 | let query = Query::new( 236 | "( AAA ”111 CCC”) (-DDD or エエエ) and (FFF -”あああ いいい”)" 237 | .into(), 238 | ); 239 | assert_eq!( 240 | LayeredQueries::parse(query).unwrap(), 241 | LayeredQueries(vec![ 242 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 243 | " AAA \"111 CCC\" ".into() 244 | ))])), 245 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 246 | "-DDD or エエエ".into() 247 | ))])), 248 | LayeredQuery::Query(Query::new(" and ".into())), 249 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 250 | "FFF -\"あああ いいい\" ".into() 251 | ))])) 252 | ]) 253 | ) 254 | } 255 | 256 | #[test] 257 | fn test_parse_with_multi_brackets_or_negative_brackets() { 258 | let query = Query::new( 259 | "( AAA ”111 CCC”)-(DDD or エエエ) and (FFF -”あああ いいい”)" 260 | .into(), 261 | ); 262 | assert_eq!( 263 | LayeredQueries::parse(query).unwrap(), 264 | LayeredQueries(vec![ 265 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 266 | " AAA \"111 CCC\" ".into() 267 | ))])), 268 | LayeredQuery::NegativeBracket(LayeredQueries(vec![LayeredQuery::Query( 269 | Query::new("DDD or エエエ".into()) 270 | )])), 271 | LayeredQuery::Query(Query::new(" and ".into())), 272 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 273 | "FFF -\"あああ いいい\" ".into() 274 | ))])) 275 | ]) 276 | ) 277 | } 278 | 279 | #[test] 280 | fn test_parse_with_multi_nested_brackets() { 281 | let query = Query::new( 282 | " AAA (”111 CCC” or ((エエエ or FFF -”あああ いいい”) and -DDD)) and EEE" 283 | .into(), 284 | ); 285 | assert_eq!( 286 | LayeredQueries::parse(query).unwrap(), 287 | LayeredQueries(vec![ 288 | LayeredQuery::Query(Query::new(" AAA ".into())), 289 | LayeredQuery::Bracket(LayeredQueries(vec![ 290 | LayeredQuery::Query(Query::new(" \"111 CCC\" or ".into())), 291 | LayeredQuery::Bracket(LayeredQueries(vec![ 292 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query( 293 | Query::new("エエエ or FFF -\"あああ いいい\" ".into()) 294 | )])), 295 | LayeredQuery::Query(Query::new(" and -DDD".into())) 296 | ])) 297 | ])), 298 | LayeredQuery::Query(Query::new(" and EEE".into())), 299 | ]) 300 | ) 301 | } 302 | 303 | #[test] 304 | fn test_parse_with_multi_nested_brackets_or_nested_negative_brackets() { 305 | let query = Query::new( 306 | " AAA -(”111 CCC” or (-(エエエ or FFF -”あああ いいい”) and (-DDD or -EEE))) and FFF" 307 | .into(), 308 | ); 309 | assert_eq!( 310 | LayeredQueries::parse(query).unwrap(), 311 | LayeredQueries(vec![ 312 | LayeredQuery::Query(Query::new(" AAA ".into())), 313 | LayeredQuery::NegativeBracket(LayeredQueries(vec![ 314 | LayeredQuery::Query(Query::new(" \"111 CCC\" or ".into())), 315 | LayeredQuery::Bracket(LayeredQueries(vec![ 316 | LayeredQuery::NegativeBracket(LayeredQueries(vec![ 317 | LayeredQuery::Query(Query::new( 318 | "エエエ or FFF -\"あああ いいい\" ".into() 319 | )) 320 | ])), 321 | LayeredQuery::Query(Query::new(" and ".into())), 322 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query( 323 | Query::new("-DDD or -EEE".into()) 324 | )])) 325 | ])) 326 | ])), 327 | LayeredQuery::Query(Query::new(" and FFF".into())), 328 | ]) 329 | ) 330 | } 331 | 332 | #[test] 333 | fn test_parse_full_pattern() { 334 | let query = 335 | Query::new(" AAA (”111 CCC” (-( DDD エエエ ) FFF) GGG (HHH -”あああ いいい” ううう)) ” JJJ ” -(KKK ( ) LLL)  (MMM) 222 ".into()); 336 | assert_eq!( 337 | LayeredQueries::parse(query).unwrap(), 338 | LayeredQueries(vec![ 339 | LayeredQuery::Query(Query::new(" AAA ".into())), 340 | LayeredQuery::Bracket(LayeredQueries(vec![ 341 | LayeredQuery::Query(Query::new(" \"111 CCC\" ".into())), 342 | LayeredQuery::Bracket(LayeredQueries(vec![ 343 | LayeredQuery::NegativeBracket(LayeredQueries(vec![ 344 | LayeredQuery::Query(Query::new(" DDD エエエ ".into())), 345 | ])), 346 | LayeredQuery::Query(Query::new(" FFF".into())), 347 | ])), 348 | LayeredQuery::Query(Query::new(" GGG ".into())), 349 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query( 350 | Query::new("HHH -\"あああ いいい\" ううう".into()) 351 | ),])) 352 | ])), 353 | LayeredQuery::Query(Query::new(" \" JJJ \" ".into())), 354 | LayeredQuery::NegativeBracket(LayeredQueries(vec![LayeredQuery::Query( 355 | Query::new("KKK LLL".into()) 356 | ),])), 357 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 358 | "MMM".into() 359 | )),])), 360 | LayeredQuery::Query(Query::new(" 222 ".into())), 361 | ]) 362 | ) 363 | } 364 | 365 | #[test] 366 | fn test_parse_with_empty_bracket() { 367 | let query = Query::new( 368 | " AAA (  ) ”111 CCC” (-DDD or エエエ) and FFF () -”あああ いいい”" 369 | .into(), 370 | ); 371 | assert_eq!( 372 | LayeredQueries::parse(query).unwrap(), 373 | LayeredQueries(vec![ 374 | LayeredQuery::Query(Query::new(" AAA \"111 CCC\" ".into())), 375 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 376 | "-DDD or エエエ".into() 377 | ))])), 378 | LayeredQuery::Query(Query::new(" and FFF -\"あああ いいい\" ".into())) 379 | ]) 380 | ) 381 | } 382 | 383 | #[test] 384 | fn test_parse_with_only_start_bracket() { 385 | let query = Query::new( 386 | " AAA ”111 CCC” -(DDD or エエエ and ( FFF -”あああ いいい”" 387 | .into(), 388 | ); 389 | assert_eq!( 390 | LayeredQueries::parse(query).unwrap(), 391 | LayeredQueries(vec![LayeredQuery::Query(Query::new(" AAA \"111 CCC\" -DDD or エエエ and FFF -\"あああ いいい\" " 392 | .into()))]) 393 | ) 394 | } 395 | 396 | #[test] 397 | fn test_parse_with_only_end_bracket() { 398 | let query = Query::new( 399 | " AAA ”111 CCC”) -DDD or エエエ ) and FFF -”あああ いいい”" 400 | .into(), 401 | ); 402 | assert_eq!( 403 | LayeredQueries::parse(query).unwrap(), 404 | LayeredQueries(vec![LayeredQuery::Query(Query::new(" AAA \"111 CCC\" -DDD or エエエ and FFF -\"あああ いいい\" " 405 | .into()))]) 406 | ) 407 | } 408 | 409 | #[test] 410 | fn test_parse_with_start_bracket_more_than_end_bracket() { 411 | let query = Query::new( 412 | " AAA (”111 CCC” (DDD or エエエ) and ( FFF -”あああ いいい”" 413 | .into(), 414 | ); 415 | assert_eq!( 416 | LayeredQueries::parse(query).unwrap(), 417 | LayeredQueries(vec![ 418 | LayeredQuery::Query(Query::new(" AAA \"111 CCC\" ".into())), 419 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 420 | "DDD or エエエ".into() 421 | ))])), 422 | LayeredQuery::Query(Query::new(" and FFF -\"あああ いいい\" ".into())) 423 | ]) 424 | ) 425 | } 426 | 427 | #[test] 428 | fn test_parse_with_start_bracket_less_than_end_bracket() { 429 | let query = Query::new( 430 | " AAA) ”111 CCC” (DDD or エエエ) and ) FFF -”あああ いいい”" 431 | .into(), 432 | ); 433 | assert_eq!( 434 | LayeredQueries::parse(query).unwrap(), 435 | LayeredQueries(vec![ 436 | LayeredQuery::Query(Query::new(" AAA \"111 CCC\" ".into())), 437 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 438 | "DDD or エエエ".into() 439 | ))])), 440 | LayeredQuery::Query(Query::new(" and FFF -\"あああ いいい\" ".into())) 441 | ]) 442 | ) 443 | } 444 | 445 | #[test] 446 | fn test_parse_with_reverse_bracket() { 447 | let query = Query::new( 448 | " AAA) ”111 CCC” (DDD or エエエ) and ( FFF -”あああ いいい”" 449 | .into(), 450 | ); 451 | assert_eq!( 452 | LayeredQueries::parse(query).unwrap(), 453 | LayeredQueries(vec![ 454 | LayeredQuery::Query(Query::new(" AAA \"111 CCC\" ".into())), 455 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 456 | "DDD or エエエ".into() 457 | ))])), 458 | LayeredQuery::Query(Query::new(" and FFF -\"あああ いいい\" ".into())) 459 | ]) 460 | ) 461 | } 462 | 463 | #[test] 464 | fn test_parse_bracket_in_phrase_keywords() { 465 | let query = Query::new( 466 | "-(A1 or \" P1 and P2 -(P3 or P4) \") and (-\" NP1 and NP2 -(NP3 or NP4) \" or A2)" 467 | .into(), 468 | ); 469 | assert_eq!( 470 | LayeredQueries::parse(query).unwrap(), 471 | LayeredQueries(vec![ 472 | LayeredQuery::NegativeBracket(LayeredQueries(vec![LayeredQuery::Query( 473 | Query::new("A1 or \" P1 and P2 -(P3 or P4) \" ".into()) 474 | )])), 475 | LayeredQuery::Query(Query::new(" and ".into())), 476 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 477 | " -\" NP1 and NP2 -(NP3 or NP4) \" or A2".into() 478 | ))])) 479 | ]) 480 | ) 481 | } 482 | 483 | #[test] 484 | fn test_parse_full_width_bracket_in_phrase_keywords() { 485 | let query = Query::new( 486 | "-(A1 or ” P1 and P2 −(P3 or P4) ”) and (-” NP1 and NP2 −(NP3 or NP4) ” or A2)" 487 | .into(), 488 | ); 489 | assert_eq!( 490 | LayeredQueries::parse(query).unwrap(), 491 | LayeredQueries(vec![ 492 | LayeredQuery::NegativeBracket(LayeredQueries(vec![LayeredQuery::Query( 493 | Query::new( 494 | "A1 or \" P1 and P2 −(P3 or P4) \" ".into() 495 | ) 496 | )])), 497 | LayeredQuery::Query(Query::new(" and ".into())), 498 | LayeredQuery::Bracket(LayeredQueries(vec![LayeredQuery::Query(Query::new( 499 | " -\" NP1 and NP2 −(NP3 or NP4) \" or A2" 500 | .into() 501 | ))])) 502 | ]) 503 | ) 504 | } 505 | } 506 | 507 | mod test_layered_queries_parse_to_condition { 508 | use super::*; 509 | 510 | #[test] 511 | fn test_layered_queries_parse_to_condition_empty_string() { 512 | let query = Query::new("".into()); 513 | assert_eq!( 514 | LayeredQueries::parse(query) 515 | .unwrap() 516 | .to_condition() 517 | .unwrap(), 518 | Condition::None 519 | ) 520 | } 521 | 522 | #[test] 523 | fn test_layered_queries_parse_to_condition_blank_string() { 524 | let query = Query::new("   ".into()); 525 | assert_eq!( 526 | LayeredQueries::parse(query) 527 | .unwrap() 528 | .to_condition() 529 | .unwrap(), 530 | Condition::None 531 | ) 532 | } 533 | 534 | #[test] 535 | fn test_layered_queries_parse_to_condition_one_keyword() { 536 | let query = Query::new(" 検索 ".into()); 537 | assert_eq!( 538 | LayeredQueries::parse(query) 539 | .unwrap() 540 | .to_condition() 541 | .unwrap(), 542 | Condition::Keyword("検索".into()) 543 | ) 544 | } 545 | 546 | #[test] 547 | fn test_layered_queries_parse_to_condition_one_phrase_keyword() { 548 | let query = Query::new(" \"検索\" ".into()); 549 | assert_eq!( 550 | LayeredQueries::parse(query) 551 | .unwrap() 552 | .to_condition() 553 | .unwrap(), 554 | Condition::PhraseKeyword("検索".into()) 555 | ) 556 | } 557 | 558 | #[test] 559 | fn test_layered_queries_parse_to_condition_one_negative_keyword() { 560 | let query = Query::new(" -検索 ".into()); 561 | assert_eq!( 562 | LayeredQueries::parse(query) 563 | .unwrap() 564 | .to_condition() 565 | .unwrap(), 566 | Condition::Not(Box::new(Condition::Keyword("検索".into()))) 567 | ) 568 | } 569 | 570 | #[test] 571 | fn test_layered_queries_parse_to_condition_one_negative_phrase_keyword() { 572 | let query = Query::new(" -\"検索\" ".into()); 573 | assert_eq!( 574 | LayeredQueries::parse(query) 575 | .unwrap() 576 | .to_condition() 577 | .unwrap(), 578 | Condition::Not(Box::new(Condition::PhraseKeyword("検索".into()))) 579 | ) 580 | } 581 | 582 | #[test] 583 | fn test_layered_queries_parse_to_condition_mutlti_keywords_concat_with_space() { 584 | let query = Query::new(" 検索1 -検索2 \"検索3\" -\"検索4\" ".into()); 585 | assert_eq!( 586 | LayeredQueries::parse(query) 587 | .unwrap() 588 | .to_condition() 589 | .unwrap(), 590 | Condition::Operator( 591 | Operator::And, 592 | vec![ 593 | Condition::Keyword("検索1".into()), 594 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 595 | Condition::PhraseKeyword("検索3".into()), 596 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 597 | ] 598 | ) 599 | ) 600 | } 601 | 602 | #[test] 603 | fn test_layered_queries_parse_to_condition_mutlti_keywords_concat_with_and() { 604 | let query = Query::new(" 検索1 and -検索2 and \"検索3\" and -\"検索4\" ".into()); 605 | assert_eq!( 606 | LayeredQueries::parse(query) 607 | .unwrap() 608 | .to_condition() 609 | .unwrap(), 610 | Condition::Operator( 611 | Operator::And, 612 | vec![ 613 | Condition::Keyword("検索1".into()), 614 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 615 | Condition::PhraseKeyword("検索3".into()), 616 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 617 | ] 618 | ) 619 | ) 620 | } 621 | 622 | #[test] 623 | fn test_layered_queries_parse_to_condition_mutlti_keywords_concat_with_or() { 624 | let query = Query::new(" 検索1 or -検索2 or \"検索3\" or -\"検索4\" ".into()); 625 | assert_eq!( 626 | LayeredQueries::parse(query) 627 | .unwrap() 628 | .to_condition() 629 | .unwrap(), 630 | Condition::Operator( 631 | Operator::Or, 632 | vec![ 633 | Condition::Keyword("検索1".into()), 634 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 635 | Condition::PhraseKeyword("検索3".into()), 636 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 637 | ] 638 | ) 639 | ) 640 | } 641 | 642 | #[test] 643 | fn test_layered_queries_parse_to_condition_mutlti_keywords_concat_with_space_or_and() { 644 | let query = Query::new(" 検索1 -検索2 or \"検索3\" and -\"検索4\" ".into()); 645 | assert_eq!( 646 | LayeredQueries::parse(query) 647 | .unwrap() 648 | .to_condition() 649 | .unwrap(), 650 | Condition::Operator( 651 | Operator::Or, 652 | vec![ 653 | Condition::Operator( 654 | Operator::And, 655 | vec![ 656 | Condition::Keyword("検索1".into()), 657 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 658 | ] 659 | ), 660 | Condition::Operator( 661 | Operator::And, 662 | vec![ 663 | Condition::PhraseKeyword("検索3".into()), 664 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 665 | ] 666 | ) 667 | ] 668 | ) 669 | ) 670 | } 671 | 672 | #[test] 673 | fn test_layered_queries_parse_to_condition_or_in_brackets() { 674 | let query = Query::new(" 検索1 and (-検索2 or \"検索3\") or -\"検索4\" ".into()); 675 | assert_eq!( 676 | LayeredQueries::parse(query) 677 | .unwrap() 678 | .to_condition() 679 | .unwrap(), 680 | Condition::Operator( 681 | Operator::Or, 682 | vec![ 683 | Condition::Operator( 684 | Operator::And, 685 | vec![ 686 | Condition::Keyword("検索1".into()), 687 | Condition::Operator( 688 | Operator::Or, 689 | vec![ 690 | Condition::Not(Box::new(Condition::Keyword( 691 | "検索2".into() 692 | ))), 693 | Condition::PhraseKeyword("検索3".into()), 694 | ] 695 | ), 696 | ] 697 | ), 698 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 699 | ] 700 | ) 701 | ) 702 | } 703 | 704 | #[test] 705 | fn test_layered_queries_parse_to_condition_or_in_negative_brackets() { 706 | let query = Query::new(" 検索1 and -(-検索2 or \"検索3\") or -\"検索4\" ".into()); 707 | assert_eq!( 708 | LayeredQueries::parse(query) 709 | .unwrap() 710 | .to_condition() 711 | .unwrap(), 712 | Condition::Operator( 713 | Operator::Or, 714 | vec![ 715 | Condition::Operator( 716 | Operator::And, 717 | vec![ 718 | Condition::Keyword("検索1".into()), 719 | Condition::Not(Box::new(Condition::Operator( 720 | Operator::Or, 721 | vec![ 722 | Condition::Not(Box::new(Condition::Keyword( 723 | "検索2".into() 724 | ))), 725 | Condition::PhraseKeyword("検索3".into()), 726 | ] 727 | ))), 728 | ] 729 | ), 730 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 731 | ] 732 | ) 733 | ) 734 | } 735 | 736 | #[test] 737 | fn test_layered_queries_parse_to_condition_mutlti_brackets() { 738 | let query = Query::new( 739 | " (検索1 or -検索2)and(\"検索3\" or -\"検索4\")(\" 検索5 検索6 \" or 検索7) " 740 | .into(), 741 | ); 742 | assert_eq!( 743 | LayeredQueries::parse(query) 744 | .unwrap() 745 | .to_condition() 746 | .unwrap(), 747 | Condition::Operator( 748 | Operator::And, 749 | vec![ 750 | Condition::Operator( 751 | Operator::Or, 752 | vec![ 753 | Condition::Keyword("検索1".into()), 754 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 755 | ] 756 | ), 757 | Condition::Operator( 758 | Operator::Or, 759 | vec![ 760 | Condition::PhraseKeyword("検索3".into()), 761 | Condition::Not(Box::new(Condition::PhraseKeyword("検索4".into()))) 762 | ] 763 | ), 764 | Condition::Operator( 765 | Operator::Or, 766 | vec![ 767 | Condition::PhraseKeyword(" 検索5 検索6 ".into()), 768 | Condition::Keyword("検索7".into()) 769 | ] 770 | ) 771 | ] 772 | ) 773 | ) 774 | } 775 | 776 | #[test] 777 | fn test_layered_queries_parse_to_condition_nested_brackets() { 778 | let query = Query::new( 779 | " (word1 and -word2) or ((\"phrase word 1\" or -\"phrase word 2\") and -(\" a long phrase word \" or word3)) " 780 | .into(), 781 | ); 782 | assert_eq!( 783 | LayeredQueries::parse(query) 784 | .unwrap() 785 | .to_condition() 786 | .unwrap(), 787 | Condition::Operator( 788 | Operator::Or, 789 | vec![ 790 | Condition::Operator( 791 | Operator::And, 792 | vec![ 793 | Condition::Keyword("word1".into()), 794 | Condition::Not(Box::new(Condition::Keyword("word2".into()))), 795 | ] 796 | ), 797 | Condition::Operator( 798 | Operator::And, 799 | vec![ 800 | Condition::Operator( 801 | Operator::Or, 802 | vec![ 803 | Condition::PhraseKeyword("phrase word 1".into()), 804 | Condition::Not(Box::new(Condition::PhraseKeyword( 805 | "phrase word 2".into() 806 | ))) 807 | ] 808 | ), 809 | Condition::Not(Box::new(Condition::Operator( 810 | Operator::Or, 811 | vec![ 812 | Condition::PhraseKeyword(" a long phrase word ".into()), 813 | Condition::Keyword("word3".into()) 814 | ] 815 | ))) 816 | ] 817 | ), 818 | ] 819 | ) 820 | ) 821 | } 822 | 823 | #[test] 824 | fn test_layered_queries_parse_to_condition_bracket_in_phrase_keywords() { 825 | let query = Query::new( 826 | " (検索1 and -検索2) or ((\" P1 and P2 -(P3 or P4) \" or -\" NP1 and NP2 -(NP3 or NP4) \") and (\" 検索5 検索6 \" or 検索7)) " 827 | .into(), 828 | ); 829 | assert_eq!( 830 | LayeredQueries::parse(query) 831 | .unwrap() 832 | .to_condition() 833 | .unwrap(), 834 | Condition::Operator( 835 | Operator::Or, 836 | vec![ 837 | Condition::Operator( 838 | Operator::And, 839 | vec![ 840 | Condition::Keyword("検索1".into()), 841 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 842 | ] 843 | ), 844 | Condition::Operator( 845 | Operator::And, 846 | vec![ 847 | Condition::Operator( 848 | Operator::Or, 849 | vec![ 850 | Condition::PhraseKeyword( 851 | " P1 and P2 -(P3 or P4) ".into() 852 | ), 853 | Condition::Not(Box::new(Condition::PhraseKeyword( 854 | " NP1 and NP2 -(NP3 or NP4) ".into() 855 | ))) 856 | ] 857 | ), 858 | Condition::Operator( 859 | Operator::Or, 860 | vec![ 861 | Condition::PhraseKeyword(" 検索5 検索6 ".into()), 862 | Condition::Keyword("検索7".into()) 863 | ] 864 | ) 865 | ] 866 | ), 867 | ] 868 | ) 869 | ) 870 | } 871 | 872 | #[test] 873 | fn test_layered_queries_parse_to_condition_unnecessary_nested_brackets() { 874 | let query = Query::new( 875 | " ((検索1 and -検索2)) or (((((\"検索3\" or -\"検索4\"))) and ((((\" 検索5 検索6 \" or 検索7)))))) " 876 | .into(), 877 | ); 878 | assert_eq!( 879 | LayeredQueries::parse(query) 880 | .unwrap() 881 | .to_condition() 882 | .unwrap(), 883 | Condition::Operator( 884 | Operator::Or, 885 | vec![ 886 | Condition::Operator( 887 | Operator::And, 888 | vec![ 889 | Condition::Keyword("検索1".into()), 890 | Condition::Not(Box::new(Condition::Keyword("検索2".into()))), 891 | ] 892 | ), 893 | Condition::Operator( 894 | Operator::And, 895 | vec![ 896 | Condition::Operator( 897 | Operator::Or, 898 | vec![ 899 | Condition::PhraseKeyword("検索3".into()), 900 | Condition::Not(Box::new(Condition::PhraseKeyword( 901 | "検索4".into() 902 | ))) 903 | ] 904 | ), 905 | Condition::Operator( 906 | Operator::Or, 907 | vec![ 908 | Condition::PhraseKeyword(" 検索5 検索6 ".into()), 909 | Condition::Keyword("検索7".into()) 910 | ] 911 | ) 912 | ] 913 | ), 914 | ] 915 | ) 916 | ) 917 | } 918 | 919 | #[test] 920 | fn test_layered_queries_parse_to_condition_full_pattern() { 921 | let query = 922 | Query::new(" AAA (”111 CCC” or(-( DDD or エエエ )and FFF)or GGG (HHH or -”あああ いいい” ううう)) ” JJJ ” or -(KKK and ( ) or LLL)  (MMM)or 222 ".into()); 923 | assert_eq!( 924 | LayeredQueries::parse(query) 925 | .unwrap() 926 | .to_condition() 927 | .unwrap(), 928 | Condition::Operator( 929 | Operator::Or, 930 | vec![ 931 | Condition::Operator( 932 | Operator::And, 933 | vec![ 934 | Condition::Keyword("AAA".into()), 935 | Condition::Operator( 936 | Operator::Or, 937 | vec![ 938 | Condition::PhraseKeyword("111 CCC".into()), 939 | Condition::Operator( 940 | Operator::And, 941 | vec![ 942 | Condition::Not(Box::new(Condition::Operator( 943 | Operator::Or, 944 | vec![ 945 | Condition::Keyword("DDD".into()), 946 | Condition::Keyword("エエエ".into()), 947 | ] 948 | ))), 949 | Condition::Keyword("FFF".into()), 950 | ] 951 | ), 952 | Condition::Operator( 953 | Operator::And, 954 | vec![ 955 | Condition::Keyword("GGG".into()), 956 | Condition::Operator( 957 | Operator::Or, 958 | vec![ 959 | Condition::Keyword("HHH".into()), 960 | Condition::Operator( 961 | Operator::And, 962 | vec![ 963 | Condition::Not(Box::new( 964 | Condition::PhraseKeyword( 965 | "あああ いいい".into() 966 | ) 967 | )), 968 | Condition::Keyword("ううう".into()), 969 | ] 970 | ) 971 | ] 972 | ) 973 | ] 974 | ) 975 | ] 976 | ), 977 | Condition::PhraseKeyword(" JJJ ".into()) 978 | ] 979 | ), 980 | Condition::Operator( 981 | Operator::And, 982 | vec![ 983 | Condition::Not(Box::new(Condition::Operator( 984 | Operator::Or, 985 | vec![ 986 | Condition::Keyword("KKK".into()), 987 | Condition::Keyword("LLL".into()), 988 | ] 989 | ))), 990 | Condition::Keyword("MMM".into()), 991 | ] 992 | ), 993 | Condition::Keyword("222".into()) 994 | ] 995 | ) 996 | ) 997 | } 998 | } 999 | } 1000 | -------------------------------------------------------------------------------- /src/regex_approach/query.rs: -------------------------------------------------------------------------------- 1 | use crate::regex_approach::{regex_match_not_blank_query, regex_match_number}; 2 | use crate::{Condition, Operator}; 3 | use eyre::Result; 4 | use regex::{Captures, Regex}; 5 | 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub(crate) struct Query(String); 8 | 9 | impl Query { 10 | pub(crate) fn new(value: String) -> Self { 11 | Self(value) 12 | } 13 | 14 | pub(crate) fn value(self) -> String { 15 | self.0 16 | } 17 | 18 | pub(crate) fn value_ref(&self) -> &str { 19 | self.0.as_str() 20 | } 21 | 22 | pub(crate) fn normalize_double_quotation(self) -> Self { 23 | Self(self.value().replace("”", "\"")) 24 | } 25 | 26 | pub(crate) fn normalize_symbols_except_double_quotation(self) -> Self { 27 | Self( 28 | self.value() 29 | .replace("(", "(") 30 | .replace(")", ")") 31 | .replace(" ", " "), 32 | ) 33 | } 34 | 35 | fn remove_double_quotation(self) -> Self { 36 | Self(self.value().replace("\"", "")) 37 | } 38 | 39 | pub(crate) fn remove_bracket(self) -> Self { 40 | Self(self.value().replace("(", "").replace(")", "")) 41 | } 42 | 43 | pub(crate) fn is_not_blank(&self) -> bool { 44 | self.value_ref() 45 | .replace(" ", "") 46 | .replace(" ", "") 47 | .is_empty() 48 | == false 49 | } 50 | 51 | pub(crate) fn extract_phrase_keywords(self) -> Result<(Self, Vec, Vec)> { 52 | let mut query = self; 53 | let mut negative_phrase_keywords = Vec::::new(); 54 | let mut phrase_keywords = Vec::::new(); 55 | vec![ 56 | ( 57 | Regex::new("-\"([^\"]*)\"")?, 58 | &mut negative_phrase_keywords, 59 | "NPK", 60 | ), 61 | (Regex::new("\"([^\"]*)\"")?, &mut phrase_keywords, "PK"), 62 | ] 63 | .iter_mut() 64 | .for_each(|(regex, vec, prefix)| { 65 | query = Query::new( 66 | regex 67 | .replace_all(query.value_ref(), |captures: &Captures| { 68 | match regex_match_not_blank_query(captures.get(1)) { 69 | Some(q) => { 70 | vec.push(q); 71 | format!(" ”{}:{}” ", prefix, vec.len()) 72 | } 73 | None => String::from(""), 74 | } 75 | }) 76 | .to_string(), 77 | ) 78 | }); 79 | query = query.remove_double_quotation(); 80 | Ok((query, negative_phrase_keywords, phrase_keywords)) 81 | } 82 | 83 | pub(crate) fn combine_phrase_keywords( 84 | self, negative_phrase_keywords: &Vec, phrase_keywords: &Vec, 85 | ) -> Result { 86 | let mut query = self; 87 | vec![ 88 | (Regex::new(r"”NPK:(\d+)”")?, negative_phrase_keywords, "-"), 89 | (Regex::new(r"”PK:(\d+)”")?, phrase_keywords, ""), 90 | ] 91 | .into_iter() 92 | .for_each(|(regex, vec, prefix)| { 93 | query = Query::new( 94 | regex 95 | .replace_all(query.value_ref(), |captures: &Captures| { 96 | regex_match_number(captures.get(1), |i| { 97 | vec.get(i - 1) 98 | .map(|q| format!("{}\"{}\"", prefix, q.value_ref())) 99 | }) 100 | .unwrap_or("".into()) 101 | }) 102 | .into(), 103 | ); 104 | }); 105 | Ok(query) 106 | } 107 | 108 | pub(crate) fn to_condition(self) -> Result<(bool, Condition, bool)> { 109 | let (mut query, negative_phrase_keywords, phrase_keywords) = 110 | self.extract_phrase_keywords()?; 111 | 112 | query = Query::new( 113 | Regex::new(" +(?i)[A|A](?i)[N|N](?i)[D|D] +")? 114 | .replace_all(query.value_ref(), |_: &Captures| String::from(" ")) 115 | .to_string(), 116 | ); 117 | 118 | let mut or_conditions = Vec::::new(); 119 | let (is_start_with_or, is_end_with_or) = match ( 120 | Regex::new("^ *(?i)[O|O](?i)[R|R] *$")?.is_match(query.value_ref()), 121 | Regex::new("^ *(?i)[O|O](?i)[R|R] +")?.is_match(query.value_ref()), 122 | Regex::new(" +(?i)[O|O](?i)[R|R] *$")?.is_match(query.value_ref()), 123 | ) { 124 | (true, _, _) => (true, true), 125 | (false, is_start_with_or, is_end_with_or) => (is_start_with_or, is_end_with_or), 126 | }; 127 | let or_queries = Regex::new(" +(?i)[O|O](?i)[R|R] +")? 128 | .split(query.value_ref()) 129 | .into_iter() 130 | .collect::>(); 131 | let and_regex = Regex::new(" +")?; 132 | or_queries.into_iter().for_each(|q| { 133 | let query = Query::new(q.into()); 134 | if query.is_not_blank() { 135 | let and_conditions = and_regex 136 | .split(query.value_ref()) 137 | .into_iter() 138 | .filter_map(|k| { 139 | let q = Query::new(k.into()); 140 | match q.is_not_blank() { 141 | true => Some(q), 142 | false => None, 143 | } 144 | }) 145 | .filter_map(|keyword| { 146 | keyword 147 | .keyword_condition(&negative_phrase_keywords, &phrase_keywords) 148 | .unwrap_or(None) 149 | }) 150 | .collect::>(); 151 | or_conditions.push(Condition::Operator(Operator::And, and_conditions)); 152 | } 153 | }); 154 | 155 | return Ok(( 156 | is_start_with_or, 157 | Condition::Operator(Operator::Or, or_conditions).simplify(), 158 | is_end_with_or, 159 | )); 160 | } 161 | 162 | fn keyword_condition( 163 | self, negative_phrase_keywords: &Vec, phrase_keywords: &Vec, 164 | ) -> Result> { 165 | Ok( 166 | match ( 167 | Regex::new(r"^”NPK:(\d+)”$")?.captures(self.value_ref()), 168 | Regex::new(r"^”PK:(\d+)”$")?.captures(self.value_ref()), 169 | ) { 170 | (Some(npk), _) => regex_match_number(npk.get(1), |i| { 171 | negative_phrase_keywords.get(i - 1).map(|npk| { 172 | Condition::Not(Box::new(Condition::PhraseKeyword(npk.value_ref().into()))) 173 | }) 174 | }), 175 | (_, Some(pk)) => regex_match_number(pk.get(1), |i| { 176 | phrase_keywords 177 | .get(i - 1) 178 | .map(|pk| Condition::PhraseKeyword(pk.value_ref().into())) 179 | }), 180 | (None, None) => match (self.value_ref().len(), self.value_ref().starts_with("-")) { 181 | (1, _) => Some(Condition::Keyword(self.value())), 182 | (_, true) => Some(Condition::Not(Box::new(Condition::Keyword( 183 | self.value_ref()[1..self.value_ref().len()].into(), 184 | )))), 185 | _ => { 186 | let operation_regexes = vec![ 187 | Regex::new("^(?i)[A|A](?i)[N|N](?i)[D|D]$")?, 188 | Regex::new("^(?i)[O|O](?i)[R|R]$")?, 189 | ]; 190 | operation_regexes 191 | .into_iter() 192 | .find(|regex| regex.is_match(self.value_ref())) 193 | .map(|_| None) 194 | .unwrap_or(Some(Condition::Keyword(self.value()))) 195 | } 196 | }, 197 | }, 198 | ) 199 | } 200 | } 201 | 202 | #[cfg(test)] 203 | mod tests { 204 | use super::*; 205 | 206 | mod test_query_normalize { 207 | use super::*; 208 | 209 | #[test] 210 | fn test_normalize_replace_full_width_double_quotation() { 211 | let target = 212 | Query::new(" AAA (”111 CCC” (-( DDD エエエ ) FFF) GGG (HHH -”あああ いいい” ううう)) ” JJJ ” -(KKK ( ) LLL)  (MMM) 222 ".into()); 213 | assert_eq!( 214 | target.normalize_double_quotation().value_ref(), 215 | " AAA (\"111 CCC\" (-( DDD エエエ ) FFF) GGG (HHH -\"あああ いいい\" ううう)) \" JJJ \" -(KKK ( ) LLL)  (MMM) 222 " 216 | ) 217 | } 218 | 219 | #[test] 220 | fn test_normalize_replace_full_width_bracket_and_space() { 221 | let target = 222 | Query::new(" AAA (”111 CCC” (-( DDD エエエ ) FFF) GGG (HHH -”あああ いいい” ううう)) ” JJJ ” -(KKK ( ) LLL)  (MMM) 222 ".into()); 223 | assert_eq!( 224 | target.normalize_symbols_except_double_quotation().value_ref(), 225 | " AAA (”111 CCC” (-( DDD エエエ ) FFF) GGG (HHH -”あああ いいい” ううう)) ” JJJ ” -(KKK ( ) LLL) (MMM) 222 " 226 | ) 227 | } 228 | } 229 | 230 | mod test_extract_phrase_keywords { 231 | use super::*; 232 | 233 | #[test] 234 | fn test_extract_phrase_keywords_empty() { 235 | let target = Query::new("A1 A2".into()); 236 | let (query, negative_phrase_keywords, phrase_keywords) = 237 | target.extract_phrase_keywords().unwrap(); 238 | assert_eq!(query, Query::new("A1 A2".into())); 239 | assert_eq!(negative_phrase_keywords, vec![]); 240 | assert_eq!(phrase_keywords, vec![]) 241 | } 242 | 243 | #[test] 244 | fn test_extract_phrase_keywords_empty_phrase_keyword() { 245 | let target = Query::new("A1 \"\" A2".into()); 246 | let (query, negative_phrase_keywords, phrase_keywords) = 247 | target.extract_phrase_keywords().unwrap(); 248 | assert_eq!(query, Query::new("A1 A2".into())); 249 | assert_eq!(negative_phrase_keywords, vec![]); 250 | assert_eq!(phrase_keywords, vec![]) 251 | } 252 | 253 | #[test] 254 | fn test_extract_phrase_keywords_blank_phrase_keyword() { 255 | let target = Query::new("A1 \"    \" A2".into()); 256 | let (query, negative_phrase_keywords, phrase_keywords) = 257 | target.extract_phrase_keywords().unwrap(); 258 | assert_eq!(query, Query::new("A1 A2".into())); 259 | assert_eq!(negative_phrase_keywords, vec![]); 260 | assert_eq!(phrase_keywords, vec![]) 261 | } 262 | 263 | #[test] 264 | fn test_extract_phrase_keywords_one_phrase_keyword() { 265 | let target = Query::new("A1 \"P1\" A2".into()); 266 | let (query, negative_phrase_keywords, phrase_keywords) = 267 | target.extract_phrase_keywords().unwrap(); 268 | assert_eq!(query, Query::new("A1 ”PK:1” A2".into())); 269 | assert_eq!(negative_phrase_keywords, vec![]); 270 | assert_eq!(phrase_keywords, vec![Query::new("P1".into())]) 271 | } 272 | 273 | #[test] 274 | fn test_extract_phrase_keywords_one_negative_phrase_keyword() { 275 | let target = Query::new("A1 -\"NP1\" A2".into()); 276 | let (query, negative_phrase_keywords, phrase_keywords) = 277 | target.extract_phrase_keywords().unwrap(); 278 | assert_eq!(query, Query::new("A1 ”NPK:1” A2".into())); 279 | assert_eq!(negative_phrase_keywords, vec![Query::new("NP1".into())]); 280 | assert_eq!(phrase_keywords, vec![]) 281 | } 282 | 283 | #[test] 284 | fn test_extract_phrase_keywords_multi_phrase_keywords_and_negative_phrase_keywords() { 285 | let target = Query::new("-\"NP1\" A1 or \"P3\" and -\"NP3\" -\"NP2\" \"P2\" or A2 and \"P1\"".into()); 286 | let (query, negative_phrase_keywords, phrase_keywords) = 287 | target.extract_phrase_keywords().unwrap(); 288 | assert_eq!(query, Query::new(" ”NPK:1” A1 or ”PK:1” and ”NPK:2” ”NPK:3” ”PK:2” or A2 and ”PK:3” ".into())); 289 | assert_eq!( 290 | negative_phrase_keywords, 291 | vec![ 292 | Query::new("NP1".into()), 293 | Query::new("NP3".into()), 294 | Query::new("NP2".into()) 295 | ] 296 | ); 297 | assert_eq!( 298 | phrase_keywords, 299 | vec![ 300 | Query::new("P3".into()), 301 | Query::new("P2".into()), 302 | Query::new("P1".into()) 303 | ] 304 | ) 305 | } 306 | 307 | #[test] 308 | fn test_extract_phrase_keywords_special_symbol_in_phrase_keyword() { 309 | let target = Query::new("A1 \" P1 and P2 -(P3 or P4) \" A2".into()); 310 | let (query, negative_phrase_keywords, phrase_keywords) = 311 | target.extract_phrase_keywords().unwrap(); 312 | assert_eq!(query, Query::new("A1 ”PK:1” A2".into())); 313 | assert_eq!(negative_phrase_keywords, vec![]); 314 | assert_eq!( 315 | phrase_keywords, 316 | vec![Query::new(" P1 and P2 -(P3 or P4) ".into())] 317 | ) 318 | } 319 | 320 | #[test] 321 | fn test_extract_phrase_keywords_full_width_special_symbol_in_phrase_keyword() { 322 | let target = 323 | Query::new("A1 \" P1 and P2 −(P3 or P4) \" A2".into()); 324 | let (query, negative_phrase_keywords, phrase_keywords) = 325 | target.extract_phrase_keywords().unwrap(); 326 | assert_eq!(query, Query::new("A1 ”PK:1” A2".into())); 327 | assert_eq!(negative_phrase_keywords, vec![]); 328 | assert_eq!( 329 | phrase_keywords, 330 | vec![Query::new( 331 | " P1 and P2 −(P3 or P4) ".into() 332 | )] 333 | ) 334 | } 335 | 336 | #[test] 337 | fn test_extract_phrase_keywords_special_symbol_in_negative_phrase_keyword() { 338 | let target = Query::new("A1 -\" P1 and P2 -(P3 or P4) \" A2".into()); 339 | let (query, negative_phrase_keywords, phrase_keywords) = 340 | target.extract_phrase_keywords().unwrap(); 341 | assert_eq!(query, Query::new("A1 ”NPK:1” A2".into())); 342 | assert_eq!( 343 | negative_phrase_keywords, 344 | vec![Query::new(" P1 and P2 -(P3 or P4) ".into())] 345 | ); 346 | assert_eq!(phrase_keywords, vec![]) 347 | } 348 | 349 | #[test] 350 | fn test_extract_phrase_keywords_full_width_special_symbol_in_negative_phrase_keyword() { 351 | let target = 352 | Query::new("A1 -\" P1 and P2 −(P3 or P4) \" A2".into()); 353 | let (query, negative_phrase_keywords, phrase_keywords) = 354 | target.extract_phrase_keywords().unwrap(); 355 | assert_eq!(query, Query::new("A1 ”NPK:1” A2".into())); 356 | assert_eq!( 357 | negative_phrase_keywords, 358 | vec![Query::new( 359 | " P1 and P2 −(P3 or P4) ".into() 360 | )] 361 | ); 362 | assert_eq!(phrase_keywords, vec![]) 363 | } 364 | 365 | #[test] 366 | fn test_extract_phrase_keywords_excess_double_quotation_1() { 367 | let target = Query::new("A1 \"P1\" -\"NP1\" \" A2".into()); 368 | let (query, negative_phrase_keywords, phrase_keywords) = 369 | target.extract_phrase_keywords().unwrap(); 370 | assert_eq!(query, Query::new("A1 ”PK:1” ”NPK:1” A2".into())); 371 | assert_eq!(negative_phrase_keywords, vec![Query::new("NP1".into())]); 372 | assert_eq!(phrase_keywords, vec![Query::new("P1".into())]) 373 | } 374 | 375 | #[test] 376 | fn test_extract_phrase_keywords_excess_double_quotation_2() { 377 | let target = Query::new("A1 \"P1\" -\"NP1\" -\"A2".into()); 378 | let (query, negative_phrase_keywords, phrase_keywords) = 379 | target.extract_phrase_keywords().unwrap(); 380 | assert_eq!(query, Query::new("A1 ”PK:1” ”NPK:1” -A2".into())); 381 | assert_eq!(negative_phrase_keywords, vec![Query::new("NP1".into())]); 382 | assert_eq!(phrase_keywords, vec![Query::new("P1".into())]) 383 | } 384 | } 385 | 386 | mod test_combine_phrase_keywords { 387 | use super::*; 388 | 389 | #[test] 390 | fn test_combine_phrase_keywords_none() { 391 | let target = Query::new("A1 and A2 or A3".into()); 392 | let query = target 393 | .combine_phrase_keywords( 394 | &vec![ 395 | Query::new("否定的な連続キーワード1".into()), 396 | Query::new("否定的な連続キーワード2".into()), 397 | ], 398 | &vec![ 399 | Query::new("連続キーワード1".into()), 400 | Query::new("連続キーワード2".into()), 401 | ], 402 | ) 403 | .unwrap(); 404 | assert_eq!(query, Query::new("A1 and A2 or A3".into())) 405 | } 406 | 407 | #[test] 408 | fn test_combine_phrase_keywords_multi_phrase_keywords_and_negative_phrase_keywords() { 409 | let target = Query::new("A1 ”PK:1” and ”NPK:1” A2 ”PK:2” or ”NPK:2” A3".into()); 410 | let query = target 411 | .combine_phrase_keywords( 412 | &vec![ 413 | Query::new("否定的な連続キーワード1".into()), 414 | Query::new("否定的な連続キーワード2".into()), 415 | ], 416 | &vec![ 417 | Query::new("連続キーワード1".into()), 418 | Query::new("連続キーワード2".into()), 419 | ], 420 | ) 421 | .unwrap(); 422 | assert_eq!(query,Query::new("A1 \"連続キーワード1\" and -\"否定的な連続キーワード1\" A2 \"連続キーワード2\" or -\"否定的な連続キーワード2\" A3".into())) 423 | } 424 | 425 | #[test] 426 | fn test_combine_phrase_keywords_not_exists() { 427 | let target = Query::new("A1 ”PK:1” and ”NPK:1” A2 ”PK:2” or ”NPK:2” A3".into()); 428 | let query = target 429 | .combine_phrase_keywords( 430 | &vec![Query::new("否定的な連続キーワード1".into())], 431 | &vec![Query::new("連続キーワード1".into())], 432 | ) 433 | .unwrap(); 434 | assert_eq!( 435 | query, 436 | Query::new( 437 | "A1 \"連続キーワード1\" and -\"否定的な連続キーワード1\" A2 or A3" 438 | .into() 439 | ) 440 | ) 441 | } 442 | } 443 | 444 | mod test_query_to_condition { 445 | use super::*; 446 | 447 | #[test] 448 | fn test_query_to_condition_only_space() { 449 | let target = Query::new(" ".into()); 450 | let actual = target.to_condition().unwrap(); 451 | assert_eq!(actual, (false, Condition::None, false)) 452 | } 453 | 454 | #[test] 455 | fn test_query_to_condition_only_one_keyword() { 456 | let target = Query::new("AAA".into()); 457 | let actual = target.to_condition().unwrap(); 458 | assert_eq!(actual, (false, Condition::Keyword("AAA".into()), false)) 459 | } 460 | 461 | #[test] 462 | fn test_query_to_condition_only_one_phrase_keyword() { 463 | let target = Query::new("\"AAA BBB\"".into()); 464 | let actual = target.to_condition().unwrap(); 465 | assert_eq!( 466 | actual, 467 | ( 468 | false, 469 | Condition::PhraseKeyword("AAA BBB".into()), 470 | false 471 | ) 472 | ) 473 | } 474 | 475 | #[test] 476 | fn test_query_to_condition_only_one_phrase_keyword_include_special_word() { 477 | let target = Query::new("\" P1 and P2 -(P3 or P4) \"".into()); 478 | let actual = target.to_condition().unwrap(); 479 | assert_eq!( 480 | actual, 481 | ( 482 | false, 483 | Condition::PhraseKeyword(" P1 and P2 -(P3 or P4) ".into()), 484 | false 485 | ) 486 | ) 487 | } 488 | 489 | #[test] 490 | fn test_query_to_condition_only_one_phrase_keyword_include_full_width_special_word() { 491 | let target = Query::new("\" P1 and P2 −(P3 or P4) \"".into()); 492 | let actual = target.to_condition().unwrap(); 493 | assert_eq!( 494 | actual, 495 | ( 496 | false, 497 | Condition::PhraseKeyword( 498 | " P1 and P2 −(P3 or P4) ".into() 499 | ), 500 | false 501 | ) 502 | ) 503 | } 504 | 505 | #[test] 506 | fn test_query_to_condition_ten_phrase_keywords() { 507 | let target = Query::new("\"AAA1\" \"AAA2\" \"AAA3\" \"AAA4\" \"AAA5\" \"AAA6\" \"AAA7\" \"AAA8\" \"AAA9\" \"AAA10\"".into()); 508 | let actual = target.to_condition().unwrap(); 509 | assert_eq!( 510 | actual, 511 | ( 512 | false, 513 | Condition::Operator( 514 | Operator::And, 515 | vec![ 516 | Condition::PhraseKeyword("AAA1".into()), 517 | Condition::PhraseKeyword("AAA2".into()), 518 | Condition::PhraseKeyword("AAA3".into()), 519 | Condition::PhraseKeyword("AAA4".into()), 520 | Condition::PhraseKeyword("AAA5".into()), 521 | Condition::PhraseKeyword("AAA6".into()), 522 | Condition::PhraseKeyword("AAA7".into()), 523 | Condition::PhraseKeyword("AAA8".into()), 524 | Condition::PhraseKeyword("AAA9".into()), 525 | Condition::PhraseKeyword("AAA10".into()), 526 | ] 527 | ), 528 | false 529 | ) 530 | ) 531 | } 532 | 533 | #[test] 534 | fn test_query_to_condition_only_one_negative_keyword() { 535 | let target = Query::new("-AAA".into()); 536 | let actual = target.to_condition().unwrap(); 537 | assert_eq!( 538 | actual, 539 | ( 540 | false, 541 | Condition::Not(Box::new(Condition::Keyword("AAA".into()))), 542 | false 543 | ) 544 | ) 545 | } 546 | 547 | #[test] 548 | fn test_query_to_condition_only_one_negative_phrase_keyword() { 549 | let target = Query::new("-\"AAA BBB\"".into()); 550 | let actual = target.to_condition().unwrap(); 551 | assert_eq!( 552 | actual, 553 | ( 554 | false, 555 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA BBB".into()))), 556 | false 557 | ) 558 | ) 559 | } 560 | 561 | #[test] 562 | fn test_query_to_condition_only_one_negative_phrase_keyword_include_special_word() { 563 | let target = Query::new("-\" NP1 and NP2 -(NP3 or NP4) \"".into()); 564 | let actual = target.to_condition().unwrap(); 565 | assert_eq!( 566 | actual, 567 | ( 568 | false, 569 | Condition::Not(Box::new(Condition::PhraseKeyword( 570 | " NP1 and NP2 -(NP3 or NP4) ".into() 571 | ))), 572 | false 573 | ) 574 | ) 575 | } 576 | 577 | #[test] 578 | fn test_query_to_condition_only_one_negative_phrase_keyword_include_full_width_special_word( 579 | ) { 580 | let target = 581 | Query::new("-\" NP1 and NP2 −(NP3 or NP4) \"".into()); 582 | let actual = target.to_condition().unwrap(); 583 | assert_eq!( 584 | actual, 585 | ( 586 | false, 587 | Condition::Not(Box::new(Condition::PhraseKeyword( 588 | " NP1 and NP2 −(NP3 or NP4) ".into() 589 | ))), 590 | false 591 | ) 592 | ) 593 | } 594 | 595 | #[test] 596 | fn test_query_to_condition_ten_negative_phrase_keywords() { 597 | let target = Query::new("-\"AAA1\" -\"AAA2\" -\"AAA3\" -\"AAA4\" -\"AAA5\" -\"AAA6\" -\"AAA7\" -\"AAA8\" -\"AAA9\" -\"AAA10\"".into()); 598 | let actual = target.to_condition().unwrap(); 599 | assert_eq!( 600 | actual, 601 | ( 602 | false, 603 | Condition::Operator( 604 | Operator::And, 605 | vec![ 606 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA1".into()))), 607 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA2".into()))), 608 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA3".into()))), 609 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA4".into()))), 610 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA5".into()))), 611 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA6".into()))), 612 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA7".into()))), 613 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA8".into()))), 614 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA9".into()))), 615 | Condition::Not(Box::new(Condition::PhraseKeyword("AAA10".into()))), 616 | ] 617 | ), 618 | false 619 | ) 620 | ) 621 | } 622 | 623 | #[test] 624 | fn test_query_to_condition_two_keywords() { 625 | let target = Query::new("AAA BBB".into()); 626 | let actual = target.to_condition().unwrap(); 627 | assert_eq!( 628 | actual, 629 | ( 630 | false, 631 | Condition::Operator( 632 | Operator::And, 633 | vec![ 634 | Condition::Keyword("AAA".into()), 635 | Condition::Keyword("BBB".into()) 636 | ] 637 | ), 638 | false 639 | ) 640 | ) 641 | } 642 | 643 | #[test] 644 | fn test_query_to_condition_two_phrase_keywords() { 645 | let target = Query::new("\"AAA BBB\" \"CCC DDD\"".into()); 646 | let actual = target.to_condition().unwrap(); 647 | assert_eq!( 648 | actual, 649 | ( 650 | false, 651 | Condition::Operator( 652 | Operator::And, 653 | vec![ 654 | Condition::PhraseKeyword("AAA BBB".into()), 655 | Condition::PhraseKeyword("CCC DDD".into()) 656 | ] 657 | ), 658 | false 659 | ) 660 | ) 661 | } 662 | 663 | #[test] 664 | fn test_query_to_condition_two_negative_keywords() { 665 | let target = Query::new("-AAA -BBB".into()); 666 | let actual = target.to_condition().unwrap(); 667 | assert_eq!( 668 | actual, 669 | ( 670 | false, 671 | Condition::Operator( 672 | Operator::And, 673 | vec![ 674 | Condition::Not(Box::new(Condition::Keyword("AAA".into()))), 675 | Condition::Not(Box::new(Condition::Keyword("BBB".into()))) 676 | ] 677 | ), 678 | false 679 | ) 680 | ) 681 | } 682 | 683 | #[test] 684 | fn test_query_to_condition_two_negative_phrase_keywords() { 685 | let target = Query::new("-\"AAA BBB\" -\"CCC DDD\"".into()); 686 | let actual = target.to_condition().unwrap(); 687 | assert_eq!( 688 | actual, 689 | ( 690 | false, 691 | Condition::Operator( 692 | Operator::And, 693 | vec![ 694 | Condition::Not(Box::new(Condition::PhraseKeyword( 695 | "AAA BBB".into() 696 | ))), 697 | Condition::Not(Box::new(Condition::PhraseKeyword( 698 | "CCC DDD".into() 699 | ))) 700 | ] 701 | ), 702 | false 703 | ) 704 | ) 705 | } 706 | 707 | #[test] 708 | fn test_query_to_condition_multi_keywords() { 709 | let target = Query::new("AAA \"BBB\" -\"CCC\" -DDD".into()); 710 | let actual = target.to_condition().unwrap(); 711 | assert_eq!( 712 | actual, 713 | ( 714 | false, 715 | Condition::Operator( 716 | Operator::And, 717 | vec![ 718 | Condition::Keyword("AAA".into()), 719 | Condition::PhraseKeyword("BBB".into()), 720 | Condition::Not(Box::new(Condition::PhraseKeyword("CCC".into()))), 721 | Condition::Not(Box::new(Condition::Keyword("DDD".into()))) 722 | ] 723 | ), 724 | false 725 | ) 726 | ) 727 | } 728 | 729 | #[test] 730 | fn test_query_to_condition_multi_keywords_without_space() { 731 | let target = 732 | Query::new("AAA\"BBB\"\"bbb\"-\"CCC\"-\"ccc\"-DDD".into()); 733 | let actual = target.to_condition().unwrap(); 734 | assert_eq!( 735 | actual, 736 | ( 737 | false, 738 | Condition::Operator( 739 | Operator::And, 740 | vec![ 741 | Condition::Keyword("AAA".into()), 742 | Condition::PhraseKeyword("BBB".into()), 743 | Condition::PhraseKeyword("bbb".into()), 744 | Condition::Not(Box::new(Condition::PhraseKeyword("CCC".into()))), 745 | Condition::Not(Box::new(Condition::PhraseKeyword("ccc".into()))), 746 | Condition::Not(Box::new(Condition::Keyword("DDD".into()))) 747 | ] 748 | ), 749 | false 750 | ) 751 | ) 752 | } 753 | 754 | #[test] 755 | fn test_query_to_condition_two_keywords_with_or() { 756 | let target = Query::new("AAA or BBB".into()); 757 | let actual = target.to_condition().unwrap(); 758 | assert_eq!( 759 | actual, 760 | ( 761 | false, 762 | Condition::Operator( 763 | Operator::Or, 764 | vec![ 765 | Condition::Keyword("AAA".into()), 766 | Condition::Keyword("BBB".into()) 767 | ] 768 | ), 769 | false 770 | ) 771 | ) 772 | } 773 | 774 | #[test] 775 | fn test_query_to_condition_two_phrase_keywords_with_or() { 776 | let target = Query::new("\"AAA BBB\" or \"CCC DDD\"".into()); 777 | let actual = target.to_condition().unwrap(); 778 | assert_eq!( 779 | actual, 780 | ( 781 | false, 782 | Condition::Operator( 783 | Operator::Or, 784 | vec![ 785 | Condition::PhraseKeyword("AAA BBB".into()), 786 | Condition::PhraseKeyword("CCC DDD".into()) 787 | ] 788 | ), 789 | false 790 | ) 791 | ) 792 | } 793 | 794 | #[test] 795 | fn test_query_to_condition_two_negative_keywords_with_or() { 796 | let target = Query::new("-AAA or -BBB".into()); 797 | let actual = target.to_condition().unwrap(); 798 | assert_eq!( 799 | actual, 800 | ( 801 | false, 802 | Condition::Operator( 803 | Operator::Or, 804 | vec![ 805 | Condition::Not(Box::new(Condition::Keyword("AAA".into()))), 806 | Condition::Not(Box::new(Condition::Keyword("BBB".into()))) 807 | ] 808 | ), 809 | false 810 | ) 811 | ) 812 | } 813 | 814 | #[test] 815 | fn test_query_to_condition_two_negative_phrase_keywords_with_or() { 816 | let target = Query::new("-\"AAA BBB\" or -\"CCC DDD\"".into()); 817 | let actual = target.to_condition().unwrap(); 818 | assert_eq!( 819 | actual, 820 | ( 821 | false, 822 | Condition::Operator( 823 | Operator::Or, 824 | vec![ 825 | Condition::Not(Box::new(Condition::PhraseKeyword( 826 | "AAA BBB".into() 827 | ))), 828 | Condition::Not(Box::new(Condition::PhraseKeyword( 829 | "CCC DDD".into() 830 | ))) 831 | ] 832 | ), 833 | false 834 | ) 835 | ) 836 | } 837 | 838 | #[test] 839 | fn test_query_to_condition_two_keywords_with_double_or() { 840 | let target = Query::new("AAA or or BBB".into()); 841 | let actual = target.to_condition().unwrap(); 842 | assert_eq!( 843 | actual, 844 | ( 845 | false, 846 | Condition::Operator( 847 | Operator::Or, 848 | vec![ 849 | Condition::Keyword("AAA".into()), 850 | Condition::Keyword("BBB".into()) 851 | ] 852 | ), 853 | false 854 | ) 855 | ) 856 | } 857 | 858 | #[test] 859 | fn test_query_to_condition_two_keywords_with_and() { 860 | let target = Query::new("AAA and BBB".into()); 861 | let actual = target.to_condition().unwrap(); 862 | assert_eq!( 863 | actual, 864 | ( 865 | false, 866 | Condition::Operator( 867 | Operator::And, 868 | vec![ 869 | Condition::Keyword("AAA".into()), 870 | Condition::Keyword("BBB".into()) 871 | ] 872 | ), 873 | false 874 | ) 875 | ) 876 | } 877 | 878 | #[test] 879 | fn test_query_to_condition_two_phrase_keywords_with_and() { 880 | let target = Query::new("\"AAA BBB\" and \"CCC DDD\"".into()); 881 | let actual = target.to_condition().unwrap(); 882 | assert_eq!( 883 | actual, 884 | ( 885 | false, 886 | Condition::Operator( 887 | Operator::And, 888 | vec![ 889 | Condition::PhraseKeyword("AAA BBB".into()), 890 | Condition::PhraseKeyword("CCC DDD".into()) 891 | ] 892 | ), 893 | false 894 | ) 895 | ) 896 | } 897 | 898 | #[test] 899 | fn test_query_to_condition_two_negative_keywords_with_and() { 900 | let target = Query::new("-AAA and -BBB".into()); 901 | let actual = target.to_condition().unwrap(); 902 | assert_eq!( 903 | actual, 904 | ( 905 | false, 906 | Condition::Operator( 907 | Operator::And, 908 | vec![ 909 | Condition::Not(Box::new(Condition::Keyword("AAA".into()))), 910 | Condition::Not(Box::new(Condition::Keyword("BBB".into()))) 911 | ] 912 | ), 913 | false 914 | ) 915 | ) 916 | } 917 | 918 | #[test] 919 | fn test_query_to_condition_two_negative_phrase_keywords_with_and() { 920 | let target = Query::new("-\"AAA BBB\" and -\"CCC DDD\"".into()); 921 | let actual = target.to_condition().unwrap(); 922 | assert_eq!( 923 | actual, 924 | ( 925 | false, 926 | Condition::Operator( 927 | Operator::And, 928 | vec![ 929 | Condition::Not(Box::new(Condition::PhraseKeyword( 930 | "AAA BBB".into() 931 | ))), 932 | Condition::Not(Box::new(Condition::PhraseKeyword( 933 | "CCC DDD".into() 934 | ))) 935 | ] 936 | ), 937 | false 938 | ) 939 | ) 940 | } 941 | 942 | #[test] 943 | fn test_query_to_condition_two_keywords_with_double_and() { 944 | let target = Query::new("AAA and and BBB".into()); 945 | let actual = target.to_condition().unwrap(); 946 | assert_eq!( 947 | actual, 948 | ( 949 | false, 950 | Condition::Operator( 951 | Operator::And, 952 | vec![ 953 | Condition::Keyword("AAA".into()), 954 | Condition::Keyword("BBB".into()) 955 | ] 956 | ), 957 | false 958 | ) 959 | ) 960 | } 961 | 962 | #[test] 963 | fn test_query_to_condition_multi_keywords_with_or_and() { 964 | let target = Query::new( 965 | "AAA and BBB or CCC DDD and EEE or FFF or GGG HHH".into(), 966 | ); 967 | let actual = target.to_condition().unwrap(); 968 | assert_eq!( 969 | actual, 970 | ( 971 | false, 972 | Condition::Operator( 973 | Operator::Or, 974 | vec![ 975 | Condition::Operator( 976 | Operator::And, 977 | vec![ 978 | Condition::Keyword("AAA".into()), 979 | Condition::Keyword("BBB".into()) 980 | ] 981 | ), 982 | Condition::Operator( 983 | Operator::And, 984 | vec![ 985 | Condition::Keyword("CCC".into()), 986 | Condition::Keyword("DDD".into()), 987 | Condition::Keyword("EEE".into()) 988 | ] 989 | ), 990 | Condition::Keyword("FFF".into()), 991 | Condition::Operator( 992 | Operator::And, 993 | vec![ 994 | Condition::Keyword("GGG".into()), 995 | Condition::Keyword("HHH".into()) 996 | ] 997 | ), 998 | ] 999 | ), 1000 | false 1001 | ) 1002 | ) 1003 | } 1004 | 1005 | #[test] 1006 | fn test_query_to_condition_two_keywords_with_double_and_or() { 1007 | let target = Query::new("AAA and or and or BBB".into()); 1008 | let actual = target.to_condition().unwrap(); 1009 | assert_eq!( 1010 | actual, 1011 | ( 1012 | false, 1013 | Condition::Operator( 1014 | Operator::Or, 1015 | vec![ 1016 | Condition::Keyword("AAA".into()), 1017 | Condition::Keyword("BBB".into()) 1018 | ] 1019 | ), 1020 | false 1021 | ) 1022 | ) 1023 | } 1024 | 1025 | #[test] 1026 | fn test_query_to_condition_and_or_in_phrase_keyword() { 1027 | let target = Query::new( 1028 | "AAA \" and BBB or CCC and \" \" or DDD and EEE or \" FFF".into(), 1029 | ); 1030 | let actual = target.to_condition().unwrap(); 1031 | assert_eq!( 1032 | actual, 1033 | ( 1034 | false, 1035 | Condition::Operator( 1036 | Operator::And, 1037 | vec![ 1038 | Condition::Keyword("AAA".into()), 1039 | Condition::PhraseKeyword(" and BBB or CCC and ".into()), 1040 | Condition::PhraseKeyword(" or DDD and EEE or ".into()), 1041 | Condition::Keyword("FFF".into()), 1042 | ] 1043 | ), 1044 | false 1045 | ) 1046 | ) 1047 | } 1048 | 1049 | #[test] 1050 | fn test_query_to_condition_full_pattern() { 1051 | let target = Query::new(" AAA And -BBB AnD CorC ccc Or \"c1 and c2\" -\"c3 or c4\" DandD anD \" P1 and P2 -(P3 or P4) \" and -\" NP1 and NP2 -(NP3 or NP4) \" oR III and ".into()); 1052 | let actual = target.to_condition().unwrap(); 1053 | assert_eq!( 1054 | actual, 1055 | ( 1056 | false, 1057 | Condition::Operator( 1058 | Operator::Or, 1059 | vec![ 1060 | Condition::Operator( 1061 | Operator::And, 1062 | vec![ 1063 | Condition::Keyword("AAA".into()), 1064 | Condition::Not(Box::new(Condition::Keyword("BBB".into()))), 1065 | Condition::Keyword("CorC".into()), 1066 | Condition::Keyword("ccc".into()), 1067 | ] 1068 | ), 1069 | Condition::Operator( 1070 | Operator::And, 1071 | vec![ 1072 | Condition::PhraseKeyword("c1 and c2".into()), 1073 | Condition::Not(Box::new(Condition::PhraseKeyword( 1074 | "c3 or c4".into() 1075 | ))), 1076 | Condition::Keyword("DandD".into()), 1077 | Condition::PhraseKeyword( 1078 | " P1 and P2 -(P3 or P4) ".into() 1079 | ), 1080 | Condition::Not(Box::new(Condition::PhraseKeyword( 1081 | " NP1 and NP2 -(NP3 or NP4) ".into() 1082 | ))) 1083 | ] 1084 | ), 1085 | Condition::Keyword("III".into()), 1086 | ] 1087 | ), 1088 | false 1089 | ) 1090 | ) 1091 | } 1092 | 1093 | #[test] 1094 | fn test_query_to_condition_start_end_with_and() { 1095 | let target = Query::new("and AAA BBB and".into()); 1096 | let actual = target.to_condition().unwrap(); 1097 | assert_eq!( 1098 | actual, 1099 | ( 1100 | false, 1101 | Condition::Operator( 1102 | Operator::And, 1103 | vec![ 1104 | Condition::Keyword("AAA".into()), 1105 | Condition::Keyword("BBB".into()) 1106 | ] 1107 | ), 1108 | false 1109 | ) 1110 | ) 1111 | } 1112 | 1113 | #[test] 1114 | fn test_query_to_condition_start_end_with_and_with_space() { 1115 | let target = Query::new(" and AAA BBB and ".into()); 1116 | let actual = target.to_condition().unwrap(); 1117 | assert_eq!( 1118 | actual, 1119 | ( 1120 | false, 1121 | Condition::Operator( 1122 | Operator::And, 1123 | vec![ 1124 | Condition::Keyword("AAA".into()), 1125 | Condition::Keyword("BBB".into()) 1126 | ] 1127 | ), 1128 | false 1129 | ) 1130 | ) 1131 | } 1132 | 1133 | #[test] 1134 | fn test_query_to_condition_start_end_with_or() { 1135 | let target = Query::new("or AAA BBB or".into()); 1136 | let actual = target.to_condition().unwrap(); 1137 | assert_eq!( 1138 | actual, 1139 | ( 1140 | true, 1141 | Condition::Operator( 1142 | Operator::And, 1143 | vec![ 1144 | Condition::Keyword("AAA".into()), 1145 | Condition::Keyword("BBB".into()) 1146 | ] 1147 | ), 1148 | true 1149 | ) 1150 | ) 1151 | } 1152 | 1153 | #[test] 1154 | fn test_query_to_condition_start_end_with_or_with_space() { 1155 | let target = Query::new(" or AAA BBB or ".into()); 1156 | let actual = target.to_condition().unwrap(); 1157 | assert_eq!( 1158 | actual, 1159 | ( 1160 | true, 1161 | Condition::Operator( 1162 | Operator::And, 1163 | vec![ 1164 | Condition::Keyword("AAA".into()), 1165 | Condition::Keyword("BBB".into()) 1166 | ] 1167 | ), 1168 | true 1169 | ) 1170 | ) 1171 | } 1172 | 1173 | #[test] 1174 | fn test_query_to_condition_start_end_with_or_with_space_include_one_keyword() { 1175 | let target = Query::new(" or AAA or ".into()); 1176 | let actual = target.to_condition().unwrap(); 1177 | assert_eq!(actual, (true, Condition::Keyword("AAA".into()), true)) 1178 | } 1179 | 1180 | #[test] 1181 | fn test_query_to_condition_only_or() { 1182 | let target = Query::new("or".into()); 1183 | let actual = target.to_condition().unwrap(); 1184 | assert_eq!(actual, (true, Condition::None, true)) 1185 | } 1186 | 1187 | #[test] 1188 | fn test_query_to_condition_only_or_with_space() { 1189 | let target = Query::new(" or ".into()); 1190 | let actual = target.to_condition().unwrap(); 1191 | assert_eq!(actual, (true, Condition::None, true)) 1192 | } 1193 | } 1194 | } 1195 | --------------------------------------------------------------------------------