package maigosoft.mcpdict; import java.util.ArrayList; import java.util.List; import java.util.Locale; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.preference.PreferenceManager; import com.readystatesoftware.sqliteasset.SQLiteAssetHelper; public class MCPDatabase extends SQLiteAssetHelper { private static final String DATABASE_NAME = "mcpdict"; private static final int DATABASE_VERSION = 9; // Must be the same order as defined in the string array "search_as" public static final int SEARCH_AS_HZ = 0; public static final int SEARCH_AS_MC = 1; public static final int SEARCH_AS_PU = 2; public static final int SEARCH_AS_CT = 3; public static final int SEARCH_AS_SH = 4; public static final int SEARCH_AS_MN = 5; public static final int SEARCH_AS_KR = 6; public static final int SEARCH_AS_VN = 7; public static final int SEARCH_AS_JP_GO = 8; public static final int SEARCH_AS_JP_KAN = 9; public static final int SEARCH_AS_JP_ANY = 10; private static final String[] SEARCH_AS_TO_COLUMN_NAME = { "unicode", "mc", "pu", "ct", "sh", "mn", "kr", "vn", "jp_go", "jp_kan", null }; private static Context context; private static SQLiteDatabase db = null; public static void initialize(Context c) { if (db != null) return; context = c; db = new MCPDatabase(context).getWritableDatabase(); String userDbPath = UserDatabase.getDatabasePath(); db.execSQL("ATTACH DATABASE '" + userDbPath + "' AS user"); } public MCPDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); setForcedUpgradeVersion(DATABASE_VERSION); // Uncomment the following statements to force a database upgrade during development // SQLiteDatabase db = getWritableDatabase(); // db.setVersion(-1); // db.close(); // db = getWritableDatabase(); } public static Cursor search(String input, int mode) { // Search for one or more keywords, considering mode and options // Get options and settings from SharedPreferences SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); Resources r = context.getResources(); boolean kuangxYonhOnly = sp.getBoolean(r.getString(R.string.pref_key_kuangx_yonh_only), false); boolean allowVariants = sp.getBoolean(r.getString(R.string.pref_key_allow_variants), true); boolean toneInsensitive = sp.getBoolean(r.getString(R.string.pref_key_tone_insensitive), false); int cantoneseSystem = sp.getInt(r.getString(R.string.pref_key_cantonese_romanization), 0); // Split the input string into keywords and canonicalize them List<String> keywords = new ArrayList<String>(); List<String> variants = new ArrayList<String>(); if (mode == SEARCH_AS_HZ) { // Each character is a query for (int i = 0; i < input.length(); i++) { char inputChar = input.charAt(i); if (!Orthography.Hanzi.isHanzi(inputChar)) continue; if (input.indexOf(inputChar) < i) continue; // Ignore a character if it has appeared earlier String inputHex = String.format("%04X", (int) inputChar); if (!allowVariants) { keywords.add(inputHex); } else { for (char variant : Orthography.Hanzi.getVariants(inputChar)) { String variantHex = String.format("%04X", (int) variant); int p = keywords.indexOf(variantHex); if (variant == inputChar) { if (p >= 0) { // The character itself must appear where it is keywords.remove(p); variants.remove(p); } keywords.add(inputHex); variants.add(null); // And no variant information is appended } else { if (p == -1) { // This variant character may have appeared before keywords.add(variantHex); variants.add(inputHex); } else { if (variants.get(p) != null) { variants.set(p, variants.get(p) + " " + inputHex); } } } } } } } else { // Each contiguous run of non-separator and non-comma characters is a query if (mode == SEARCH_AS_KR) { // For Korean, put separators around all hanguls StringBuilder sb = new StringBuilder(); for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); if (Orthography.Korean.isHangul(c)) { sb.append(" " + c + " "); } else { sb.append(c); } } input = sb.toString(); } for (String token : input.split("[\\s,]+")) { if (token.equals("")) continue; token = token.toLowerCase(Locale.US); // Canonicalization switch (mode) { case SEARCH_AS_MC: token = Orthography.MiddleChinese.canonicalize(token); break; case SEARCH_AS_PU: token = Orthography.Mandarin.canonicalize(token); break; case SEARCH_AS_CT: token = Orthography.Cantonese.canonicalize(token, cantoneseSystem); break; case SEARCH_AS_SH: token = Orthography.Shanghai.canonicalize(token); break; case SEARCH_AS_MN: token = Orthography.Minnan.canonicalize(token); break; case SEARCH_AS_KR: token = Orthography.Korean.canonicalize(token); break; case SEARCH_AS_VN: token = Orthography.Vietnamese.canonicalize(token); break; case SEARCH_AS_JP_GO: case SEARCH_AS_JP_KAN: case SEARCH_AS_JP_ANY: token = Orthography.Japanese.canonicalize(token); break; } if (token == null) continue; List<String> allTones = null; if (toneInsensitive) { switch (mode) { case SEARCH_AS_MC: allTones = Orthography.MiddleChinese.getAllTones(token); break; case SEARCH_AS_PU: allTones = Orthography.Mandarin.getAllTones(token); break; case SEARCH_AS_CT: allTones = Orthography.Cantonese.getAllTones(token); break; case SEARCH_AS_SH: allTones = Orthography.Shanghai.getAllTones(token); break; case SEARCH_AS_MN: allTones = Orthography.Minnan.getAllTones(token); break; case SEARCH_AS_VN: allTones = Orthography.Vietnamese.getAllTones(token); break; } } if (allTones != null) { keywords.addAll(allTones); } else { keywords.add(token); } } } if (keywords.isEmpty()) return null; // Columns to search String[] columns = (mode != SEARCH_AS_JP_ANY) ? new String[] {SEARCH_AS_TO_COLUMN_NAME[mode]} : new String[] {"jp_go", "jp_kan", "jp_tou", "jp_kwan", "jp_other"}; // Build inner query statement (a union query returning the id's of matching Chinese characters) SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables("mcpdict"); List<String> queries = new ArrayList<String>(); List<String> args = new ArrayList<String>(); for (int i = 0; i < keywords.size(); i++) { String variant = (mode == SEARCH_AS_HZ && allowVariants && variants.get(i) != null) ? ("\"" + variants.get(i) + "\"") : "null"; String[] projection = {"rowid AS _id", i + " AS rank", variant + " AS variants"}; for (String column : columns) { queries.add(qb.buildQuery(projection, column + " MATCH ?", null, null, null, null)); args.add(keywords.get(i)); } } String query = qb.buildUnionQuery(queries.toArray(new String[0]), null, null); // Build outer query statement (returning all information about the matching Chinese characters) qb.setTables("(" + query + ") AS u, mcpdict AS v LEFT JOIN user.favorite AS w ON v.unicode = w.unicode"); qb.setDistinct(true); String[] projection = {"_id", "v.unicode AS unicode", "variants", "mc", "pu", "ct", "sh", "mn", "kr", "vn", "jp_go", "jp_kan", "jp_tou", "jp_kwan", "jp_other", "timestamp IS NOT NULL AS is_favorite", "comment"}; String selection = "u._id = v.rowid"; if (kuangxYonhOnly) { selection += " AND mc IS NOT NULL"; } query = qb.buildQuery(projection, selection, null, null, "rank", null); // Search return db.rawQuery(query, args.toArray(new String[0])); } @SuppressWarnings("deprecation") public static Cursor directSearch(char unicode) { // Search for a single Chinese character without any conversions SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables("mcpdict AS v LEFT JOIN user.favorite AS w ON v.unicode = w.unicode"); String[] projection = {"v.rowid AS _id", "v.unicode AS unicode", "NULL AS variants", "mc", "pu", "ct", "sh", "mn", "kr", "vn", "jp_go", "jp_kan", "jp_tou", "jp_kwan", "jp_other", "timestamp IS NOT NULL AS is_favorite", "comment"}; String selection = "v.unicode = ?"; String query = qb.buildQuery(projection, selection, null, null, null, null, null); String[] args = {String.format("%04X", (int) unicode)}; return db.rawQuery(query, args); } }