java/alt2xml-in-ini/src/cz/frantovo/alt2xml/in/ini/Reader.java
author František Kučera <franta-hg@frantovo.cz>
Sat, 06 Sep 2014 22:04:11 +0200
changeset 91 8146ad99fc67
parent 89 46c7cc4863c1
child 92 03c1c831cfcb
permissions -rw-r--r--
in-ini: no whitespace in entry keys – but do encoding
     1 /**
     2  * Alt2XML
     3  * Copyright © 2014 František Kučera (frantovo.cz)
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, either version 3 of the License, or
     8  * (at your option) any later version.
     9  *
    10  * This program is distributed in the hope that it will be useful,
    11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  * GNU General Public License for more details.
    14  *
    15  * You should have received a copy of the GNU General Public License
    16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 package cz.frantovo.alt2xml.in.ini;
    19 
    20 import cz.frantovo.alt2xml.AbstractAlt2XmlReader;
    21 import cz.frantovo.alt2xml.in.Alt2ContentHandler;
    22 import cz.frantovo.alt2xml.in.Functions;
    23 import java.io.BufferedReader;
    24 import java.io.IOException;
    25 import java.io.InputStreamReader;
    26 import java.util.ArrayList;
    27 import java.util.List;
    28 import java.util.logging.Level;
    29 import java.util.logging.Logger;
    30 import java.util.regex.Matcher;
    31 import java.util.regex.Pattern;
    32 import org.xml.sax.InputSource;
    33 import org.xml.sax.SAXException;
    34 
    35 /**
    36  *
    37  * @author Ing. František Kučera (frantovo.cz)
    38  */
    39 public class Reader extends AbstractAlt2XmlReader {
    40 
    41 	public static final String ROOT_ELEMENT = "ini";
    42 	private static final Logger log = Logger.getLogger(Reader.class.getName());
    43 
    44 	@Override
    45 	public void parse(InputSource input) throws IOException, SAXException {
    46 		outputStart();
    47 
    48 		try (BufferedReader br = new BufferedReader(new InputStreamReader(input.getByteStream()))) {
    49 			FileContext fc = new FileContext(contentHandler);
    50 			for (String currentLine = br.readLine(); currentLine != null; currentLine = br.readLine()) {
    51 				fc.lineNumber++;
    52 				boolean lineProcessed = false;
    53 				for (LINE_TYPE lineType : LINE_TYPE.values()) {
    54 					lineProcessed = lineType.processLine(currentLine, fc);
    55 					if (lineProcessed) {
    56 						break;
    57 					}
    58 				}
    59 				if (!lineProcessed) {
    60 					log.log(Level.SEVERE, "Invalid line in INI file: {0}", currentLine);
    61 				}
    62 			}
    63 			fc.outputEndSection(fc.lastSection);
    64 
    65 		}
    66 
    67 		outputEnd();
    68 	}
    69 
    70 	private void outputStart() throws SAXException {
    71 		contentHandler.startDocument();
    72 		contentHandler.lineBreak();
    73 		contentHandler.startElement(null, null, ROOT_ELEMENT, null);
    74 		contentHandler.lineBreak();
    75 	}
    76 
    77 	private void outputEnd() throws SAXException {
    78 		contentHandler.endElement(null, null, "ini");
    79 		contentHandler.lineBreak();
    80 		contentHandler.endDocument();
    81 	}
    82 
    83 	private static class FileContext {
    84 
    85 		private final Alt2ContentHandler contentHandler;
    86 		private String lastSection;
    87 		private int lineNumber;
    88 
    89 		public FileContext(Alt2ContentHandler contentHandler) {
    90 			this.contentHandler = contentHandler;
    91 		}
    92 
    93 		protected void outputStartSection(String name) throws SAXException {
    94 			contentHandler.indentation(1);
    95 			contentHandler.startElement(null, null, name, null);
    96 			contentHandler.lineBreak();
    97 		}
    98 
    99 		protected void outputEndSection(String name) throws SAXException {
   100 			if (name != null) {
   101 				contentHandler.indentation(1);
   102 				contentHandler.endElement(null, null, name);
   103 				contentHandler.lineBreak();
   104 			}
   105 		}
   106 	}
   107 
   108 	private static String encodeXmlName(String originalName, int lineNumber) {
   109 		String encodedName = Functions.encodeXmlName(originalName);
   110 		if (!encodedName.equals(originalName)) {
   111 			log.log(Level.FINE, "Line {0}: name „{1} was encoded to „{2}““", new Object[]{lineNumber, originalName, encodedName});
   112 		}
   113 		return encodedName;
   114 	}
   115 
   116 	private static class LineContext {
   117 
   118 		private final Matcher matcher;
   119 
   120 		public LineContext(Matcher matcher) {
   121 			this.matcher = matcher;
   122 		}
   123 	}
   124 
   125 	private enum LINE_TYPE {
   126 
   127 		BLANK_LINE("\\s*") {
   128 					@Override
   129 					public void processLine(LineContext lc, FileContext fc) throws SAXException {
   130 						log.log(Level.FINEST, "Line {0}: skipping blank line", fc.lineNumber);
   131 					}
   132 				},
   133 		COMMENT("\\s*(;|#)\\s*(?<comment>.*)") {
   134 					@Override
   135 					public void processLine(LineContext lc, FileContext fc) throws SAXException {
   136 						// TODO: comment → LexicalHandler
   137 						log.log(Level.FINER, "Line {0}: comment: {1}", new Object[]{fc.lineNumber, lc.matcher.group("comment")});
   138 					}
   139 
   140 				},
   141 		SECTION("\\s*\\[\\s*(?<name>[^\\]\\]]+)\\s*\\]\\s*") {
   142 					@Override
   143 					public void processLine(LineContext lc, FileContext fc) throws SAXException {
   144 						String name = encodeXmlName(lc.matcher.group("name"), fc.lineNumber);
   145 						fc.outputEndSection(fc.lastSection);
   146 						fc.outputStartSection(name);
   147 						fc.lastSection = name;
   148 					}
   149 
   150 				},
   151 		ENTRY(
   152 				"\\s*(?<key>[^=\\s]+)\\s*=\\s*\"(?<value>[^']+)\"\\s*((;|#)\\s*(?<comment>.*)){0,1}", // quoted value → include spaces + might have comment
   153 				"\\s*(?<key>[^=\\s]+)\\s*=\\s*'(?<value>[^']+)'\\s*((;|#)\\s*(?<comment>.*)){0,1}", // apostrophed value → include spaces + might have comment
   154 				"\\s*(?<key>[^=\\s]+)\\s*=\\s*(?<value>.+)" // unquoted value → strip spaces + no comments
   155 		) {
   156 					@Override
   157 					public void processLine(LineContext lc, FileContext fc) throws SAXException {
   158 						String key = encodeXmlName(lc.matcher.group("key"), fc.lineNumber);
   159 						String value = lc.matcher.group("value");
   160 
   161 						if (lc.matcher.groupCount() > 2) {
   162 							String comment = lc.matcher.group("comment");
   163 							// TODO: comment → LexicalHandler
   164 							log.log(Level.FINER, "Line {0}: comment for entry „{1}“ is: {2}", new Object[]{fc.lineNumber, key, comment});
   165 						}
   166 
   167 						fc.contentHandler.indentation(fc.lastSection == null ? 1 : 2);
   168 						fc.contentHandler.textElement(value, null, null, key, null);
   169 						fc.contentHandler.lineBreak();
   170 
   171 					}
   172 
   173 				},;
   174 
   175 		private LINE_TYPE(String... patterns) {
   176 			for (String pattern : patterns) {
   177 				this.patterns.add(Pattern.compile(pattern));
   178 			}
   179 		}
   180 
   181 		private final List<Pattern> patterns = new ArrayList<>();
   182 
   183 		protected boolean processLine(String currentLine, FileContext fc) throws SAXException {
   184 			for (Pattern pattern : patterns) {
   185 				Matcher m = pattern.matcher(currentLine);
   186 				if (m.matches()) {
   187 					log.log(Level.FINEST, "Line {0}: pattern „{1}“ matches „{2}“", new Object[]{fc.lineNumber, pattern, currentLine});
   188 					processLine(new LineContext(m), fc);
   189 					return true;
   190 				}
   191 			}
   192 			return false;
   193 		}
   194 
   195 		public abstract void processLine(LineContext lc, FileContext fc) throws SAXException;
   196 	}
   197 
   198 }