BinaryDictOffdeviceUtils.java revision e9a10ff0f026b5ec458f116afc7a75806574cbcd
177fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard/*
277fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * Copyright (C) 2012 The Android Open Source Project
377fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard *
477fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * Licensed under the Apache License, Version 2.0 (the "License"); you may not
577fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * use this file except in compliance with the License. You may obtain a copy of
677fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * the License at
777fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard *
877fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * http://www.apache.org/licenses/LICENSE-2.0
977fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard *
1077fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * Unless required by applicable law or agreed to in writing, software
1177fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1277fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1377fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * License for the specific language governing permissions and limitations under
1477fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard * the License.
1577fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard */
1677fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard
1777fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalardpackage com.android.inputmethod.latin.dicttool;
1877fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard
1977bce05e6f6e3a988253f9305ae22e51f56f5b1aYuichiro Hanadaimport com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
20e9a10ff0f026b5ec458f116afc7a75806574cbcdYuichiro Hanadaimport com.android.inputmethod.latin.makedict.DictDecoder;
216ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalardimport com.android.inputmethod.latin.makedict.FusionDictionary;
226ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalardimport com.android.inputmethod.latin.makedict.UnsupportedFormatException;
23112257e40f6f6d914fac1c3a45f39a770693b386Yuichiro Hanadaimport com.android.inputmethod.latin.makedict.Ver3DictDecoder;
246ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard
256ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalardimport org.xml.sax.SAXException;
26b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
27b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardimport java.io.File;
28b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardimport java.io.BufferedInputStream;
29b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardimport java.io.BufferedOutputStream;
30b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardimport java.io.FileInputStream;
31b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardimport java.io.FileOutputStream;
3277fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalardimport java.io.IOException;
3377fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalardimport java.io.InputStream;
3477fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalardimport java.io.OutputStream;
35b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardimport java.util.ArrayList;
3677fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard
376ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalardimport javax.xml.parsers.ParserConfigurationException;
386ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard
3977fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard/**
40b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard * Class grouping utilities for offline dictionary making.
41b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard *
42b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard * Those should not be used on-device, essentially because they are quite
43b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard * liberal about I/O and performance.
44b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard */
45b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalardpublic final class BinaryDictOffdeviceUtils {
46b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    // Prefix and suffix are arbitrary, the values do not really matter
47b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    private final static String PREFIX = "dicttool";
48b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    private final static String SUFFIX = ".tmp";
49b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
50f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard    public final static String COMPRESSION = "compressed";
51f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard    public final static String ENCRYPTION = "encrypted";
52b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
53a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard    private final static int MAX_DECODE_DEPTH = 8;
54a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard
55b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    public static class DecoderChainSpec {
56b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        ArrayList<String> mDecoderSpec = new ArrayList<String>();
57b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        File mFile;
58b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        public DecoderChainSpec addStep(final String stepDescription) {
59b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            mDecoderSpec.add(stepDescription);
60b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            return this;
61b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        }
62f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard        public String describeChain() {
63f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard            final StringBuilder s = new StringBuilder("raw");
64f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard            for (final String step : mDecoderSpec) {
65f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard                s.append(" > ");
66f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard                s.append(step);
67f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard            }
68f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard            return s.toString();
69f1d35ac5dc0cca2b357940cab1001cadca37bcb4Jean Chalard        }
70b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    }
71b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
7277fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard    public static void copy(final InputStream input, final OutputStream output) throws IOException {
7377fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard        final byte[] buffer = new byte[1000];
7477fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard        final BufferedInputStream in = new BufferedInputStream(input);
7577fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard        final BufferedOutputStream out = new BufferedOutputStream(output);
7677fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard        for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer))
7777fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard            output.write(buffer, 0, readBytes);
7877fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard        in.close();
7977fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard        out.close();
8077fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard    }
81b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
82b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    /**
83b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     * Returns a decrypted/uncompressed binary dictionary.
84b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     *
85b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     * This will decrypt/uncompress any number of times as necessary until it finds the binary
86b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     * dictionary signature, and copy the decoded file to a temporary place.
87b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     * If this is not a binary dictionary, the method returns null.
88b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     */
89b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    public static DecoderChainSpec getRawBinaryDictionaryOrNull(final File src) {
90a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        return getRawBinaryDictionaryOrNullInternal(new DecoderChainSpec(), src, 0);
91b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    }
92b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
93b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    private static DecoderChainSpec getRawBinaryDictionaryOrNullInternal(
94a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard            final DecoderChainSpec spec, final File src, final int depth) {
95a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        // Unfortunately the decoding scheme we use can consider any data to be encrypted
96a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        // and will product some output, meaning it's not possible to reliably detect encrypted
97a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        // data. Thus, some non-dictionary files (especially small) ones may successfully decrypt
98a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        // over and over, ending in a stack overflow. Hence we limit the depth at which we try
99a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        // decoding the file.
100a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard        if (depth > MAX_DECODE_DEPTH) return null;
10177bce05e6f6e3a988253f9305ae22e51f56f5b1aYuichiro Hanada        if (BinaryDictDecoderUtils.isBinaryDictionary(src)) {
102b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            spec.mFile = src;
103b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            return spec;
104b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        }
105b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        // It's not a raw dictionary - try to see if it's compressed.
106b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        final File uncompressedFile = tryGetUncompressedFile(src);
107b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        if (null != uncompressedFile) {
108b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            final DecoderChainSpec newSpec =
109a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard                    getRawBinaryDictionaryOrNullInternal(spec, uncompressedFile, depth + 1);
110b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            if (null == newSpec) return null;
111b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            return newSpec.addStep(COMPRESSION);
112b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        }
1130044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        // It's not a compressed either - try to see if it's crypted.
1140044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        final File decryptedFile = tryGetDecryptedFile(src);
1150044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        if (null != decryptedFile) {
1160044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            final DecoderChainSpec newSpec =
117a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard                    getRawBinaryDictionaryOrNullInternal(spec, decryptedFile, depth + 1);
1180044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            if (null == newSpec) return null;
1190044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            return newSpec.addStep(ENCRYPTION);
1200044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        }
121b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        return null;
122b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    }
123b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard
124b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    /* Try to uncompress the file passed as an argument.
125b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     *
126b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     * If the file can be uncompressed, the uncompressed version is returned. Otherwise, null
127b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     * is returned.
128b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard     */
129b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    private static File tryGetUncompressedFile(final File src) {
130b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        try {
131b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            final File dst = File.createTempFile(PREFIX, SUFFIX);
132a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard            dst.deleteOnExit();
133b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            final FileOutputStream dstStream = new FileOutputStream(dst);
134b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            copy(Compress.getUncompressedStream(new BufferedInputStream(new FileInputStream(src))),
135b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard                    new BufferedOutputStream(dstStream)); // #copy() closes the streams
136b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            return dst;
137b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        } catch (IOException e) {
138b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            // Could not uncompress the file: presumably the file is simply not a compressed file
139b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard            return null;
140b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard        }
141b3c98901c5fc1460b54cdf27d74405f27c88e74bJean Chalard    }
1420044df6cf2f4ef00d78e530220565b8272187446Jean Chalard
1430044df6cf2f4ef00d78e530220565b8272187446Jean Chalard    /* Try to decrypt the file passed as an argument.
1440044df6cf2f4ef00d78e530220565b8272187446Jean Chalard     *
1450044df6cf2f4ef00d78e530220565b8272187446Jean Chalard     * If the file can be decrypted, the decrypted version is returned. Otherwise, null
1460044df6cf2f4ef00d78e530220565b8272187446Jean Chalard     * is returned.
1470044df6cf2f4ef00d78e530220565b8272187446Jean Chalard     */
1480044df6cf2f4ef00d78e530220565b8272187446Jean Chalard    private static File tryGetDecryptedFile(final File src) {
1490044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        try {
1500044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            final File dst = File.createTempFile(PREFIX, SUFFIX);
151a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard            dst.deleteOnExit();
1520044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            final FileOutputStream dstStream = new FileOutputStream(dst);
1530044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            copy(Crypt.getDecryptedStream(new BufferedInputStream(new FileInputStream(src))),
1540044df6cf2f4ef00d78e530220565b8272187446Jean Chalard                    dstStream); // #copy() closes the streams
1550044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            return dst;
1560044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        } catch (IOException e) {
157a8058d169dad450eca428ca76c5a0f44e45f41a7Jean Chalard            // Could not decrypt the file: presumably the file is simply not a crypted file
1580044df6cf2f4ef00d78e530220565b8272187446Jean Chalard            return null;
1590044df6cf2f4ef00d78e530220565b8272187446Jean Chalard        }
1600044df6cf2f4ef00d78e530220565b8272187446Jean Chalard    }
1616ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard
1626ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard    static void crash(final String filename, final Exception e) {
1636ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        throw new RuntimeException("Can't read file " + filename, e);
1646ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard    }
1656ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard
1666ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard    static FusionDictionary getDictionary(final String filename, final boolean report) {
1676ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        final File file = new File(filename);
1686ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        if (report) {
1696ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            System.out.println("Dictionary : " + file.getAbsolutePath());
1706ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            System.out.println("Size : " + file.length() + " bytes");
1716ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        }
1726ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        try {
1736ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            if (XmlDictInputOutput.isXmlUnigramDictionary(filename)) {
1746ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                if (report) System.out.println("Format : XML unigram list");
1756ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                return XmlDictInputOutput.readDictionaryXml(
1766ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                        new BufferedInputStream(new FileInputStream(file)),
1776ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                        null /* shortcuts */, null /* bigrams */);
1786ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            } else if (CombinedInputOutput.isCombinedDictionary(filename)) {
1796ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                if (report) System.out.println("Format : Combined format");
1806ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                return CombinedInputOutput.readDictionaryCombined(
1816ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                        new BufferedInputStream(new FileInputStream(file)));
1826ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            } else {
1836ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                final DecoderChainSpec decodedSpec = getRawBinaryDictionaryOrNull(file);
1846ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                if (null == decodedSpec) {
1856ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                    crash(filename, new RuntimeException(
1866ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                            filename + " does not seem to be a dictionary file"));
1876ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                } else {
188e9a10ff0f026b5ec458f116afc7a75806574cbcdYuichiro Hanada                    final DictDecoder dictDecoder = new Ver3DictDecoder(decodedSpec.mFile,
189e9a10ff0f026b5ec458f116afc7a75806574cbcdYuichiro Hanada                            DictDecoder.USE_BYTEARRAY);
1906ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                    if (report) {
1916ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                        System.out.println("Format : Binary dictionary format");
1926ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                        System.out.println("Packaging : " + decodedSpec.describeChain());
1936ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                        System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
1946ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                    }
195e9a10ff0f026b5ec458f116afc7a75806574cbcdYuichiro Hanada                    return dictDecoder.readDictionaryBinary(null);
1966ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard                }
1976ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            }
1986ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        } catch (IOException e) {
1996ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            crash(filename, e);
2006ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        } catch (SAXException e) {
2016ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            crash(filename, e);
2026ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        } catch (ParserConfigurationException e) {
2036ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            crash(filename, e);
2046ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        } catch (UnsupportedFormatException e) {
2056ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard            crash(filename, e);
2066ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        }
2076ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard        return null;
2086ecc50a867dc09eb1d9dafe62f40e73de01b30cbJean Chalard    }
20977fe603a3d82f5fc28816520bac479ff48bf15e5Jean Chalard}
210