| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define C_LUCY_NORMALIZER |
| #define C_LUCY_TOKEN |
| #include <ctype.h> |
| #include "Lucy/Util/ToolSet.h" |
| |
| #include "Lucy/Analysis/Normalizer.h" |
| #include "Lucy/Analysis/Token.h" |
| #include "Lucy/Analysis/Inversion.h" |
| |
| #include "utf8proc.h" |
| |
| #define INITIAL_BUFSIZE 63 |
| |
| Normalizer* |
| Normalizer_new(const CharBuf *form, bool_t case_fold, bool_t strip_accents) { |
| Normalizer *self = (Normalizer*)VTable_Make_Obj(NORMALIZER); |
| return Normalizer_init(self, form, case_fold, strip_accents); |
| } |
| |
| Normalizer* |
| Normalizer_init(Normalizer *self, const CharBuf *form, bool_t case_fold, |
| bool_t strip_accents) { |
| int options = UTF8PROC_STABLE; |
| |
| if (form == NULL |
| || CB_Equals_Str(form, "NFKC", 4) || CB_Equals_Str(form, "nfkc", 4) |
| ) { |
| options |= UTF8PROC_COMPOSE | UTF8PROC_COMPAT; |
| } |
| else if (CB_Equals_Str(form, "NFC", 3) || CB_Equals_Str(form, "nfc", 3)) { |
| options |= UTF8PROC_COMPOSE; |
| } |
| else if (CB_Equals_Str(form, "NFKD", 4) || CB_Equals_Str(form, "nfkd", 4)) { |
| options |= UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT; |
| } |
| else if (CB_Equals_Str(form, "NFD", 3) || CB_Equals_Str(form, "nfd", 3)) { |
| options |= UTF8PROC_DECOMPOSE; |
| } |
| else { |
| THROW(ERR, "Invalid normalization form %o", form); |
| } |
| |
| if (case_fold) { options |= UTF8PROC_CASEFOLD; } |
| if (strip_accents) { options |= UTF8PROC_STRIPMARK; } |
| |
| self->options = options; |
| |
| return self; |
| } |
| |
| Inversion* |
| Normalizer_transform(Normalizer *self, Inversion *inversion) { |
| // allocate additional space because utf8proc_reencode adds a |
| // terminating null char |
| int32_t static_buffer[INITIAL_BUFSIZE + 1]; |
| int32_t *buffer = static_buffer; |
| ssize_t bufsize = INITIAL_BUFSIZE; |
| Token *token; |
| |
| while (NULL != (token = Inversion_Next(inversion))) { |
| ssize_t len = utf8proc_decompose((uint8_t*)token->text, token->len, |
| buffer, bufsize, self->options); |
| |
| if (len > bufsize) { |
| // buffer too small, (re)allocate |
| if (buffer != static_buffer) { |
| FREEMEM(buffer); |
| } |
| // allocate additional INITIAL_BUFSIZE items |
| bufsize = len + INITIAL_BUFSIZE; |
| buffer = (int32_t*)MALLOCATE((bufsize + 1) * sizeof(int32_t)); |
| len = utf8proc_decompose((uint8_t*)token->text, token->len, |
| buffer, bufsize, self->options); |
| } |
| if (len < 0) { |
| continue; |
| } |
| |
| len = utf8proc_reencode(buffer, len, self->options); |
| |
| if (len >= 0) { |
| if (len > token->len) { |
| FREEMEM(token->text); |
| token->text = (char*)MALLOCATE(len + 1); |
| } |
| memcpy(token->text, buffer, len + 1); |
| token->len = len; |
| } |
| } |
| |
| if (buffer != static_buffer) { |
| FREEMEM(buffer); |
| } |
| |
| Inversion_Reset(inversion); |
| return (Inversion*)INCREF(inversion); |
| } |
| |
| Hash* |
| Normalizer_dump(Normalizer *self) { |
| Normalizer_dump_t super_dump |
| = (Normalizer_dump_t)SUPER_METHOD(NORMALIZER, Normalizer, Dump); |
| Hash *dump = super_dump(self); |
| int options = self->options; |
| |
| CharBuf *form = options & UTF8PROC_COMPOSE ? |
| options & UTF8PROC_COMPAT ? |
| CB_new_from_trusted_utf8("NFKC", 4) : |
| CB_new_from_trusted_utf8("NFC", 3) : |
| options & UTF8PROC_COMPAT ? |
| CB_new_from_trusted_utf8("NFKD", 4) : |
| CB_new_from_trusted_utf8("NFD", 3); |
| |
| Hash_Store_Str(dump, "normalization_form", 18, (Obj*)form); |
| |
| BoolNum *case_fold = Bool_singleton(options & UTF8PROC_CASEFOLD); |
| Hash_Store_Str(dump, "case_fold", 9, (Obj*)case_fold); |
| |
| BoolNum *strip_accents = Bool_singleton(options & UTF8PROC_STRIPMARK); |
| Hash_Store_Str(dump, "strip_accents", 13, (Obj*)strip_accents); |
| |
| return dump; |
| } |
| |
| Normalizer* |
| Normalizer_load(Normalizer *self, Obj *dump) { |
| Normalizer_load_t super_load |
| = (Normalizer_load_t)SUPER_METHOD(NORMALIZER, Normalizer, Load); |
| Normalizer *loaded = super_load(self, dump); |
| Hash *source = (Hash*)CERTIFY(dump, HASH); |
| |
| Obj *obj = Hash_Fetch_Str(source, "normalization_form", 18); |
| CharBuf *form = (CharBuf*)CERTIFY(obj, CHARBUF); |
| obj = Hash_Fetch_Str(source, "case_fold", 9); |
| bool_t case_fold = Obj_To_Bool(CERTIFY(obj, OBJ)); |
| obj = Hash_Fetch_Str(source, "strip_accents", 13); |
| bool_t strip_accents = Obj_To_Bool(CERTIFY(obj, OBJ)); |
| |
| return Normalizer_init(loaded, form, case_fold, strip_accents); |
| } |
| |
| bool_t |
| Normalizer_equals(Normalizer *self, Obj *other) { |
| Normalizer *const twin = (Normalizer*)other; |
| if (twin == self) { return true; } |
| if (!Obj_Is_A(other, NORMALIZER)) { return false; } |
| if (twin->options != self->options) { return false; } |
| return true; |
| } |
| |
| |