]> gitweb.fperrin.net Git - DictionaryPC.git/blob - src/com/hughes/android/dictionary/parser/WikiParser.java
go
[DictionaryPC.git] / src / com / hughes / android / dictionary / parser / WikiParser.java
1 package com.hughes.android.dictionary.parser;
2
3 import java.util.ArrayList;
4 import java.util.LinkedHashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.regex.Matcher;
8 import java.util.regex.Pattern;
9
10 public class WikiParser {
11   
12   private static final Pattern markup = Pattern.compile("$|''|\\{\\{|\\[\\[|(==+)\\s*$|<!--|<pre>", Pattern.MULTILINE);
13   private static final Pattern listStart = Pattern.compile("^[*#;:]+");
14   private static final Pattern pipeSplit = Pattern.compile("\\s*\\|\\s*");
15   private static final Pattern whitespace = Pattern.compile("\\s+");
16   private static final Pattern headerStart = Pattern.compile("^==+");
17   
18   
19   static void parse(final String wikiText, final WikiCallback callback) {
20     
21     boolean boldOn = false;
22     boolean italicOn = false;
23     int insideHeaderDepth = -1;
24     String lastListItem = null;
25
26     final List<String> positionalArgs = new ArrayList<String>();
27     final Map<String, String> namedArgs = new LinkedHashMap<String, String>();
28
29     String rest = wikiText;
30     while (rest.length() > 0) {
31       final Matcher matcher = markup.matcher(rest);
32       if (matcher.find()) {
33         final int nextMarkupPos = matcher.start();
34         if (nextMarkupPos != 0) {
35           String text = rest.substring(0, nextMarkupPos);
36           whitespace.matcher(text).replaceAll(" ");
37           callback.onText(text);
38           rest = rest.substring(nextMarkupPos);
39         }
40         
41         if (rest.equals("")) {
42           continue;
43         } else if (rest.startsWith("\n")) {
44           rest = rest.substring(1);
45           
46           if (insideHeaderDepth != -1) {
47             throw new RuntimeException("barf");
48           }
49           if (lastListItem != null) {
50             callback.onListItemEnd(lastListItem, null);
51           }
52           
53           final Matcher headerMatcher = headerStart.matcher(rest);
54           if (headerMatcher.find()) {
55             lastListItem = null;
56             insideHeaderDepth = headerMatcher.group().length();            
57             callback.onHeadingStart(insideHeaderDepth);
58             rest = rest.substring(headerMatcher.group().length());
59             continue;
60           }
61
62           final Matcher listStartMatcher = listStart.matcher(rest);
63           if (listStartMatcher.find()) {
64             lastListItem = listStartMatcher.group();
65             callback.onListItemStart(lastListItem, null);
66             rest = rest.substring(lastListItem.length());
67             continue;
68           } else if (lastListItem != null) {
69             callback.onNewParagraph();
70             lastListItem = null;
71           }
72           
73           if (rest.startsWith("\n")) {
74             callback.onNewParagraph();
75             continue;
76           }
77           callback.onNewLine();
78         } else if (rest.startsWith("'''")) {
79           boldOn = !boldOn;
80           callback.onFormatBold(boldOn);
81           rest = rest.substring(3);
82         } else if (rest.startsWith("''")) {
83           italicOn = !italicOn;
84           callback.onFormatItalic(italicOn);
85           rest = rest.substring(2);
86         } else if (rest.startsWith("{{")) {
87           int end = rest.indexOf("}}");
88           if (end == -1) {
89             callback.onUnterminated("{{", rest);
90             return;
91           }
92           final String template = rest.substring(2, end).trim();
93           //todo: this doesn't work.  can't split pipes inside [[asdf|asdf]]
94           final List<String> templateArray = new ArrayList<String>();
95           contextSensitivePipeSplit(template, templateArray);
96           positionalArgs.clear();
97           namedArgs.clear();
98           for (int i = 0; i < templateArray.size(); ++i) {
99             
100             int equalPos = -1;
101             do {
102               equalPos = templateArray.get(i).indexOf('=', equalPos + 1);
103             } while (equalPos > 1 && templateArray.get(i).charAt(equalPos - 1) == ' ');
104
105             if (equalPos == -1) {
106               positionalArgs.add(templateArray.get(i));
107             } else {
108               namedArgs.put(templateArray.get(i).substring(0, equalPos), templateArray.get(i).substring(equalPos + 1));
109             }
110           }
111           callback.onTemplate(positionalArgs, namedArgs);
112           rest = rest.substring(end + 2);
113         } else if (rest.startsWith("[[")) {
114           int end = rest.indexOf("]]");
115           if (end == -1) {
116             callback.onUnterminated("[[", rest);
117             return;
118           }
119           final String wikiLink = rest.substring(2, end);
120           final String[] args = pipeSplit.split(wikiLink);
121           callback.onWikiLink(args);
122           rest = rest.substring(end + 2);
123         } else if (rest.startsWith("=")) {
124           final String match = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
125           if (insideHeaderDepth == -1) {
126           } else {
127             if (match.length() != insideHeaderDepth) {
128               callback.onInvalidHeaderEnd(rest);
129               return;
130             }
131             callback.onHeadingEnd(insideHeaderDepth);
132             insideHeaderDepth = -1;
133           }
134           rest = rest.substring(match.length());
135         } else if (rest.startsWith("<!--")) {
136           int end = rest.indexOf("-->");
137           if (end == -1) {
138             callback.onUnterminated("<!--", rest);
139             return;
140           }
141           callback.onComment(rest.substring(4, end));
142           rest = rest.substring(end + 3);
143         } else if (rest.startsWith("<pre>")) {
144           int end = rest.indexOf("</pre>");
145           if (end == -1) {
146             callback.onUnterminated("<pre>", rest);
147             return;
148           }
149           callback.onText(rest.substring(5, end));
150           rest = rest.substring(end + 6);
151         } else {
152           throw new RuntimeException("barf: " + rest);
153         }
154       }  // matcher.find()
155     }
156   }
157   
158   private static final Pattern openBracketOrPipe = Pattern.compile("($)|(\\[\\[)|(\\s*\\|\\s*)");
159   private static void contextSensitivePipeSplit(String template, final List<String> result) {
160     StringBuilder builder = new StringBuilder();
161     while (template.length() > 0) {
162       final Matcher matcher = openBracketOrPipe.matcher(template);
163       if (matcher.find()) {
164         // append to the match.
165         builder.append(template.substring(0, matcher.start()));
166         if (matcher.group(2) != null) {  // [[
167           // append to the close ]].
168           final int closeIndex = template.indexOf("]]", matcher.end());
169           builder.append(template.substring(matcher.start(), closeIndex + 2));
170           template = template.substring(closeIndex + 2);
171         } else if (matcher.group(3) != null) { // |
172           result.add(builder.toString());
173           builder = new StringBuilder();
174           template = template.substring(matcher.end());
175         } else {
176           template = template.substring(matcher.start());
177           assert template.length() == 0 : template;
178         }
179       } else {
180         assert false;
181       }
182     }
183     result.add(builder.toString());
184   }
185
186   // ------------------------------------------------------------------------
187
188   public static String simpleParse(final String wikiText) {
189     final StringBuilderCallback callback = new StringBuilderCallback();
190     parse(wikiText, callback);
191     return callback.builder.toString();
192   }
193   
194   static final class StringBuilderCallback implements WikiCallback {
195
196     final StringBuilder builder = new StringBuilder();
197     
198     @Override
199     public void onComment(String text) {
200     }
201
202     @Override
203     public void onFormatBold(boolean boldOn) {
204     }
205
206     @Override
207     public void onFormatItalic(boolean italicOn) {
208     }
209
210     @Override
211     public void onWikiLink(String[] args) {
212       builder.append(args[args.length - 1]);
213     }
214
215     @Override
216     public void onTemplate(List<String> positionalArgs,
217         Map<String, String> namedArgs) {
218       builder.append("{{").append(positionalArgs).append(namedArgs).append("}}");
219     }
220
221     @Override
222     public void onText(String text) {
223       builder.append(text);
224     }
225
226     @Override
227     public void onHeadingStart(int depth) {
228     }
229
230     @Override
231     public void onHeadingEnd(int depth) {
232     }
233
234     @Override
235     public void onNewLine() {
236     }
237
238     @Override
239     public void onNewParagraph() {
240     }
241
242     @Override
243     public void onListItemStart(String header, int[] section) {
244     }
245
246     @Override
247     public void onListItemEnd(String header, int[] section) {
248     }
249
250     @Override
251     public void onUnterminated(String start, String rest) {
252       throw new RuntimeException(start + rest);
253     }
254
255     @Override
256     public void onInvalidHeaderEnd(String rest) {
257       throw new RuntimeException(rest);
258     }
259     
260   }
261
262
263 }