]> gitweb.fperrin.net Git - DictionaryPC.git/blob - src/com/hughes/android/dictionary/parser/wiktionary/AbstractWiktionaryParser.java
55a57f46595dafdab65e0e297ec68c73f1adda87
[DictionaryPC.git] / src / com / hughes / android / dictionary / parser / wiktionary / AbstractWiktionaryParser.java
1 // Copyright 2012 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package com.hughes.android.dictionary.parser.wiktionary;
16
17 import java.io.BufferedInputStream;
18 import java.io.DataInputStream;
19 import java.io.EOFException;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.SortedMap;
30 import java.util.TreeMap;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.logging.Level;
33 import java.util.logging.Logger;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36
37 import org.apache.commons.compress.compressors.CompressorException;
38 import org.apache.commons.compress.compressors.CompressorStreamFactory;
39
40 import com.hughes.android.dictionary.engine.EntrySource;
41 import com.hughes.android.dictionary.engine.EntryTypeName;
42 import com.hughes.android.dictionary.engine.IndexBuilder;
43 import com.hughes.android.dictionary.engine.IndexedEntry;
44 import com.hughes.android.dictionary.engine.ReadAheadBuffer;
45 import com.hughes.android.dictionary.parser.Parser;
46 import com.hughes.android.dictionary.parser.WikiTokenizer;
47 import com.hughes.util.EnumUtil;
48
49 public abstract class AbstractWiktionaryParser implements Parser {
50
51     static final Logger LOG = Logger.getLogger("WiktionaryParser");
52
53     private static final Pattern SUPERSCRIPT = Pattern.compile("<sup>[0-9]*</sup>");
54
55     final SortedMap<String, AtomicInteger> counters = new TreeMap<String, AtomicInteger>();
56     final Set<String> pairsAdded = new LinkedHashSet<String>();
57
58     public EntrySource entrySource;
59     public String title;
60
61
62     abstract void parseSection(final String heading, final String text);
63
64     abstract void removeUselessArgs(final Map<String, String> namedArgs);
65
66     private static String replaceSuperscript(String in) {
67         Matcher matcher;
68         while ((matcher = SUPERSCRIPT.matcher(in)).find()) {
69             String replace = "";
70             String orig = matcher.group();
71             for (int i = 5; i < orig.length() - 6; i++)
72             {
73                 char c = 0;
74                 switch (orig.charAt(i)) {
75                 case '0': c = '\u2070'; break;
76                 case '1': c = '\u00b9'; break;
77                 case '2': c = '\u00b2'; break;
78                 case '3': c = '\u00b3'; break;
79                 case '4': c = '\u2074'; break;
80                 case '5': c = '\u2075'; break;
81                 case '6': c = '\u2076'; break;
82                 case '7': c = '\u2077'; break;
83                 case '8': c = '\u2078'; break;
84                 case '9': c = '\u2079'; break;
85                 }
86                 if (c == 0) throw new RuntimeException();
87                 replace += c;
88             }
89             in = matcher.replaceFirst(replace);
90         }
91         return in;
92     }
93
94     @Override
95     public void parse(final File file, final EntrySource entrySource, final int pageLimit) throws IOException {
96         this.entrySource = entrySource;
97         int pageCount = 0;
98         File input = new File(file.getPath() + ".bz2");
99         if (!input.exists()) input = new File(file.getPath() + ".gz");
100         if (!input.exists()) input = new File(file.getPath() + ".xz");
101         DataInputStream dis;
102         if (!input.exists()) {
103             // Fallback to uncompressed file
104             dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
105         } else {
106             InputStream compressedIn = new BufferedInputStream(new FileInputStream(input));
107             try {
108                 InputStream in = new CompressorStreamFactory().createCompressorInputStream(compressedIn);
109                 in = new ReadAheadBuffer(in, 20 * 1024 * 1024);
110                 dis = new DataInputStream(in);
111             } catch (CompressorException e) {
112                 throw new IOException(e);
113             }
114         }
115         try {
116             while (true) {
117                 if (pageLimit >= 0 && pageCount >= pageLimit) {
118                     return;
119                 }
120
121                 try {
122                     title = dis.readUTF();
123                 } catch (EOFException e) {
124                     LOG.log(Level.INFO, "EOF reading split.");
125                     dis.close();
126                     return;
127                 }
128                 final String heading = dis.readUTF();
129                 final int bytesLength = dis.readInt();
130                 final byte[] bytes = new byte[bytesLength];
131                 dis.readFully(bytes);
132                 final String text = new String(bytes, "UTF8");
133
134                 parseSection(heading, replaceSuperscript(text));
135
136                 ++pageCount;
137                 if (pageCount % 1000 == 0) {
138                     LOG.info("pageCount=" + pageCount);
139                 }
140             }
141         } finally {
142             dis.close();
143             LOG.info("***COUNTERS***");
144             for (final Map.Entry<String, AtomicInteger> entry : counters.entrySet()) {
145                 LOG.info(entry.getKey() + ": " + entry.getValue());
146             }
147         }
148     }
149
150     static final Pattern whitespace = Pattern.compile("\\s+");
151     static String trim(final String s) {
152         return whitespace.matcher(s).replaceAll(" ").trim();
153     }
154
155     public void incrementCount(final String string) {
156         AtomicInteger counter = counters.get(string);
157         if (counter == null) {
158             counter = new AtomicInteger();
159             counters.put(string, counter);
160         }
161         counter.incrementAndGet();
162     }
163
164     public void addLinkToCurrentEntry(final String token, final String lang, final EntryTypeName entryTypeName) {
165         assert false : token + ", title=" + title;
166     }
167
168
169     // -------------------------------------------------------------------------
170
171     static class AppendAndIndexWikiCallback<T extends AbstractWiktionaryParser> implements WikiTokenizer.Callback {
172
173         final T parser;
174         StringBuilder builder;
175         IndexedEntry indexedEntry;
176         IndexBuilder indexBuilder;
177         final Map<String,FunctionCallback<T>> functionCallbacks = new LinkedHashMap<String, FunctionCallback<T>>();
178
179         boolean entryTypeNameSticks = false;
180         EntryTypeName entryTypeName = null;
181
182         final Map<String,AtomicInteger> langCodeToTCount = new LinkedHashMap<String, AtomicInteger>();
183
184         final NameAndArgs<T> nameAndArgs = new NameAndArgs<T>();
185
186         public AppendAndIndexWikiCallback(final T parser) {
187             this.parser = parser;
188         }
189
190         public void reset(final StringBuilder builder, final IndexedEntry indexedEntry) {
191             this.builder = builder;
192             this.indexedEntry = indexedEntry;
193             this.indexBuilder = null;
194             entryTypeName = null;
195             entryTypeNameSticks = false;
196         }
197
198         public void dispatch(final String wikiText, final IndexBuilder indexBuilder, final EntryTypeName entryTypeName) {
199             final IndexBuilder oldIndexBuilder = this.indexBuilder;
200             final EntryTypeName oldEntryTypeName = this.entryTypeName;
201             this.indexBuilder = indexBuilder;
202             if (!entryTypeNameSticks) {
203                 this.entryTypeName = EnumUtil.min(entryTypeName, this.entryTypeName);
204             }
205             if (entryTypeName == null) this.entryTypeName = null;
206             WikiTokenizer.dispatch(wikiText, false, this);
207             this.indexBuilder = oldIndexBuilder;
208             this.entryTypeName = oldEntryTypeName;
209         }
210
211         public String dispatch(final String wikiText, final EntryTypeName entryTypeName) {
212             final int start = builder.length();
213             dispatch(wikiText, this.indexBuilder, entryTypeName);
214             return builder.substring(start);
215         }
216
217         @Override
218         public void onPlainText(final String plainText) {
219             // The only non-recursive callback.  Just appends to the builder, and indexes.
220             builder.append(plainText);
221             if (indexBuilder != null && entryTypeName != null && indexedEntry != null) {
222                 indexBuilder.addEntryWithString(indexedEntry, plainText, entryTypeName);
223             }
224         }
225
226         @Override
227         public void onWikiLink(WikiTokenizer wikiTokenizer) {
228             final String text = wikiTokenizer.wikiLinkText();
229             @SuppressWarnings("unused")
230             final String link = wikiTokenizer.wikiLinkDest();
231             dispatch(text, entryTypeName);
232         }
233
234         @Override
235         public void onFunction(
236             final WikiTokenizer wikiTokenizer,
237             final String name,
238             final List<String> args,
239             final Map<String, String> namedArgs) {
240
241             FunctionCallback<T> functionCallback = functionCallbacks.get(name);
242             if (functionCallback == null || !functionCallback.onWikiFunction(wikiTokenizer, name, args, namedArgs, parser, this)) {
243                 // Default function handling:
244                 parser.removeUselessArgs(namedArgs);
245                 final boolean single = args.isEmpty() && namedArgs.isEmpty();
246                 builder.append(single ? "{" : "{{");
247
248                 final IndexBuilder oldIndexBuilder = indexBuilder;
249                 indexBuilder = null;
250                 nameAndArgs.onWikiFunction(wikiTokenizer, name, args, namedArgs, parser, this);
251                 indexBuilder = oldIndexBuilder;
252
253                 builder.append(single ? "}" : "}}");
254             }
255         }
256
257         @Override
258         public void onHtml(WikiTokenizer wikiTokenizer) {
259             if (wikiTokenizer.token().startsWith("<ref>")) {
260                 // Do nothing.
261                 return;
262             }
263             // Unindexed for now.
264             builder.append(wikiTokenizer.token());
265         }
266
267         @Override
268         public void onMarkup(WikiTokenizer wikiTokenizer) {
269             // Do nothing.
270         }
271
272         @Override
273         public final void onComment(WikiTokenizer wikiTokenizer) {
274             // Do nothing.
275         }
276
277         @Override
278         public void onNewline(WikiTokenizer wikiTokenizer) {
279             assert false;
280         }
281
282         @Override
283         public void onHeading(WikiTokenizer wikiTokenizer) {
284             assert false;
285         }
286
287         @Override
288         public void onListItem(WikiTokenizer wikiTokenizer) {
289             assert false;
290         }
291
292     }
293
294     // --------------------------------------------------------------------
295
296     static final class NameAndArgs<T extends AbstractWiktionaryParser> implements FunctionCallback<T> {
297         @Override
298         public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
299                                       final Map<String, String> namedArgs, final T parser,
300                                       final AppendAndIndexWikiCallback<T> appendAndIndexWikiCallback) {
301
302             if (name != null) {
303                 appendAndIndexWikiCallback.dispatch(name, null);
304             }
305             for (int i = 0; i < args.size(); ++i) {
306                 if (args.get(i).length() > 0) {
307                     appendAndIndexWikiCallback.builder.append("|");
308                     appendAndIndexWikiCallback.dispatch(args.get(i), null, null);
309                 }
310             }
311             appendNamedArgs(namedArgs, appendAndIndexWikiCallback);
312             return true;
313         }
314     }
315     static NameAndArgs<AbstractWiktionaryParser> NAME_AND_ARGS = new NameAndArgs<AbstractWiktionaryParser>();
316
317     static void appendNamedArgs(final Map<String, String> namedArgs,
318                                 final AppendAndIndexWikiCallback<?> appendAndIndexWikiCallback) {
319         for (final Map.Entry<String, String> entry : namedArgs.entrySet()) {
320             appendAndIndexWikiCallback.builder.append("|");
321             appendAndIndexWikiCallback.dispatch(entry.getKey(), null, null);
322             appendAndIndexWikiCallback.builder.append("=");
323             EntryTypeName entryTypeName = null;
324             IndexBuilder indexBuilder = null;
325             // This doesn't work: we'd need to add to word-forms.
326 //      System.out.println(entry.getKey());
327 //      if (entry.getKey().equals("tr")) {
328 //        entryTypeName = EntryTypeName.WIKTIONARY_TRANSLITERATION;
329 //        indexBuilder = appendAndIndexWikiCallback.parser.foreignIndexBuilder;
330 //      }
331             appendAndIndexWikiCallback.dispatch(entry.getValue(), indexBuilder, entryTypeName);
332         }
333     }
334
335 }