/* * Copyright (C) 2011 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. */ package com.android.inputmethod.latin; import com.android.inputmethod.latin.FusionDictionary.WeightedString; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.TreeSet; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Reads and writes XML files for a FusionDictionary. * * All functions in this class are static. */ public class XmlDictInputOutput { private static final String WORD_TAG = "w"; private static final String BIGRAM_TAG = "bigram"; private static final String FREQUENCY_ATTR = "f"; private static final String WORD_ATTR = "word"; /** * SAX handler for a unigram XML file. */ static private class UnigramHandler extends DefaultHandler { // Parser states private static final int NONE = 0; private static final int START = 1; private static final int WORD = 2; private static final int BIGRAM = 4; private static final int END = 5; private static final int UNKNOWN = 6; final FusionDictionary mDictionary; int mState; // the state of the parser int mFreq; // the currently read freq String mWord; // the current word final HashMap> mBigramsMap; /** * Create the handler. * * @param dict the dictionary to construct. * @param bigrams the bigrams as a map. This may be empty, but may not be null. */ public UnigramHandler(FusionDictionary dict, HashMap> bigrams) { mDictionary = dict; mBigramsMap = bigrams; mWord = ""; mState = START; mFreq = 0; } @Override public void startElement(String uri, String localName, String qName, Attributes attrs) { if (WORD_TAG.equals(localName)) { mState = WORD; mWord = ""; for (int attrIndex = 0; attrIndex < attrs.getLength(); ++attrIndex) { final String attrName = attrs.getLocalName(attrIndex); if (FREQUENCY_ATTR.equals(attrName)) { mFreq = Integer.parseInt(attrs.getValue(attrIndex)); } } } else { mState = UNKNOWN; } } @Override public void characters(char[] ch, int start, int length) { if (WORD == mState) { // The XML parser is free to return text in arbitrary chunks one after the // other. In particular, this happens in some implementations when it finds // an escape code like "&". mWord += String.copyValueOf(ch, start, length); } } @Override public void endElement(String uri, String localName, String qName) { if (WORD == mState) { mDictionary.add(mWord, mFreq, mBigramsMap.get(mWord)); mState = START; } } } /** * SAX handler for a bigram XML file. */ static private class BigramHandler extends DefaultHandler { private final static String BIGRAM_W1_TAG = "bi"; private final static String BIGRAM_W2_TAG = "w"; private final static String BIGRAM_W1_ATTRIBUTE = "w1"; private final static String BIGRAM_W2_ATTRIBUTE = "w2"; private final static String BIGRAM_FREQ_ATTRIBUTE = "p"; // In this version of the XML file, the bigram frequency is given as an int 0..XML_MAX private final static int XML_MAX = 256; // In memory and in the binary dictionary the bigram frequency is 0..MEMORY_MAX private final static int MEMORY_MAX = 16; private final static int XML_TO_MEMORY_RATIO = XML_MAX / MEMORY_MAX; String mW1; final HashMap> mBigramsMap; public BigramHandler() { mW1 = null; mBigramsMap = new HashMap>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attrs) { if (BIGRAM_W1_TAG.equals(localName)) { mW1 = attrs.getValue(uri, BIGRAM_W1_ATTRIBUTE); } else if (BIGRAM_W2_TAG.equals(localName)) { String w2 = attrs.getValue(uri, BIGRAM_W2_ATTRIBUTE); int freq = Integer.parseInt(attrs.getValue(uri, BIGRAM_FREQ_ATTRIBUTE)); WeightedString bigram = new WeightedString(w2, freq / XML_TO_MEMORY_RATIO); ArrayList bigramList = mBigramsMap.get(mW1); if (null == bigramList) bigramList = new ArrayList(); bigramList.add(bigram); mBigramsMap.put(mW1, bigramList); } } public HashMap> getBigramMap() { return mBigramsMap; } } /** * Reads a dictionary from an XML file. * * This is the public method that will parse an XML file and return the corresponding memory * representation. * * @param unigrams the file to read the data from. * @return the in-memory representation of the dictionary. */ public static FusionDictionary readDictionaryXml(InputStream unigrams, InputStream bigrams) throws SAXException, IOException, ParserConfigurationException { final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); final SAXParser parser = factory.newSAXParser(); final BigramHandler bigramHandler = new BigramHandler(); if (null != bigrams) parser.parse(bigrams, bigramHandler); final FusionDictionary dict = new FusionDictionary(); final UnigramHandler unigramHandler = new UnigramHandler(dict, bigramHandler.getBigramMap()); parser.parse(unigrams, unigramHandler); return dict; } /** * Reads a dictionary in the first, legacy XML format * * This method reads data from the parser and creates a new FusionDictionary with it. * The format parsed by this method is the format used before Ice Cream Sandwich, * which has no support for bigrams or shortcuts. * It is important to note that this method expects the parser to have already eaten * the first, all-encompassing tag. * * @param xpp the parser to read the data from. * @return the parsed dictionary. */ /** * Writes a dictionary to an XML file. * * The output format is the "second" format, which supports bigrams and shortcuts. * * @param destination a destination stream to write to. * @param dict the dictionary to write. */ public static void writeDictionaryXml(Writer destination, FusionDictionary dict) throws IOException { final TreeSet set = new TreeSet(); for (Word word : dict) { set.add(word); } // TODO: use an XMLSerializer if this gets big destination.write("\n"); for (Word word : set) { destination.write(" <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" " + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\">"); if (null != word.mBigrams) { destination.write("\n"); for (WeightedString bigram : word.mBigrams) { destination.write(" <" + BIGRAM_TAG + " " + FREQUENCY_ATTR + "=\"" + bigram.mFrequency + "\">" + bigram.mWord + "\n"); } destination.write(" "); } destination.write("\n"); } destination.write("\n"); destination.close(); } }