package scrum.client.wiki; import ilarkesto.core.base.Str; import scrum.client.ScrumGwtApplication; /** * http://en.wikipedia.org/wiki/Wikipedia:Cheatsheet http://en.wikipedia.org/wiki/Help:Table */ public class WikiParser { private String input; private WikiModel model; private boolean oneliner; public WikiParser(String input) { assert input != null; this.input = input; } private void appendWord(Paragraph p, String word) { if (isUrl(word)) { p.add(new Link(word)); return; } if (word.length() > 1 && isIgnorableWordPrefix(word.charAt(0))) { p.add(new Text(word.substring(0, 1))); word = word.substring(1); } StringBuilder suffix = null; for (int i = word.length() - 1; i >= 0; i--) { if (isIgnorableWordSuffix(word.charAt(i))) { if (suffix == null) suffix = new StringBuilder(); suffix.insert(0, word.charAt(i)); } else { break; } } if (suffix != null) word = word.substring(0, word.length() - suffix.length()); if (isEntityReference(word)) { p.add(new EntityReference(word)); if (suffix != null) p.add(new Text(suffix.toString())); return; } p.add(new Text(word)); if (suffix != null) p.add(new Text(suffix.toString())); } private Paragraph appendText(Paragraph p, String text) { int begin; // code begin = text.indexOf("<code>"); if (begin >= 0 && begin < text.length() - 7) { int end = text.indexOf("</code>", begin); if (end > begin) { String prefix = text.substring(0, begin); String content = text.substring(begin + 6, end); String suffix = text.substring(end + 7); if (content.trim().length() > 0) { appendText(p, prefix); p.add(new Code(content)); appendText(p, suffix); return p; } } } begin = text.indexOf('\n'); if (begin > 0) { String prefix = text.substring(0, begin); String suffix = text.substring(begin + 1); appendText(p, prefix); p.add(new LineBreak()); appendText(p, suffix); return p; } // internal links begin = text.indexOf("[["); if (begin >= 0 && begin < text.length() - 4) { int end = text.indexOf("]]", begin); if (end > begin) { String prefix = text.substring(0, begin); String content = text.substring(begin + 2, end); String suffix = text.substring(end + 2); if (content.trim().length() > 0) { appendText(p, prefix); if (content.startsWith("Image:")) { createImage(p, content.substring(6)); } else { createEntityReference(p, content); } appendText(p, suffix); return p; } } } // external links begin = text.indexOf("["); if (begin >= 0 && begin < text.length() - 3) { if (text.charAt(begin + 1) != '[') { int end = text.indexOf("]", begin); if (end > begin) { String prefix = text.substring(0, begin); String link = text.substring(begin + 1, end); String suffix = text.substring(end + 1); appendText(p, prefix); int spaceIdx = link.indexOf(' '); if (spaceIdx > 0) { String href = link.substring(0, spaceIdx); String label = link.substring(spaceIdx + 1); p.add(new Link(href, label)); } else { p.add(new Link(link)); } appendText(p, suffix); return p; } } } // strong end emph begin = text.indexOf("'''''"); if (begin >= 0 && begin < text.length() - 5) { int end = text.indexOf("'''''", begin + 5); if (end >= 0) { String prefix = text.substring(0, begin); String content = text.substring(begin + 5, end); String suffix = text.substring(end + 5); if (content.trim().length() > 0) { appendText(p, prefix); Highlight h = new Highlight(true, true); appendText(h, content); p.add(h); appendText(p, suffix); return p; } } } else { begin = text.indexOf("'''"); if (begin >= 0 && begin < text.length() - 3) { int end = text.indexOf("'''", begin + 3); if (end >= 0) { String prefix = text.substring(0, begin); String content = text.substring(begin + 3, end); String suffix = text.substring(end + 3); if (content.trim().length() > 0) { appendText(p, prefix); Highlight h = new Highlight(false, true); appendText(h, content); p.add(h); appendText(p, suffix); return p; } } } else { begin = text.indexOf("''"); if (begin >= 0 && begin < text.length() - 2) { int end = text.indexOf("''", begin + 2); if (end >= 0) { String prefix = text.substring(0, begin); String content = text.substring(begin + 2, end); String suffix = text.substring(end + 2); if (content.trim().length() > 0) { appendText(p, prefix); Highlight h = new Highlight(true, false); appendText(h, content); p.add(h); appendText(p, suffix); return p; } } } } } while (text.length() > 0) { int idx = text.indexOf(' '); if (idx < 0) { appendWord(p, text); text = ""; } else { appendWord(p, text.substring(0, idx)); p.add(Text.SPACE); text = text.substring(idx + 1); } } p.add(new Text(text)); return p; } private void createImage(Paragraph p, String code) { boolean thumb = code.contains("|thumb"); if (thumb) { code = code.replace("|thumb", ""); } boolean left = code.contains("|left"); if (left) { code = code.replace("|left", ""); } p.add(new Image(code, thumb, left)); } private void createEntityReference(Paragraph p, String code) { int sepIdx = code.indexOf('|'); if (sepIdx > 0) { String name = code.substring(0, sepIdx); String label = code.substring(sepIdx + 1); p.add(new EntityReference("[[" + name + "]]", label)); } else { p.add(new EntityReference("[[" + code + "]]", code)); } } private void nextPart() { nextLine = null; // empty if (input.length() == 0) { input = null; return; } // empty lines if (input.startsWith("\n")) { String line = getNextLine(); while (line.trim().length() == 0 && input.length() > 0) { burn(line.length() + 1); line = getNextLine(); } return; } // h1 if (input.startsWith("= ")) { String line = getNextLine(); if (line.length() > 4 && line.endsWith(" =")) { model.add(new Header(line.substring(2, line.length() - 2), 1)); burn(line.length() + 1); return; } } // h2 if (input.startsWith("== ")) { String line = getNextLine(); if (line.length() > 6 && line.endsWith(" ==")) { model.add(new Header(line.substring(3, line.length() - 3), 2)); burn(line.length() + 1); return; } } // h3 if (input.startsWith("=== ")) { String line = getNextLine(); if (line.length() > 8 && line.endsWith(" ===")) { model.add(new Header(line.substring(4, line.length() - 4), 3)); burn(line.length() + 1); return; } } // h4 if (input.startsWith("==== ")) { String line = getNextLine(); if (line.length() > 10 && line.endsWith(" ====")) { model.add(new Header(line.substring(5, line.length() - 5), 4)); burn(line.length() + 1); return; } } // code if (input.startsWith("<code>")) { int endIdx = input.indexOf("</code>"); if (endIdx > 0) { String code = input.substring(6, endIdx); Paragraph p = new Paragraph(true); p.add(new Code(code)); model.add(p); burn(endIdx + 8); return; } } // pre if (input.startsWith(" ")) { StringBuilder sb = new StringBuilder(); String line = getNextLine(); boolean first = true; while (line.startsWith(" ")) { if (first) { first = false; } else { sb.append("\n"); } sb.append(line); burn(line.length() + 1); line = getNextLine(); } model.add(new Pre(sb.toString())); return; } // list if (input.startsWith("* ") || input.startsWith("# ")) { boolean ordered = input.startsWith("#"); ItemList list = new ItemList(ordered); Paragraph item = null; String line = getNextLine(); String leadingSpaces = Str.getLeadingSpaces(line); String lineTrimmed = leadingSpaces.length() == 0 ? line : line.substring(leadingSpaces.length()); while (!line.startsWith("\n") && line.length() > 0) { if (lineTrimmed.startsWith("# ") || lineTrimmed.startsWith("* ")) { item = new Paragraph(false); appendText(item, lineTrimmed.substring(2)); list.add(item, leadingSpaces, lineTrimmed.startsWith("#")); } else { item.add(LineBreak.INSTANCE); appendText(item, line); } burn(line.length() + 1); line = getNextLine(); leadingSpaces = Str.getLeadingSpaces(line); lineTrimmed = leadingSpaces.length() == 0 ? line : line.substring(leadingSpaces.length()); } model.add(list); return; } // table if (input.startsWith("{|")) { burn(2); Table table = new Table(); model.add(table); Paragraph p = null; boolean header = false; while (true) { boolean lastLine = false; String line = getNextLine(); burn(line.length() + 1); if (line.length() == 0 && input.length() == 0) return; int closeTagIndex = line.indexOf("|}"); if (closeTagIndex >= 0) { line = line.substring(0, closeTagIndex); lastLine = true; } if (line.startsWith("|-")) { table.addCell(p, header); table.nextRow(); p = null; header = false; continue; } if (line.startsWith("|") || line.startsWith("!")) { table.addCell(p, header); p = null; header = line.startsWith("!"); line = line.substring(1); } if (line.length() > 0) { if (p == null) { p = new Paragraph(false); } else { p.add(new Text("\n")); } int cellSepIndex = Str.indexOf(line, new String[] { "||", "!!", "!|" }, 0); while (cellSepIndex >= 0) { String cellContent = line.substring(0, cellSepIndex); appendText(p, cellContent); table.addCell(p, header); p = new Paragraph(false); header = line.charAt(cellSepIndex) == '!'; line = line.substring(cellSepIndex + 2); cellSepIndex = Str.indexOf(line, new String[] { "||", "!!", "!|" }, 0); } appendText(p, line); } if (lastLine) { table.addCell(p, header); return; } } } // toc if (input.startsWith("TOC")) { String line = getNextLine(); if (line.equals("TOC")) { burn(line.length() + 1); model.add(new Toc(model)); return; } } // paragraph model.add(appendText(new Paragraph(!oneliner), cutParagraph())); } public WikiModel parse(boolean onelinerWithoutP) { model = new WikiModel(); input = input.replace("\r", ""); input = input.replace("\t", " "); oneliner = onelinerWithoutP && input.indexOf('\n') < 0; while (input != null) { nextPart(); } return model; } private boolean isIgnorableWordPrefix(char c) { return c == '('; } private boolean isIgnorableWordSuffix(char c) { return c == '.' || c == ',' || c == '!' || c == '?' || c == ')'; } private boolean isUrl(String s) { if (s.startsWith("http://")) return true; if (s.startsWith("https://")) return true; if (s.startsWith("www.")) return true; if (s.startsWith("ftp://")) return true; if (s.startsWith("apt://")) return true; if (s.startsWith("mailto://")) return true; return false; } private boolean isEntityReference(String s) { if (s.length() < 4) return false; if (!Character.isDigit(s.charAt(3))) return false; boolean prefixOk = false; for (String prefix : ScrumGwtApplication.REFERENCE_PREFIXES) { if (s.startsWith(prefix)) { prefixOk = true; break; } } if (!prefixOk) return false; int len = s.length(); for (int i = 3; i < len; i++) { if (!Character.isDigit(s.charAt(i))) return false; } try { String number = s.substring(3); Integer.parseInt(number); } catch (NumberFormatException ex) { return false; } return true; } private String cutParagraph() { StringBuilder sb = new StringBuilder(); boolean first = true; while (input.length() > 0) { String line = getNextLine(); if (line.trim().length() == 0) { break; } if (first) { first = false; } else { sb.append('\n'); } sb.append(line); burn(line.length() + 1); } return sb.toString(); } private void burn(int length) { nextLine = null; if (length >= input.length()) { input = ""; } else { input = input.substring(length); } } private String nextLine; private String getNextLine() { if (nextLine == null) { int idx = input.indexOf('\n'); if (idx < 0) return input; nextLine = input.substring(0, idx); } return nextLine; } public static final String SYNTAX_INFO_HTML = "<table class='WikiParser-syntax-table' cellpadding='5px'>" + "<tr><td><i>Italic text</i></td> <td><code>''italic text''</code></td></tr>" + "<tr><td><b>Bold text</b></td> <td><code>'''bold text'''</code></td></tr>" + "<tr><td><b><i>Bold and italic</i></b></td> <td><code>'''''bold and italic'''''</td></tr>" + "<tr><td>Internal link</td> <td><code>[[Name of page]]<br>[[Name of page|Text to display]]</code></td></tr>" + "<tr><td>External link</td> <td><code>[http://servisto.de]<br>[http://servisto.de Text to display]<br>http://servisto.de</code></td></tr>" + "<tr><td><h2>Section headings</h2></td> <td><code>= heading 1 =<br>== heading 2 ==<br>=== heading 3 ===<br>==== heading 4 ====</code></td></tr>" + "<tr><td>Bulleted list</td> <td><code>* Item 1<br>* Item 2<br>* Item 3</code></td></tr>" + "<tr><td>Numbered list</td> <td><code># Item<br># Item 2<br># Item 3</code></td></tr>" + "<tr><td>Internal image<br>thumb</td> <td><code>[[Image:fle3]]<br>[[Image:fle3|thumb]]</code></td></tr>" + "<tr><td>External image<br>thumb</td> <td><code>[[Image:http://servisto.de/image.jpg]]<br>[[Image:http://servisto.de/image.jpg|thumb|left]]</code></td></tr>" + "<tr><td><code>Code</code></td> <td><code><code>Code</code></code></td></tr>" + "</table>"; }