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