1a2bf1f0061e0b5a34a2054db3f53690168a1b8b
[mir.git] / source / mir / util / xml / html / HTMLScanner.java
1 package mir.util.xml.html;
2
3 import java.io.IOException;
4 import java.io.Reader;
5 import java.util.HashMap;
6 import java.util.Map;
7
8 public class HTMLScanner {
9   private ReaderWrapper reader;
10   private ScannerReceiver receiver;
11
12   public HTMLScanner(ScannerReceiver aReceiver, Reader aReader) {
13     reader = new ReaderWrapper(aReader);
14     receiver = aReceiver;
15   }
16
17   public interface ScannerReceiver {
18     public void handleDTD(String aDTD) throws HTMLParserExc;
19     public void handleOpenTag(String aTag, Map anAttributes) throws HTMLParserExc;
20     public void handleClosingTag(String aTag) throws HTMLParserExc;
21     public void handleCData(String aData) throws HTMLParserExc;
22     public void handleComment(String aTag) throws HTMLParserExc;
23     public void handleEndOfStream() throws HTMLParserExc;
24   }
25
26   public void run() throws IOException, HTMLParserExc {
27
28     while (!reader.isAtEnd()) {
29       char c = reader.peek();
30
31       if (c != '<')
32         readCData();
33       else {
34         reader.get();
35         c = reader.peek();
36
37         switch (c) {
38           case '!':
39             reader.get();
40             readSpecial();
41             break;
42           case '/':
43             reader.get();
44             readEndTag();
45             break;
46           default:
47             readTag();
48         }
49       }
50     }
51
52     receiver.handleEndOfStream();
53   }
54
55   private boolean isValidTagNameCharacter(char aCharacter) {
56     int type = Character.getType(aCharacter);
57
58     return
59         (type == Character.UPPERCASE_LETTER)  ||
60         (type == Character.LOWERCASE_LETTER)  ||
61         (type == Character.DECIMAL_DIGIT_NUMBER)  ||
62         (aCharacter == '-') ||
63         (aCharacter == '_') ||
64         (aCharacter == ':');
65   }
66
67   private void skipWhiteSpace() throws IOException {
68     while (!reader.isAtEnd() && Character.isWhitespace(reader.peek())) {
69       reader.get();
70     }
71   }
72
73   private void readEndTag() throws IOException, HTMLParserExc {
74     StringBuffer result = new StringBuffer();
75
76     skipWhiteSpace();
77
78     while (!reader.isAtEnd() && isValidTagNameCharacter(reader.peek())) {
79       result.append(reader.get());
80     }
81
82     skipWhiteSpace();
83
84     if (!reader.isAtEnd() && reader.peek()=='>')
85       reader.get();
86
87     receiver.handleClosingTag(result.toString());
88   }
89
90   private String getName() throws IOException {
91     StringBuffer result = new StringBuffer();
92
93     skipWhiteSpace();
94
95     while (!reader.isAtEnd() && isValidTagNameCharacter(reader.peek())) {
96       result.append(reader.get());
97     }
98
99     if (result.length()==0)
100       return null;
101     else
102       return result.toString();
103   }
104
105   private String getAttributeValue() throws IOException {
106     StringBuffer result = new StringBuffer();
107
108     skipWhiteSpace();
109
110     if (!reader.isAtEnd()) {
111       if (reader.peek() == '\'' || reader.peek() == '\"') {
112         char boundary = reader.get();
113
114         while (!reader.isAtEnd() && reader.peek()!=boundary) {
115           result.append(reader.get());
116         }
117
118         if (!reader.isAtEnd() && reader.peek()==boundary)
119           reader.get();
120       }
121       else {
122         while (!reader.isAtEnd() && isValidTagNameCharacter(reader.peek())) {
123           result.append(reader.get());
124         }
125       }
126     }
127
128     return result.toString();
129   }
130
131   private void readTag() throws IOException, HTMLParserExc {
132     String tagName = getName();
133
134     Map attributes = new HashMap();
135
136     String attributeName = getName();
137     String attributeValue = null;
138
139     while (attributeName!=null) {
140       skipWhiteSpace();
141
142       if (!reader.isAtEnd() && reader.peek()=='=') {
143         reader.get();
144         attributeValue = getAttributeValue();
145       }
146
147       attributes.put(attributeName, attributeValue);
148
149       attributeName = getName();
150     }
151
152     boolean isClosed=false;
153
154     skipWhiteSpace();
155     if (!reader.isAtEnd() && reader.peek()=='/') {
156       isClosed = true;
157       reader.get();
158     }
159
160     skipWhiteSpace();
161     if (!reader.isAtEnd() && reader.peek()=='>') {
162       reader.get();
163     }
164
165     receiver.handleOpenTag(tagName, attributes);
166     if (isClosed)
167       receiver.handleClosingTag(tagName);
168   }
169
170   private void readSpecial() throws IOException, HTMLParserExc  {
171     StringBuffer result = new StringBuffer();
172
173     if (!reader.isAtEnd() && reader.peek()=='-') {
174       reader.get();
175       if (!reader.isAtEnd() && reader.peek()=='-') {
176         reader.get();
177
178         while (!reader.isAtEnd()) {
179           if (reader.peek()=='-') {
180             reader.get();
181             if (!reader.isAtEnd() && reader.peek()=='-') {
182               reader.get();
183               break;
184             }
185             result.append('-');
186           }
187           if (!reader.isAtEnd())
188             result.append(reader.get());
189         }
190
191         skipWhiteSpace();
192
193         if (!reader.isAtEnd() && reader.peek()=='>')
194           reader.get();
195
196         receiver.handleComment(result.toString());
197
198         return;
199       }
200       else {
201         result.append('-');
202       }
203     }
204
205     while (!reader.isAtEnd() && reader.peek()!='>') {
206       result.append(reader.get());
207     }
208
209     if (!reader.isAtEnd() && reader.peek()=='>')
210       reader.get();
211
212     receiver.handleDTD("<!"+result.toString()+">");
213   }
214
215   private void readCData() throws IOException, HTMLParserExc {
216     StringBuffer result = new StringBuffer();
217
218     while (!reader.isAtEnd() && reader.peek()!='<') {
219       result.append(reader.get());
220     }
221
222     receiver.handleCData(result.toString());
223   }
224
225   /**
226    * Class to provide for a 1 character look-ahead on a reader
227    */
228   public static class ReaderWrapper {
229     private Reader reader;
230     private char buffer;
231     private boolean haveBuffer;
232
233     public ReaderWrapper(Reader aReader) {
234       reader = aReader;
235       haveBuffer = false;
236     }
237
238     /**
239      * Returns <code>true</code> if the stream contains no more characters.
240      */
241     public boolean isAtEnd() throws IOException {
242       fillBuffer();
243
244       return !haveBuffer;
245     }
246
247     /**
248      * Gets the next character from the reader but will not remove it from the
249      *    stream.
250      *    {@link #isAtEnd()} must return <code>false</code> before call this
251      *    routine.
252      */
253     public char peek() throws IOException {
254       fillBuffer();
255
256       return buffer;
257     }
258
259     /**
260      * Gets the next character from the reader and removes it from the stream.
261      *    {@link #isAtEnd()} must return <code>false</code> before call this
262      *    routine.
263      */
264     public char get() throws IOException {
265       fillBuffer();
266       haveBuffer = false;
267
268       return buffer;
269     }
270
271     /**
272      * If the reader is not at it's end, then upon return, the buffer will
273      *    be filled. If the buffer was already filled, then this method won't
274      *    do anything.
275      */
276     public void fillBuffer() throws IOException {
277       if (!haveBuffer) {
278         int c = reader.read();
279
280         if (c!=-1) {
281           buffer = (char) c;
282           haveBuffer=true;
283         }
284       }
285     }
286   }
287 }