Prepare dictionary file creating in native code.
Bug: 6669677 Change-Id: I7c476a6e99ec7ac883f05d84ef306265255154damain
parent
989596844e
commit
1592eb8a3a
|
@ -85,6 +85,7 @@ LATIN_IME_CORE_SRC_FILES := \
|
||||||
$(addprefix suggest/policyimpl/dictionary/utils/, \
|
$(addprefix suggest/policyimpl/dictionary/utils/, \
|
||||||
buffer_with_extendable_buffer.cpp \
|
buffer_with_extendable_buffer.cpp \
|
||||||
byte_array_utils.cpp \
|
byte_array_utils.cpp \
|
||||||
|
dict_file_writing_utils.cpp \
|
||||||
format_utils.cpp) \
|
format_utils.cpp) \
|
||||||
suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
|
suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
|
||||||
$(addprefix suggest/policyimpl/typing/, \
|
$(addprefix suggest/policyimpl/typing/, \
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
|
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
|
#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
|
||||||
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
|
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
|
||||||
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
|
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
|
||||||
|
@ -28,13 +25,12 @@
|
||||||
#include "suggest/policyimpl/dictionary/header/header_policy.h"
|
#include "suggest/policyimpl/dictionary/header/header_policy.h"
|
||||||
#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
|
#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
|
||||||
#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
|
#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
|
||||||
|
#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
|
||||||
#include "utils/hash_map_compat.h"
|
#include "utils/hash_map_compat.h"
|
||||||
|
|
||||||
namespace latinime {
|
namespace latinime {
|
||||||
|
|
||||||
const int DynamicPatriciaTrieWritingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
|
const int DynamicPatriciaTrieWritingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
|
||||||
const char *const DynamicPatriciaTrieWritingHelper::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE =
|
|
||||||
".tmp";
|
|
||||||
// TODO: Make MAX_DICTIONARY_SIZE 8MB.
|
// TODO: Make MAX_DICTIONARY_SIZE 8MB.
|
||||||
const size_t DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE = 2 * 1024 * 1024;
|
const size_t DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
@ -147,7 +143,7 @@ void DynamicPatriciaTrieWritingHelper::writeToDictFile(const char *const fileNam
|
||||||
if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */)) {
|
if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
flushAllToFile(fileName, &headerBuffer, mBuffer);
|
DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, mBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos,
|
void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos,
|
||||||
|
@ -161,7 +157,7 @@ void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNod
|
||||||
if (!runGC(rootPtNodeArrayPos, &newDictBuffer)) {
|
if (!runGC(rootPtNodeArrayPos, &newDictBuffer)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
flushAllToFile(fileName, &headerBuffer, &newDictBuffer);
|
DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, &newDictBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DynamicPatriciaTrieWritingHelper::markNodeAsDeleted(
|
bool DynamicPatriciaTrieWritingHelper::markNodeAsDeleted(
|
||||||
|
@ -463,60 +459,6 @@ bool DynamicPatriciaTrieWritingHelper::reallocatePtNodeAndAddNewPtNodes(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Create a struct which contains header, body and etc... and use here as an argument.
|
|
||||||
void DynamicPatriciaTrieWritingHelper::flushAllToFile(const char *const fileName,
|
|
||||||
BufferWithExtendableBuffer *const dictHeader,
|
|
||||||
BufferWithExtendableBuffer *const dictBody) const {
|
|
||||||
const int tmpFileNameBufSize = strlen(fileName)
|
|
||||||
+ strlen(TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE) + 1 /* terminator */;
|
|
||||||
// Name of a temporary file used for writing that is a connected string of original name and
|
|
||||||
// TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE.
|
|
||||||
char tmpFileName[tmpFileNameBufSize];
|
|
||||||
snprintf(tmpFileName, tmpFileNameBufSize, "%s%s", fileName,
|
|
||||||
TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE);
|
|
||||||
FILE *const file = fopen(tmpFileName, "wb");
|
|
||||||
if (!file) {
|
|
||||||
AKLOGI("Dictionary file %s cannnot be opened.", tmpFileName);
|
|
||||||
ASSERT(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Write the dictionary header.
|
|
||||||
if (!writeBufferToFilePointer(file, dictHeader)) {
|
|
||||||
remove(tmpFileName);
|
|
||||||
AKLOGI("Dictionary header cannnot be written. size: %d", dictHeader->getTailPosition());
|
|
||||||
ASSERT(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Write the dictionary body.
|
|
||||||
if (!writeBufferToFilePointer(file, dictBody)) {
|
|
||||||
remove(tmpFileName);
|
|
||||||
AKLOGI("Dictionary body cannnot be written. size: %d", dictBody->getTailPosition());
|
|
||||||
ASSERT(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fclose(file);
|
|
||||||
rename(tmpFileName, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This closes file pointer when an error is caused and returns whether the writing was succeeded
|
|
||||||
// or not.
|
|
||||||
bool DynamicPatriciaTrieWritingHelper::writeBufferToFilePointer(FILE *const file,
|
|
||||||
const BufferWithExtendableBuffer *const buffer) const {
|
|
||||||
const int originalBufSize = buffer->getOriginalBufferSize();
|
|
||||||
if (originalBufSize > 0 && fwrite(buffer->getBuffer(false /* usesAdditionalBuffer */),
|
|
||||||
originalBufSize, 1, file) < 1) {
|
|
||||||
fclose(file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const int additionalBufSize = buffer->getTailPosition() - buffer->getOriginalBufferSize();
|
|
||||||
if (additionalBufSize > 0 && fwrite(buffer->getBuffer(true /* usesAdditionalBuffer */),
|
|
||||||
additionalBufSize, 1, file) < 1) {
|
|
||||||
fclose(file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DynamicPatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
|
bool DynamicPatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
|
||||||
BufferWithExtendableBuffer *const bufferToWrite) {
|
BufferWithExtendableBuffer *const bufferToWrite) {
|
||||||
DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
|
DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
|
#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
|
||||||
#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
|
#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
@ -85,7 +84,6 @@ class DynamicPatriciaTrieWritingHelper {
|
||||||
DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
|
DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
|
||||||
|
|
||||||
static const int CHILDREN_POSITION_FIELD_SIZE;
|
static const int CHILDREN_POSITION_FIELD_SIZE;
|
||||||
static const char *const TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE;
|
|
||||||
static const size_t MAX_DICTIONARY_SIZE;
|
static const size_t MAX_DICTIONARY_SIZE;
|
||||||
|
|
||||||
BufferWithExtendableBuffer *const mBuffer;
|
BufferWithExtendableBuffer *const mBuffer;
|
||||||
|
@ -124,13 +122,6 @@ class DynamicPatriciaTrieWritingHelper {
|
||||||
const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
|
const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
|
||||||
const int newNodeCodePointCount);
|
const int newNodeCodePointCount);
|
||||||
|
|
||||||
void flushAllToFile(const char *const fileName,
|
|
||||||
BufferWithExtendableBuffer *const dictHeader,
|
|
||||||
BufferWithExtendableBuffer *const dictBody) const;
|
|
||||||
|
|
||||||
bool writeBufferToFilePointer(FILE *const file,
|
|
||||||
const BufferWithExtendableBuffer *const buffer) const;
|
|
||||||
|
|
||||||
bool runGC(const int rootPtNodeArrayPos, BufferWithExtendableBuffer *const bufferToWrite);
|
bool runGC(const int rootPtNodeArrayPos, BufferWithExtendableBuffer *const bufferToWrite);
|
||||||
};
|
};
|
||||||
} // namespace latinime
|
} // namespace latinime
|
||||||
|
|
|
@ -36,6 +36,16 @@ const int DynamicPatriciaTrieWritingUtils::DICT_OFFSET_NEGATIVE_FLAG = 0x800000;
|
||||||
const int DynamicPatriciaTrieWritingUtils::PROBABILITY_FIELD_SIZE = 1;
|
const int DynamicPatriciaTrieWritingUtils::PROBABILITY_FIELD_SIZE = 1;
|
||||||
const int DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE = 1;
|
const int DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE = 1;
|
||||||
|
|
||||||
|
/* static */ bool DynamicPatriciaTrieWritingUtils::writeEmptyDictionary(
|
||||||
|
BufferWithExtendableBuffer *const buffer, const int rootPos) {
|
||||||
|
int writingPos = rootPos;
|
||||||
|
if (!writePtNodeArraySizeAndAdvancePosition(buffer, 0 /* arraySize */, &writingPos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return writeForwardLinkPositionAndAdvancePosition(buffer, NOT_A_DICT_POS /* forwardLinkPos */,
|
||||||
|
&writingPos);
|
||||||
|
}
|
||||||
|
|
||||||
/* static */ bool DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
|
/* static */ bool DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
|
||||||
BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
|
BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
|
||||||
int *const forwardLinkFieldPos) {
|
int *const forwardLinkFieldPos) {
|
||||||
|
|
|
@ -30,6 +30,8 @@ class DynamicPatriciaTrieWritingUtils {
|
||||||
public:
|
public:
|
||||||
static const int NODE_FLAG_FIELD_SIZE;
|
static const int NODE_FLAG_FIELD_SIZE;
|
||||||
|
|
||||||
|
static bool writeEmptyDictionary(BufferWithExtendableBuffer *const buffer, const int rootPos);
|
||||||
|
|
||||||
static bool writeForwardLinkPositionAndAdvancePosition(
|
static bool writeForwardLinkPositionAndAdvancePosition(
|
||||||
BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
|
BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
|
||||||
int *const forwardLinkFieldPos);
|
int *const forwardLinkFieldPos);
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "suggest/policyimpl/dictionary/header/header_policy.h"
|
||||||
|
#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
|
||||||
|
#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
|
||||||
|
#include "suggest/policyimpl/dictionary/utils/format_utils.h"
|
||||||
|
|
||||||
|
namespace latinime {
|
||||||
|
|
||||||
|
const char *const DictFileWritingUtils::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE = ".tmp";
|
||||||
|
|
||||||
|
/* static */ bool DictFileWritingUtils::createEmptyDictFile(const char *const filePath,
|
||||||
|
const int dictVersion, const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
|
||||||
|
switch (dictVersion) {
|
||||||
|
case 3:
|
||||||
|
return createEmptyV3DictFile(filePath, attributeMap);
|
||||||
|
default:
|
||||||
|
// Only version 3 dictionary is supported for now.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */ bool DictFileWritingUtils::createEmptyV3DictFile(const char *const filePath,
|
||||||
|
const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
|
||||||
|
BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
|
||||||
|
HeaderPolicy headerPolicy(FormatUtils::VERSION_3, attributeMap);
|
||||||
|
headerPolicy.writeHeaderToBuffer(&headerBuffer, true /* updatesLastUpdatedTime */);
|
||||||
|
BufferWithExtendableBuffer bodyBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
|
||||||
|
if (!DynamicPatriciaTrieWritingUtils::writeEmptyDictionary(&bodyBuffer, 0 /* rootPos */)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return flushAllHeaderAndBodyToFile(filePath, &headerBuffer, &bodyBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */ bool DictFileWritingUtils::flushAllHeaderAndBodyToFile(const char *const filePath,
|
||||||
|
BufferWithExtendableBuffer *const dictHeader, BufferWithExtendableBuffer *const dictBody) {
|
||||||
|
const int tmpFileNameBufSize = strlen(filePath)
|
||||||
|
+ strlen(TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE) + 1 /* terminator */;
|
||||||
|
// Name of a temporary file used for writing that is a connected string of original name and
|
||||||
|
// TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE.
|
||||||
|
char tmpFileName[tmpFileNameBufSize];
|
||||||
|
snprintf(tmpFileName, tmpFileNameBufSize, "%s%s", filePath,
|
||||||
|
TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE);
|
||||||
|
FILE *const file = fopen(tmpFileName, "wb");
|
||||||
|
if (!file) {
|
||||||
|
AKLOGE("Dictionary file %s cannnot be opened.", tmpFileName);
|
||||||
|
ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Write the dictionary header.
|
||||||
|
if (!writeBufferToFile(file, dictHeader)) {
|
||||||
|
remove(tmpFileName);
|
||||||
|
AKLOGE("Dictionary header cannnot be written. size: %d", dictHeader->getTailPosition());
|
||||||
|
ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Write the dictionary body.
|
||||||
|
if (!writeBufferToFile(file, dictBody)) {
|
||||||
|
remove(tmpFileName);
|
||||||
|
AKLOGE("Dictionary body cannnot be written. size: %d", dictBody->getTailPosition());
|
||||||
|
ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
rename(tmpFileName, filePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This closes file pointer when an error is caused and returns whether the writing was succeeded
|
||||||
|
// or not.
|
||||||
|
/* static */ bool DictFileWritingUtils::writeBufferToFile(FILE *const file,
|
||||||
|
const BufferWithExtendableBuffer *const buffer) {
|
||||||
|
const int originalBufSize = buffer->getOriginalBufferSize();
|
||||||
|
if (originalBufSize > 0 && fwrite(buffer->getBuffer(false /* usesAdditionalBuffer */),
|
||||||
|
originalBufSize, 1, file) < 1) {
|
||||||
|
fclose(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int additionalBufSize = buffer->getTailPosition() - buffer->getOriginalBufferSize();
|
||||||
|
if (additionalBufSize > 0 && fwrite(buffer->getBuffer(true /* usesAdditionalBuffer */),
|
||||||
|
additionalBufSize, 1, file) < 1) {
|
||||||
|
fclose(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace latinime
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LATINIME_DICT_FILE_WRITING_UTILS_H
|
||||||
|
#define LATINIME_DICT_FILE_WRITING_UTILS_H
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
#include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
|
||||||
|
|
||||||
|
namespace latinime {
|
||||||
|
|
||||||
|
class BufferWithExtendableBuffer;
|
||||||
|
|
||||||
|
class DictFileWritingUtils {
|
||||||
|
public:
|
||||||
|
static bool createEmptyDictFile(const char *const filePath, const int dictVersion,
|
||||||
|
const HeaderReadWriteUtils::AttributeMap *const attributeMap);
|
||||||
|
|
||||||
|
static bool flushAllHeaderAndBodyToFile(const char *const filePath,
|
||||||
|
BufferWithExtendableBuffer *const dictHeader,
|
||||||
|
BufferWithExtendableBuffer *const dictBody);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_IMPLICIT_CONSTRUCTORS(DictFileWritingUtils);
|
||||||
|
|
||||||
|
static const char *const TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE;
|
||||||
|
|
||||||
|
static bool createEmptyV3DictFile(const char *const filePath,
|
||||||
|
const HeaderReadWriteUtils::AttributeMap *const attributeMap);
|
||||||
|
|
||||||
|
static bool writeBufferToFile(FILE *const file,
|
||||||
|
const BufferWithExtendableBuffer *const buffer);
|
||||||
|
};
|
||||||
|
} // namespace latinime
|
||||||
|
#endif /* LATINIME_DICT_FILE_WRITING_UTILS_H */
|
Loading…
Reference in New Issue