001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.generator.gsp;
022    
023    import java.io.BufferedReader;
024    import java.io.IOException;
025    import java.io.Reader;
026    import java.io.StringReader;
027    import java.util.ArrayList;
028    import java.util.List;
029    
030    import org.granite.generator.gsp.token.Comment;
031    import org.granite.generator.gsp.token.Declaration;
032    import org.granite.generator.gsp.token.Directive;
033    import org.granite.generator.gsp.token.Expression;
034    import org.granite.generator.gsp.token.Scriplet;
035    import org.granite.generator.gsp.token.TemplateText;
036    import org.granite.generator.gsp.token.Token;
037    
038    /**
039     * @author Franck WOLFF
040     */
041    public class Parser {
042    
043        private final List<Token> elements = new ArrayList<Token>();
044    
045        public Parser() {
046        }
047    
048        public List<Token> parse(Reader reader) throws IOException, ParseException {
049            if (reader == null)
050                throw new NullPointerException("reader cannot be null");
051            if (!(reader instanceof StringReader || reader instanceof BufferedReader))
052                reader = new BufferedReader(reader);
053            return parse(read(reader));
054        }
055    
056        public List<Token> getElements() {
057            return elements;
058        }
059    
060        public void reset() {
061            elements.clear();
062        }
063    
064        private List<Token> parse(String template) throws ParseException {
065            if (template == null)
066                throw new NullPointerException("Argument template cannot be null");
067    
068            StringBuilder sb = new StringBuilder(64);
069    
070            final int length = template.length();
071            boolean inText = true;
072            for (int i = 0; i < length; i++) {
073                char first = inText ? '<' : '%';
074                char last = inText ? '%' : '>';
075                char c = template.charAt(i);
076                boolean appendC = true;
077    
078                if (c == first) { // '<' (template text) or '%' (gsp tag content)
079                    if (i+1 < length) {
080                        c = template.charAt(i+1);
081                        if (c == last) { // "<%" (template text) or "%>" (gsp tag content)
082                            if (inText)
083                                addTemplateText(i - sb.length(), sb.toString());
084                            else
085                                addScriptingElement(i - sb.length() - 2, sb.toString());
086                            sb.setLength(0);
087                            inText = !inText;
088                            appendC = false;
089                            i++;
090                        } else if (c == '\\') { // "<\" (template text) or "%\" (gsp tag content)
091                            sb.append(first);
092                            for (i += 2; i < length && (c = template.charAt(i)) == '\\'; i++)
093                                sb.append(c);
094                            if (c != last) // add skiped first '/'
095                                sb.append('\\');
096                        } else
097                            c = first;
098                    }
099                }
100    
101                if (appendC)
102                    sb.append(c);
103            }
104    
105            if (!inText)
106                throw new ParseException("Missing script section end (\"%>\")");
107    
108            addTemplateText(length - sb.length(), sb.toString());
109    
110            return elements;
111        }
112    
113        private String read(Reader reader) throws IOException {
114            StringBuilder sb = new StringBuilder(1024);
115    
116            int pc = -1, c = -1;
117            while((c = reader.read()) != -1) {
118                switch (c) {
119                case '\r': // "\r[\n]" (Windows or Mac)
120                    break;
121                case '\n': // "[\r]\n" (Windows or Unix)
122                    sb.append('\n');
123                    break;
124                default:
125                    if (pc == '\r')  // "\r" (Mac)
126                        sb.append('\n');
127                    sb.append((char)c);
128                    break;
129                }
130                pc = c;
131            }
132    
133            return sb.toString();
134        }
135    
136        private void addTemplateText(int index, String text) {
137            if (text.length() > 0)
138                elements.add(new TemplateText(index, text));
139        }
140    
141        private void addScriptingElement(int index, String text) throws ParseException {
142            if (text.length() == 0)
143                return;
144            char first = text.charAt(0);
145            switch (first) {
146            case '=': // expression
147                elements.add(new Expression(index, text.substring(1).trim()));
148                break;
149            case '!': // variable or method declaration
150                elements.add(new Declaration(index, text.substring(1).trim()));
151                break;
152            case '@': // directive
153                elements.add(new Directive(index, text.substring(1).trim()));
154                break;
155            case '-': // comment ?
156                if (text.startsWith("--") && text.endsWith("--")) {
157                    elements.add(new Comment(index, text.substring(2, text.length() - 4)));
158                    break;
159                }
160                // fall down (scriplet starting with '-')...
161            default: // scriplet
162                elements.add(new Scriplet(index, text));
163                break;
164            }
165        }
166    }