Subversion Repositories mkgmap

Rev

Rev 4395 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2011.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

package uk.me.parabola.mkgmap.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;

import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.typ.TYPFile;
import uk.me.parabola.imgfmt.app.typ.TypData;
import uk.me.parabola.imgfmt.app.typ.TypLabelException;
import uk.me.parabola.imgfmt.app.typ.TypParam;
import uk.me.parabola.imgfmt.sys.FileImgChannel;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.scan.SyntaxException;
import uk.me.parabola.mkgmap.typ.TypTextReader;

/**
 * Standalone program to compile a TYP file from the text format.
 * Simple main program to demonstrate compiling a typ.txt file.
 *
 * Usage: TypTextReader [in-file] [out-file]
 *
 * in-file defaults to 'default.txt'
 * out-file defaults to 'OUT.TYP'
 *
 */

public class TypCompiler implements MapProcessor {

        /**
         * The integration with mkgmap.
         *
         * @param args The options that are in force.
         * @param filename The input filename.
         * @return Returns the name of the file that was written. It depends on the family id.
         */

        public String makeMap(CommandArgs args, String filename) {
                assert filename.toLowerCase().endsWith(".txt");

                CharsetProbe probe = new CharsetProbe();
                String readCharset = probe.probeCharset(filename);

                TypData data;
                try {
                        data = compile(filename, readCharset, args.getSort());
                } catch (SyntaxException e) {
                        throw new MapFailedException("Compiling TYP txt file: " + e.getMessage());
                } catch (FileNotFoundException e) {
                        throw new MapFailedException("Could not open TYP file " + filename + " to read");
                }

                TypParam param = data.getParam();
                int family = args.get("family-id", -1);
                int product = args.get("product-id", -1);
                int cp = args.get("code-page", -1);

                if (family != -1)
                        param.setFamilyId(family);
                if (product != -1)
                        param.setProductId(product);
                if (cp != -1)
                        param.setCodePage(cp);

                File outFile = new File(filename);
                String outName = outFile.getName();

                int last;
                if (outName.length() > 4 && (last = outName.lastIndexOf('.')) > 0)
                        outName = outName.substring(0, last);

                outName += ".typ";
                outFile = new File(args.getOutputDir(), outName);

                try {
                        writeTyp(data, outFile);
                } catch (TypLabelException e) {
                        throw new MapFailedException("TYP file cannot be written in code page "
                                        + data.getSort().getCodepage());
                } catch (IOException e) {
                        throw new MapFailedException("Error while writing typ file", e);
                }

                return outFile.getPath();
        }

        /**
         * Read and compile a TYP file, returning the compiled form.
         *
         * @param filename The input filename.
         * @param charset The character set to use to read this file. We should have already determined
         * that this character set is valid and can be used to read the file.
         * @param sort The sort information from command line options, used for the output code page
         * only. If null, then the code page set by CodePage in the typ.txt file will be used.
         *
         * @return The compiled form as a data structure.
         * @throws FileNotFoundException If the file doesn't exist.
         * @throws SyntaxException All user correctable problems in the input file.
         */

        private static TypData compile(String filename, String charset, Sort sort)
                        throws FileNotFoundException, SyntaxException
        {
                TypTextReader tr = new TypTextReader();

                TypData data = tr.getData();

                data.setSort(sort);
                try (Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(filename), charset))) {
                        tr.read(filename, r, charset);
                } catch (UnsupportedEncodingException e) {
                        // Not likely to happen as we should have already used this character set!
                        throw new MapFailedException("Unsupported character set", e);
                } catch (IOException e) {
                        throw new ExitException("Unable to read/close file " + filename);
                }

                return tr.getData();
        }

        /**
         * Write the type file out from the compiled form to the given name.
         */

        private static void writeTyp(TypData data, File file) throws IOException {
                try (FileChannel channel = FileChannel.open(file.toPath(),
                                StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ))
                {
                        channel.truncate(0);

                        FileImgChannel w = new FileImgChannel(channel);
                        try (TYPFile typ = new TYPFile(w)) {
                                typ.setData(data);
                                typ.write();
                        }
                }
        }

        /**
         * Simple standalone compiler.
         *
         * Usage: TypCompiler [in-file] [out-file]
         *  in-file defaults to 'default.txt'
         *  out-file defaults to OUT.TYP
         */

        public static void main(String... args) {
                String in = "default.txt";
                if (args.length > 0)
                        in = args[0];
                String out = "OUT.TYP";
                if (args.length > 1)
                        out = args[1];

                new TypCompiler().standAloneRun(in, out);
        }

        private void standAloneRun(String in, String out) {
                CharsetProbe probe = new CharsetProbe();
                String readCharset = probe.probeCharset(in);

                TypData data;
                try {
                        data = compile(in, readCharset, null);
                } catch (SyntaxException e) {
                        System.out.println(e.getMessage());
                        return;
                } catch (FileNotFoundException e) {
                        throw new MapFailedException("Could not open TYP file " + in + " to read");
                }

                try {
                        writeTyp(data, new File(out));
                } catch (IOException e) {
                        System.out.println("Error writing file: " + e.getMessage());
                }
        }


        class CharsetProbe {
                // TODO: this should could be moved to somewhere like util and used on other text files
                // except looking for Codepage is particular to Typ files
                // and want to have ability to return default environment decoder
                // (ie inputStream without 2nd parameter)

                private String probeCharset(String file) {

                        final String BOM_UTF_8    = "\u00EF\u00BB\u00BF";
                        final String BOM_UTF_16LE = "\u00FF\u00FE";
                        final String BOM_UTF_16BE = "\u00FE\u00FF";
                        final String BOM_UTF_32LE = "\u00FF\u00FE\u0000\u0000";
                        final String BOM_UTF_32BE = "\u0000\u0000\u00FE\u00FF";

                        final Charset byteCharNoMap = StandardCharsets.ISO_8859_1; // byteVal == charVal
                        final CharsetDecoder utf8Decoder = StandardCharsets.UTF_8.newDecoder();

                        String charset = null;
                        boolean validUTF8 = true;
                        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), byteCharNoMap))) {
                                String line;
                                int lineNo = 0;
                                do {
                                        line = br.readLine();
                                        if (line == null)
                                                break;
                                        ++lineNo;
                                        if (line.isEmpty())
                                                continue;
                                        if (lineNo <= 2) { // only check the first few lines for these
                                                if (line.contains(BOM_UTF_8))
                                                        charset = "UTF-8";
                                                else if (line.contains(BOM_UTF_32LE)) // must test _32 before _16
                                                        charset = "UTF-32LE";
                                                else if (line.contains(BOM_UTF_32BE))
                                                        charset = "UTF-32BE";
                                                else if (line.contains(BOM_UTF_16LE))
                                                        charset = "UTF-16LE";
                                                else if (line.contains(BOM_UTF_16BE))
                                                        charset = "UTF-16BE";
                                                if (charset != null)
                                                        break;

                                                int strInx = line.indexOf("-*- coding:"); // be lax about start/end
                                                if (strInx >= 0) {
                                                        charset = line.substring(strInx+11).trim();
                                                        strInx = charset.indexOf(' ');
                                                        if (strInx >= 0)
                                                                charset = charset.substring(0, strInx);
                                                        break;
                                                }
                                        }

                                        // special for TypFile; to be compatible with possible old usage
                                        if (line.startsWith("CodePage=")) {
                                                charset = line.substring(9).trim();
                                                try {
                                                        int codePage = Integer.decode(charset);
                                                        if (codePage == 65001)
                                                                charset = "UTF-8";
                                                        else
                                                                charset = "cp" + codePage;
                                                } catch (NumberFormatException e) {
                                                }
                                                break;
                                        }

                                        if (validUTF8) { // test the line for being valid UTF-8
                                                ByteBuffer asBytes = byteCharNoMap.encode(line);
                                                try { // arbitrary sequences of bytes > 127 tend not to be UTF8
                                                        /*CharBuffer asChars =*/ utf8Decoder.decode(asBytes);
                                                } catch (CharacterCodingException e) {
                                                        validUTF8 = false;
                                                        // don't stop as might still get coding directive
                                                }
                                        }
                                } while (true);
                        } catch (FileNotFoundException e) {
                                throw new ExitException("File not found " + file);
                        } catch (IOException e) {
                                throw new ExitException("Unable to read file " + file);
                        }
                        return charset != null ? charset : (validUTF8 ? "UTF-8" : "cp1252");
                }
        }
}