Skip to content

Commit

Permalink
#96 add SQLite functionality, remove JSONs, bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewtavis committed Apr 15, 2023
1 parent d27f2f5 commit 3be5a59
Show file tree
Hide file tree
Showing 13 changed files with 524 additions and 261 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,17 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/).
### 🐞 Bug Fixes

- Fixed a bug where the keyboard's space bar text would change size while it was showing the language of the keyboard.
- Fixed a bug where the colors of Swedish and Russian nouns weren't being displayed because of checking original values rather than their localized equivalents.
- Fixed a bug where autocomplete options that should have been upper case would only be capitalized in cases where the prefix was itself upper case.

### ⚖️ Legal

- The text of the privacy policy was updated slightly for readability and also added information about data from Unicode CLDR for emoji suggestions and completions.

### ♻️ Code Refactoring

<!--- Scribe data is now loaded into SQLite database tables to make data reference less memory intensive and mitigate crashes.-->
<!--- All prior JSON data references have been replaced with database queries and JSON language data files have been removed.-->
- Scribe data is now loaded into SQLite database tables to make data reference less memory intensive and mitigate crashes.
- All prior JSON data references have been replaced with database queries and JSON language data files have been removed.
- GRDB.swift was added to the dependencies.

# Scribe-iOS 2.1.0
Expand Down
4 changes: 2 additions & 2 deletions Keyboards/KeyboardsBase/Colors/ScribeColor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import UIKit

/// All the colors defined in `Assets.xcassets/Colors`
enum ScribeColor: String {
case annotateRed
case annotateBlue
case annotatePurple
case annotateGreen
case annotateOrange
case annotatePurple
case annotateRed
case annotateTitle
case commandBar
case commandBarBorder
Expand Down
209 changes: 133 additions & 76 deletions Keyboards/KeyboardsBase/KeyboardViewController.swift

Large diffs are not rendered by default.

266 changes: 266 additions & 0 deletions Keyboards/KeyboardsBase/LoadData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import Foundation
import SwiftyJSON
import GRDB


/// Loads a JSON file that contains grammatical information into a dictionary.
///
Expand All @@ -17,3 +19,267 @@ func loadJSON(filename fileName: String) -> JSON {
let jsonData = try! JSON(data: data! as Data)
return jsonData
}


/// Makes a connection to the language database given the value for controllerLanguage.
func openDBQueue() -> DatabaseQueue {
let dbName = "\(String(describing: get_iso_code(keyboardLanguage: controllerLanguage).uppercased()))LanguageData"
let dbPath = Bundle.main.path(forResource: dbName, ofType: "sqlite")!
let db = try! DatabaseQueue(
path: dbPath
)

return db
}


// Variable to be replaced with the result of openDBQueue.
var languageDB = try! DatabaseQueue()


/// Returns a row from the language database given a query and arguemtns.
///
/// - Parameters
/// - query: the query to run against the language database.
/// - outputCols: the columns from which the values should come.
/// - args: arguments to pass to `query`.
func queryDBRow(query: String, outputCols: [String], args: [String]) -> [String] {
var outputValues = [String]()
do {
try languageDB.read { db in
if let row = try Row.fetchOne(db, sql: query, arguments: StatementArguments(args)) {
for col in outputCols {
outputValues.append(row[col])
}
}
}
} catch let error as DatabaseError {
let errorMessage = error.message
let errorSQL = error.sql
let errorArguments = error.arguments
print(
"An error '\(String(describing: errorMessage))' occured in the query: \(String(describing: errorSQL)) (\(String(describing: errorArguments)))"
)
} catch {}

if outputValues == [String]() {
// Append an empty string so that we can check for it and trigger commandState = .invalid.
outputValues.append("")
}

return outputValues
}


/// Writes a row of a language database table given a query and arguemtns.
///
/// - Parameters
/// - query: the query to run against the language database.
/// - args: arguments to pass to `query`.
func writeDBRow(query: String, args: StatementArguments) {
do {
try languageDB.write { db in
try db.execute(
sql: query,
arguments: args
)
}
} catch let error as DatabaseError {
let errorMessage = error.message
let errorSQL = error.sql
let errorArguments = error.arguments
print(
"An error '\(String(describing: errorMessage))' occured in the query: \(String(describing: errorSQL)) (\(String(describing: errorArguments)))"
)
} catch {}
}


/// Performs a few minor edits to language data to make sure that certain values are included.
func expandLanguageDataset() {
// Make sure that Scribe shows up in auto actions.
let checkScribeQuery = "SELECT * FROM nouns WHERE noun = ?"
let args = ["Scribe"]
let outputCols = ["noun"]
let scribeOrEmptyString = queryDBRow(query: checkScribeQuery, outputCols: outputCols, args: args)[0]

if scribeOrEmptyString == "" {
let addScribeQuery = "INSERT INTO nouns (noun, plural, form) VALUES (?, ?, ?)"
writeDBRow(query: addScribeQuery, args: ["Scribe", "Scribes", ""])
writeDBRow(query: addScribeQuery, args: ["Scribes", "isPlural", "PL"])
}

// Add German compound prepositions to the prepositions table so they also receive annotations.
if controllerLanguage == "German" {
let query = "INSERT INTO prepositions (preposition, form) VALUES (?, ?)"
for (p, f) in contractedGermanPrepositions {
writeDBRow(query: query, args: [p, f])
}
}
}


/// Creates a table in the language database from which autocompletions will be drawn.
/// Note: this function also calls expandLanguageDataset prior to creating the lexicon.
///
/// - Steps:
/// - Create a base lexicon of noun, preposition, autosuggestion and emoji keys.
/// - Remove words that have capitalized or upper case versions in the nouns table to make sure that just these versions are in autocomplete.
/// - Filter out words that are less than three characters, numbers and hyphenated words.
func createAutocompleteLexicon() {
expandLanguageDataset()

// Dropping the table before it's made to reset the values in case they change (potentially new data).
let dropLexiconTableQuery = "DROP TABLE IF EXISTS autocomplete_lexicon"
do {
try languageDB.write { db in
try db.execute(sql: dropLexiconTableQuery)
}
} catch let error as DatabaseError {
let errorMessage = error.message
let errorSQL = error.sql
print(
"An error '\(String(describing: errorMessage))' occured in the query: \(String(describing: errorSQL))"
)
} catch {}

let createLexiconTableQuery = "CREATE TABLE IF NOT EXISTS autocomplete_lexicon (word Text)"
do {
try languageDB.write { db in
try db.execute(sql: createLexiconTableQuery)
}
} catch let error as DatabaseError {
let errorMessage = error.message
let errorSQL = error.sql
print(
"An error '\(String(describing: errorMessage))' occured in the query: \(String(describing: errorSQL))"
)
} catch {}

let createLexiconQuery = """
INSERT INTO autocomplete_lexicon (word)
SELECT DISTINCT
-- Select an upper case or capitalized noun if it's available.
CASE
WHEN
UPPER(full_lexicon.word) = nouns.noun
THEN
nouns.noun
WHEN
UPPER(SUBSTR(full_lexicon.word, 1, 1)) || SUBSTR(full_lexicon.word, 2) = nouns.noun
THEN
nouns.noun
ELSE
full_lexicon.word
END
FROM (
SELECT
noun AS word
FROM
nouns
UNION
SELECT
preposition AS word
FROM
prepositions
UNION
SELECT
word AS word
FROM
autosuggestions
UNION
SELECT
word AS word
FROM
emoji_keywords
) AS full_lexicon
LEFT JOIN
nouns
ON
full_lexicon.word = nouns.noun
WHERE
LENGTH(full_lexicon.word) > 2
AND full_lexicon.word NOT LIKE '%[0-9]%'
AND full_lexicon.word NOT LIKE '%-%'
"""
do {
try languageDB.write { db in
try db.execute(sql: createLexiconQuery)
}
} catch let error as DatabaseError {
let errorMessage = error.message
let errorSQL = error.sql
print(
"An error '\(String(describing: errorMessage))' occured in the query: \(String(describing: errorSQL))"
)
} catch {}

// Note: the following lines are for checking the total of the autocomplete lexicon if needed.
// let checkLexiconTotal = "SELECT CAST(COUNT(?) AS Text) AS word_count FROM autocomplete_lexicon"
// let args = ["words"]
// let outputCols = ["word_count"]
// let lexicon_count = queryDBRow(query: checkLexiconTotal, outputCols: outputCols, args: args)[0]
}


/// Returns the next three words in the `autocomplete_lexicon` that follow a given word.
///
/// - Parameters
/// - word: the word that autosuggestions should be returned for.
func queryAutocompletions(word: String) -> [String] {
var autocompletions = [String]()

let autocompletionsQuery = """
SELECT
word
FROM
autocomplete_lexicon
WHERE
LOWER(word) LIKE ?
ORDER BY
word COLLATE NOCASE ASC
LIMIT
3
"""
let patterns = ["\(word.lowercased())%"]

do {
let rows = try languageDB.read { db in
try Row.fetchAll(db, sql: autocompletionsQuery, arguments: StatementArguments(patterns))
}
for r in rows {
autocompletions.append(r["word"])
}
} catch let error as DatabaseError {
let errorMessage = error.message
let errorSQL = error.sql
let errorArguments = error.arguments
print(
"An error '\(String(describing: errorMessage))' occured in the query: \(String(describing: errorSQL)) (\(String(describing: errorArguments)))"
)
} catch {}

if autocompletions == [String]() {
// Append an empty string so that we can check for it and trigger nothing being shown.
autocompletions.append("")
}

return autocompletions
}
Loading

0 comments on commit 3be5a59

Please sign in to comment.