001    /*
002     * Copyright 2005 [ini4j] Development Team
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.ini4j;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.io.InputStreamReader;
022    import java.io.LineNumberReader;
023    import java.io.Reader;
024    import java.net.URL;
025    
026    import org.xml.sax.*;
027    import org.xml.sax.helpers.*;
028    import javax.xml.parsers.*;
029    
030    public class IniParser
031    {
032        public static final String COMMENTS = ";#";
033        public static final char OPERATOR = '=';
034        public static final char SECTION_BEGIN = '[';
035        public static final char SECTION_END = ']';
036        
037        public static final String SERVICE_ID = "org.ini4j.IniParser";
038        public static final String DEFAULT_SERVICE = SERVICE_ID;
039    
040        public static IniParser newInstance()
041        {
042            return (IniParser) ServiceFinder.findService(SERVICE_ID, DEFAULT_SERVICE);
043        }
044        
045        public void parse(InputStream input, IniHandler handler) throws IOException, InvalidIniFormatException
046        {
047            parse(new InputStreamReader(input), handler);
048        }
049    
050        public void parse(Reader input, IniHandler handler) throws IOException, InvalidIniFormatException
051        {
052            LineNumberReader reader = new LineNumberReader(input);
053            
054            handler.startIni();
055            
056            String sectionName = null;
057            
058            for (String line = reader.readLine(); line != null; line = reader.readLine())
059            {
060                line = line.trim();
061    
062                if ( (line.length() == 0) || (COMMENTS.indexOf(line.charAt(0)) >= 0))
063                {
064                    continue;
065                }
066                
067                if ( line.charAt(0) == SECTION_BEGIN )
068                {
069                    if ( sectionName != null )
070                    {
071                        handler.endSection();
072                    }
073                    
074                    if ( line.charAt(line.length()-1) != SECTION_END )
075                    {
076                        parseError(line, reader.getLineNumber());
077                    }
078    
079                    sectionName = unescape(line.substring(1, line.length()-1).trim());
080                    
081                    if ( sectionName.length() == 0 )
082                    {
083                        parseError(line, reader.getLineNumber());
084                    }
085    
086                    handler.startSection(sectionName);
087                }
088                else
089                {
090                    if ( sectionName == null )
091                    {
092                        parseError(line, reader.getLineNumber());
093                    }
094                    
095                    int idx = line.indexOf(OPERATOR);
096                    
097                    if ( idx <= 0 )
098                    {
099                        parseError(line, reader.getLineNumber());
100                    }
101    
102                    String name = unescape(line.substring(0, idx)).trim();
103                    String value = unescape(line.substring(idx+1)).trim();
104                    
105                    if ( name.length() == 0)
106                    {
107                        parseError(line, reader.getLineNumber());
108                    }
109    
110                    handler.handleOption(name, value);
111                }
112            }
113    
114            if ( sectionName != null  )
115            {
116                handler.endSection();
117            }
118    
119            handler.endIni();
120        }
121        
122        public void parse(URL input, IniHandler handler) throws IOException, InvalidIniFormatException
123        {
124            parse(input.openStream(), handler);
125        }
126    
127        public void parseXML(InputStream input, IniHandler handler) throws IOException, InvalidIniFormatException
128        {
129            parseXML(new InputStreamReader(input), handler);
130        }
131    
132        public void parseXML(Reader input, final IniHandler handler) throws IOException, InvalidIniFormatException
133        {
134            class XML2Ini extends DefaultHandler
135            {
136                static final String TAG_SECTION = "section";
137                static final String TAG_OPTION = "option";
138                static final String TAG_INI = "ini";
139                static final String ATTR_KEY = "key";
140                static final String ATTR_VALUE = "value";
141                static final String ATTR_VERSION = "version";
142                
143                static final String CURRENT_VERSION = "1.0";
144                
145                public void startElement(String uri, String localName, String qname, Attributes attrs) throws SAXException
146                {
147                    String key = attrs.getValue(ATTR_KEY);
148    
149                    if ( qname.equals(TAG_INI) )
150                    {
151                        String ver = attrs.getValue(ATTR_VERSION);
152                        
153                        if ( (ver == null) || ! ver.equals(CURRENT_VERSION))
154                        {
155                            throw new SAXException("Missing or invalid 'version' attribute");
156                        }
157                    }
158                    else
159                    {
160                        if ( key == null )
161                        {
162                            throw new SAXException("missing '" + ATTR_KEY + "' attribute");
163                        }
164                        
165                        if ( qname.equals(TAG_SECTION) )
166                        {
167                            handler.startSection(key);
168                        }
169                        else if ( qname.equals(TAG_OPTION) )
170                        {
171                            handler.handleOption(key, attrs.getValue(ATTR_VALUE));
172                        }
173                        else
174                        {
175                            throw new SAXException("Invalid element: " + qname);
176                        }
177                    }
178                }
179                
180                public void endElement(String uri, String localName, String qname) throws SAXException
181                {
182                    if ( qname.equals(TAG_SECTION) )
183                    {
184                        handler.endSection();
185                    }
186                }
187            }
188            
189            XML2Ini xml2ini = new XML2Ini();
190            
191            try
192            {
193                SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(input), xml2ini);
194            }
195            catch (Exception x)
196            {
197                throw new InvalidIniFormatException(x);
198            }
199        }
200        
201        public void parseXML(URL input, IniHandler handler) throws IOException, InvalidIniFormatException
202        {
203            parseXML(input.openStream(), handler);
204        }
205        
206        protected String unescape(String line)
207        {
208            return Convert.unescape(line);
209        }
210        
211        protected void parseError(String line, int lineNumber) throws InvalidIniFormatException
212        {
213            throw new InvalidIniFormatException("parse error (at line: " + lineNumber + "): " + line);
214        }
215    }