|
From: <jbo...@li...> - 2005-08-11 04:45:00
|
Author: ral...@jb...
Date: 2005-08-11 00:43:24 -0400 (Thu, 11 Aug 2005)
New Revision: 817
Added:
trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiContext.txt
trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiSyntax.txt
trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiTranslator.txt
Log:
the skeleton of the HTML parser for wiki - a lot more to finish
Added: trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiContext.txt
===================================================================
--- trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiContext.txt 2005-08-11 03:49:18 UTC (rev 816)
+++ trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiContext.txt 2005-08-11 04:43:24 UTC (rev 817)
@@ -0,0 +1,107 @@
+/*
+ * Created on Aug 9, 2005
+ *
+ *
+ */
+
+/**
+ * @author rali
+ *
+ *
+ */
+package org.jboss.wiki.plugins;
+//import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+
+
+/**
+ * Provides state information throughout the processing of a page. A
+ * WikiContext is born when the JSP pages that are the main entry
+ * points, are invoked. The JSPWiki engine creates the new
+ * WikiContext, which basically holds information about the page, the
+ * handling engine, and in which context (view, edit, etc) the
+ * call was done.
+ * <P>
+ * A WikiContext also provides request-specific variables, which can
+ * be used to communicate between plugins on the same page, or
+ * between different instances of the same plugin. A WikiContext
+ * variable is valid until the processing of the page has ended. For
+ * an example, please see the Counter plugin.
+ *
+ * @see com.ecyrd.jspwiki.plugin.Counter
+ *
+ * @author Janne Jalkanen
+ */
+public class WikiContext
+{
+ String m_requestContext = VIEW;
+
+ Map m_variableMap = new HashMap();
+
+ /** The VIEW context - the user just wants to view the page
+ contents. */
+ public static final String VIEW = "view";
+
+ /** The EDIT context - the user is editing the page. */
+ public static final String EDIT = "edit";
+
+ /** User is preparing for a login/authentication. */
+ public static final String LOGIN = "login";
+
+ /** User is viewing a DIFF between the two versions of the page. */
+ public static final String DIFF = "diff";
+
+ /** User is viewing page history. */
+ public static final String INFO = "info";
+
+ /** User is previewing the changes he just made. */
+ public static final String PREVIEW = "preview";
+
+ /** User has an internal conflict, and does quite not know what to
+ do. Please provide some counseling. */
+ public static final String CONFLICT = "conflict";
+
+ /** An error has been encountered and the user needs to be informed. */
+ public static final String ERROR = "error";
+
+ public static final String UPLOAD = "upload";
+
+ public static final String COMMENT = "comment";
+
+ //INSTEAD OF PROP FILE
+ /**
+ * The default inlining pattern. Currently "*.png"
+ */
+ public final String DEFAULT_INLINEPATTERN = "*.png";
+ /**
+ * This property defines the inline image pattern.
+ */
+ public final String[] PROP_INLINEIMAGEPTRN = {"*.png","*.jpeg","*.gif"};
+
+ /** If true, consider CamelCase hyperlinks as well. */
+ public final String PROP_CAMELCASELINKS = "true";
+
+ /**
+ * If true, all hyperlinks are translated as well, regardless whether they
+ * are surrounded by brackets.
+ */
+ public final String PROP_PLAINURIS = "false";
+
+ /**
+ * If true, all outward links (external links) have a small link image
+ * appended.
+ */
+ public final String PROP_USEOUTLINKIMAGE = "true";
+
+ /**
+ * If set to "true", allows using raw HTML within Wiki text. Be warned, this
+ * is a VERY dangerous option to set - never turn this on in a publicly
+ * allowable Wiki, unless you are absolutely certain of what you're doing.
+ */
+ public static final String PROP_ALLOWHTML = "false";
+
+
+}
+
Added: trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiSyntax.txt
===================================================================
--- trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiSyntax.txt 2005-08-11 03:49:18 UTC (rev 816)
+++ trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiSyntax.txt 2005-08-11 04:43:24 UTC (rev 817)
@@ -0,0 +1,29 @@
+/*
+ * Created on Aug 9, 2005
+ *
+ *
+ */
+
+/**
+ * @author rali
+ *
+ * Defines basic Wiki syntax,
+ * later to be changed into dtd ?
+ *
+ */
+package org.jboss.wiki.plugins;
+
+public class WikiSyntax {
+ public static final String newLine = "\\";
+ public static final String bold = "__";
+ public static final String italic = "''";
+ public static final String horizontalRuler = "---";
+ public static final String preformatted_begin = "{{{";
+ public static final String preformatted_end = "}}}";
+ public static final String teletype_begin = "{{";
+ public static final String teletype_end = "}}";
+ public static final String table = "|";
+
+public static void hasle()
+{}
+}
Added: trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiTranslator.txt
===================================================================
--- trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiTranslator.txt 2005-08-11 03:49:18 UTC (rev 816)
+++ trunk/forge/portal-extensions/forge-wiki/src/java/org/jboss/wiki/plugins/WikiTranslator.txt 2005-08-11 04:43:24 UTC (rev 817)
@@ -0,0 +1,1522 @@
+package org.jboss.wiki.plugins;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.log4j.Category;
+import org.apache.oro.text.*;
+import org.apache.oro.text.regex.*;
+
+public class WikiTranslator extends Reader {
+ public static final int READ = 0;
+
+ public static final int EDIT = 1;
+
+ private static final int EMPTY = 2; // Empty message
+
+ private static final int LOCAL = 3;
+
+ private static final int LOCALREF = 4;
+
+ private static final int IMAGE = 5;
+
+ private static final int EXTERNAL = 6;
+
+ private static final int INTERWIKI = 7;
+
+ private static final int IMAGELINK = 8;
+
+ private static final int IMAGEWIKILINK = 9;
+
+ public static final int ATTACHMENT = 10;
+
+ private static final int ATTACHMENTIMAGE = 11;
+
+ private boolean m_isbold = false;
+
+ private boolean m_isitalic = false;
+
+ private boolean m_isTypedText = false;
+
+ private boolean m_istable = false;
+
+ private boolean m_isPre = false;
+
+ private boolean m_isdefinition = false;
+
+ private int m_listlevel = 0;
+
+ private int m_numlistlevel = 0;
+
+ private String encoding = "UTF-8";
+
+ /** Keeps image regexp Patterns */
+ private ArrayList m_inlineImagePatterns;
+
+ private PatternMatcher m_inlineMatcher = new Perl5Matcher();
+
+ private static final int PUSHBACK_BUFFER_SIZE = 8;
+
+ private PushbackReader m_in;
+
+ private StringReader m_data = new StringReader("");
+ private String m_closeTag = null;
+
+ private static Category log = Category.getInstance(WikiTranslator.class);
+
+ //FIXME this is temporary implementation of keeping track of wiki pages
+ private static ArrayList existingPages;
+
+ //all prefixes and parts that form the links to attachments, images, wiki
+ // pages
+ // should be set through forge-common? or in a similar way at least
+
+ private final String BASE_URL = "http://forge.sicore.org:8080/portal/index.html?ctrl:id=window.default.WikiPortletWindow";
+
+ private final String VIEW_PATH = "";
+
+ private final String EDIT_PATH = "";
+
+ private final String ATTACH_PATH = "";
+
+ private final String IMAGE_PATH = null;//not used
+
+ //wiki properties variables, currently not used
+
+ /** If true, then considers CamelCase links as well. */
+ private boolean m_camelCaseLinks = false;
+
+ /** If true, consider URIs that have no brackets as well. */
+ // FIXME: Currently reserved, but not used.
+ private boolean m_plainUris = false;
+
+ /** If true, all outward links use a small link image. */
+ private boolean m_useOutlinkImage = true;
+
+ /** If true, allows raw HTML. */
+ private boolean m_allowHTML = false;
+
+ /**
+ * These characters constitute word separators when trying to find CamelCase
+ * links.
+ */
+ private static final String WORD_SEPARATORS = ",.|:;+=&";
+
+ private PatternMatcher m_matcher = new Perl5Matcher();
+
+ private PatternCompiler m_compiler = new Perl5Compiler();
+
+
+ private Pattern m_camelCasePtrn;
+
+ //contructor
+ public WikiTranslator(Reader in, WikiContext context) {
+ PatternCompiler compiler = new GlobCompiler();
+ ArrayList compiledpatterns = new ArrayList();
+
+ m_in = new PushbackReader(new BufferedReader(in), PUSHBACK_BUFFER_SIZE);
+
+ Collection ptrns = getImagePatterns(context);
+ ptrns.add(context.DEFAULT_INLINEPATTERN);
+
+ //
+ // Make them into Regexp Patterns. Unknown patterns
+ // are ignored.
+ //
+ for (Iterator i = ptrns.iterator(); i.hasNext();) {
+ try {
+ compiledpatterns.add(compiler.compile((String) i.next()));
+ } catch (MalformedPatternException e) {
+ log.error("Malformed pattern in properties: ", e);
+ }
+ }
+
+ m_inlineImagePatterns = compiledpatterns;
+
+ try {
+ m_camelCasePtrn = m_compiler.
+ compile("^([[:^alnum:]]*|\\~)([[:upper:]]+[[:lower:]]+[[:upper:]]+[[:alnum:]]*)[[:^alnum:]]*$");
+ } catch (MalformedPatternException e) {
+ log.fatal("Internal error: Someone put in a faulty pattern.", e);
+ //throw new InternalWikiException("Faulty camelcasepattern in
+ // TranslatorReader");
+ }
+
+ //
+ // Set the properties.
+ //
+ // Properties props = m_engine.getWikiProperties();
+
+ }
+
+ /**
+ * @param context
+ * @return
+ */
+ private Collection getImagePatterns(WikiContext context) {
+ ArrayList patterns = new ArrayList();
+ for (int i = 0; i < context.PROP_INLINEIMAGEPTRN.length; i++) {
+ patterns.add(context.PROP_INLINEIMAGEPTRN[i]);
+ }
+
+ return patterns;
+ }
+
+ /**
+ * Figures out if a link is an off-site link. This recognizes the most
+ * common protocols by checking how it starts.
+ */
+ private boolean isExternalLink(String link) {
+ return link.startsWith("http:") || link.startsWith("ftp:")
+ || link.startsWith("https:") || link.startsWith("mailto:")
+ || link.startsWith("news:") || link.startsWith("file:");
+ }
+
+ /**
+ * Matches the given link to the list of image name patterns to determine
+ * whether it should be treated as an inline image or not.
+ */
+ private boolean isImageLink(String link) {
+ for (Iterator i = m_inlineImagePatterns.iterator(); i.hasNext();) {
+ if (m_inlineMatcher.matches(link, (Pattern) i.next()))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks for the existence of a traditional style CamelCase link.
+ * <P>
+ * We separate all white-space -separated words, and feed it to this routine
+ * to find if there are any possible camelcase links. For example, if "word"
+ * is "__HyperLink__" we return "HyperLink".
+ *
+ * @param word
+ * A phrase to search in.
+ * @return The match within the phrase. Returns null, if no CamelCase
+ * hyperlink exists within this phrase.
+ */
+ private String checkForCamelCaseLink(String word) {
+ PatternMatcherInput input;
+
+ input = new PatternMatcherInput(word);
+
+ if (m_matcher.contains(input, m_camelCasePtrn)) {
+ MatchResult res = m_matcher.getMatch();
+
+ int start = res.beginOffset(2);
+ int end = res.endOffset(2);
+
+ String link = res.group(2);
+ String matchedLink;
+
+ if (res.group(1) != null) {
+ if (res.group(1).equals("~") || res.group(1).indexOf('[') != -1) {
+ // Delete the (~) from beginning.
+ // We'll make '~' the generic kill-processing-character from
+ // now on.
+ return null;
+ }
+ }
+
+ return link;
+ } // if match
+
+ return null;
+ }
+
+ /**
+ * When given a link to a WikiName, we just return a proper HTML link for
+ * it. The local link mutator chain is also called.
+ */
+ private String makeCamelCaseLink(String wikiname) {
+ String matchedLink;
+ String link;
+
+ if ((matchedLink = linkExists(wikiname)) != null) {
+ link = makeLink(READ, matchedLink, wikiname);
+ } else {
+ link = makeLink(EDIT, wikiname, wikiname);
+ }
+
+ return link;
+ }
+
+ /**
+ * returns the name of the page if it exists, null otherwise
+ *
+ * @param wikiname
+ * @return
+ */
+ private String linkExists(String wikiname) {
+ int occurence = existingPages.indexOf(wikiname);
+ if (occurence != -1) {
+ return (String) existingPages.get(occurence);
+ } else
+ //doesnt exist, needs to be created somewhere*
+ return null;
+ }
+
+ /**
+ * Write a HTMLized link depending on its type. The link mutator chain is
+ * processed.
+ *
+ * @param type
+ * Type of the link.
+ * @param link
+ * The actual link.
+ * @param text
+ * The user-visible text for the link.
+ */
+ public String makeLink(int type, String link, String text) {
+ String result;
+
+ if (text == null)
+ text = link;
+
+ // Make sure we make a link name that can be accepted
+ // as a valid URL.
+
+ String encodedlink = encodeName(link);
+
+ if (encodedlink.length() == 0) {
+ type = EMPTY;
+ }
+
+ // text = callMutatorChain( m_linkMutators, text );
+
+ switch (type) {
+ case READ:
+ result = "<A CLASS=\"wikipage\" HREF=\"" + getViewURL(link) + "\">"
+ + text + "</A>";//just display a wiki page
+ break;
+
+ case EDIT:
+ result = "<U>" + text + "</U><A HREF=\"" + getEditURL(link)
+ + "\">?</A>";
+ break;
+
+ case EMPTY:
+ result = "<U>" + text + "</U>";
+ break;
+
+ //
+ // These two are for local references - footnotes and
+ // references to footnotes.
+ // We embed the page name (or whatever WikiContext gives us)
+ // to make sure the links are unique across Wiki.
+ //
+ case LOCALREF:
+ result = "<A CLASS=\"footnoteref\" HREF=\"#ref-" +
+ //m_context.getPage().getName()+"-"+
+ link + "\">[" + text + "]</A>";
+ break;
+ //FIXME change link
+ case LOCAL:
+ result = "<A CLASS=\"footnote\" NAME=\"ref-" +
+ //m_context.getPage().getName()+
+ "-" + link.substring(1) + "\">[" + text + "]</A>";
+ break;
+
+ //
+ // With the image, external and interwiki types we need to
+ // make sure nobody can put in Javascript or something else
+ // annoying into the links themselves. We do this by preventing
+ // a haxor from stopping the link name short with quotes in
+ // fillBuffer().
+ //
+ case IMAGE:
+ result = "<IMG CLASS=\"inline\" SRC=\"" + link + "\" ALT=\"" + text
+ + "\" />";
+ break;
+
+ case IMAGELINK:
+ result = "<A HREF=\"" + text + "\"><IMG CLASS=\"inline\" SRC=\""
+ + link + "\" /></A>";
+ break;
+
+ case IMAGEWIKILINK:
+ String pagelink = getViewURL(text);
+ result = "<A CLASS=\"wikipage\" HREF=\"" + pagelink
+ + "\"><IMG CLASS=\"inline\" SRC=\"" + link + "\" ALT=\""
+ + text + "\" /></A>";
+ break;
+
+ case EXTERNAL:
+ result = "<A CLASS=\"external\" HREF=\"" + link + "\">" + text
+ + "</A>";
+ break;
+
+ case INTERWIKI:
+ result = "<A CLASS=\"interwiki\" HREF=\"" + link + "\">" + text
+ + "</A>";
+ break;
+
+ case ATTACHMENT:
+ String attlink = getAttachmentURL(link);
+ result = "<a class=\"attachment\" href=\""
+ + attlink
+ + "\">"
+ + text
+ + "</a>"
+ + "<a href=\""
+ + getBaseURL()
+ + "PageInfo.jsp?page="
+ + encodedlink
+ + "\"><img src=\"images/attachment_small.png\" border=\"0\" /></a>";
+ break;
+
+ default:
+ result = "";
+ break;
+ }
+
+ return result;
+ }
+
+ /**
+ * @param link
+ * @return
+ */
+ private String encodeName(String link) {
+ String newlink;
+
+ try {
+ if (encoding != null)
+ return java.net.URLEncoder.encode(link, encoding);
+ }
+
+ catch (UnsupportedEncodingException e) {
+ log.fatal("Internal error: Wrong encoding.", e);
+
+ }
+ return null;
+
+ }
+
+ /**
+ * @return
+ */
+ private String getBaseURL() {
+
+ return BASE_URL;
+ }
+
+ /**
+ * @param link
+ * @return
+ */
+ private String getAttachmentURL(String link) {
+
+ return BASE_URL + ATTACH_PATH + encodeName(link);
+ }
+
+ /**
+ * @param link
+ * @return
+ */
+ private String getEditURL(String link) {
+
+ return BASE_URL + EDIT_PATH + encodeName(link);
+ }
+
+ /**
+ * @param link
+ * @return
+ */
+ private String getViewURL(String link) {
+
+ return BASE_URL + VIEW_PATH + encodeName(link);
+ }
+
+ /**
+ * Image links are handled differently: 1. If the text is a WikiName of an
+ * existing page, it gets linked. 2. If the text is an external link, then
+ * it is inlined. 3. Otherwise it becomes an ALT text.
+ *
+ * @param reallink
+ * The link to the image.
+ * @param link
+ * Link text portion, may be a link to somewhere else.
+ * @param hasLinkText
+ * If true, then the defined link had a link text available. This
+ * means that the link text may be a link to a wiki page, or an
+ * external resource.
+ */
+
+ private String handleImageLink(String reallink, String link,
+ boolean hasLinkText) {
+ String possiblePage = cleanLink(link);
+ String matchedLink;
+ String res = "";
+
+ if (isExternalLink(link) && hasLinkText) {
+ res = makeLink(IMAGELINK, reallink, link);
+ } else if ((matchedLink = linkExists(possiblePage)) != null
+ && hasLinkText) {
+
+ res = makeLink(IMAGEWIKILINK, reallink, link);
+ } else {
+ res = makeLink(IMAGE, reallink, link);
+ }
+
+ return res;
+ }
+
+ /**
+ * Cleans a Wiki name.
+ * <P>[ This is a link ] -> ThisIsALink
+ *
+ * @param link
+ * Link to be cleared. Null is safe, and causes this to return
+ * null.
+ * @return A cleaned link.
+ *
+ * @since 2.0
+ */
+ public static String cleanLink(String link) {
+ StringBuffer clean = new StringBuffer();
+
+ if (link == null)
+ return null;
+
+ //
+ // Compress away all whitespace and capitalize
+ // all words in between.
+ //
+
+ StringTokenizer st = new StringTokenizer(link, " -");
+
+ while (st.hasMoreTokens()) {
+ StringBuffer component = new StringBuffer(st.nextToken());
+
+ component.setCharAt(0, Character.toUpperCase(component.charAt(0)));
+
+ //
+ // We must do this, because otherwise compiling on JDK 1.4 causes
+ // a downwards incompatibility to JDK 1.3.
+ //
+ clean.append(component.toString());
+ }
+
+ //
+ // Remove non-alphanumeric characters that should not
+ // be put inside WikiNames. Note that all valid
+ // Unicode letters are considered okay for WikiNames.
+ // It is the problem of the WikiPageProvider to take
+ // care of actually storing that information.
+ //
+
+ for (int i = 0; i < clean.length(); i++) {
+ if (!(Character.isLetterOrDigit(clean.charAt(i))
+ || clean.charAt(i) == '_' || clean.charAt(i) == '.')) {
+ clean.deleteCharAt(i);
+ --i; // We just shortened this buffer.
+ }
+ }
+
+ return clean.toString();
+ }
+
+ /**
+ * If outlink images are turned on, returns a link to the outward linking
+ * image.
+ */
+ private final String outlinkImage() {
+ if (m_useOutlinkImage) {
+ return "<img class=\"outlink\" src=\"" + getBaseURL()
+ + "images/out.png\" alt=\"\" />";
+ }
+
+ return "";
+ }
+
+ private int nextToken() throws IOException {
+ return m_in.read();
+ }
+ private void pushBack(int c) throws IOException {
+ if (c != -1) {
+ m_in.unread(c);
+ }
+ }
+ private String handleBackslash()
+ throws IOException
+{
+ int ch = nextToken();
+
+ if( ch == '\\' )
+ {
+ int ch2 = nextToken();
+
+ if( ch2 == '\\' )
+ {
+ return "<BR clear=\"all\" />";
+ }
+
+ pushBack( ch2 );
+
+ return "<BR />";
+ }
+
+ pushBack( ch );
+
+ return "\\";
+}
+
+ private String handleUnderscore()
+ throws IOException
+ {
+ int ch = nextToken();
+ String res = "_";
+
+ if( ch == '_' )
+ {
+ res = m_isbold ? "</B>" : "<B>";
+ m_isbold = !m_isbold;
+ }
+ else
+ {
+ pushBack( ch );
+ }
+
+ return res;
+ }
+
+ /**
+ * For example: italics.
+ */
+ private String handleApostrophe()
+ throws IOException
+ {
+ int ch = nextToken();
+ String res = "'";
+
+ if( ch == '\'' )
+ {
+ res = m_isitalic ? "</I>" : "<I>";
+ m_isitalic = !m_isitalic;
+ }
+ else
+ {
+ m_in.unread( ch );
+ }
+
+ return res;
+ }
+
+ private String handleOpenbrace()
+ throws IOException
+ {
+ int ch = nextToken();
+ String res = "{";
+
+ if( ch == '{' )
+ {
+ int ch2 = nextToken();
+
+ if( ch2 == '{' )
+ {
+ res = "<PRE>";
+ m_isPre = true;
+ }
+ else
+ {
+ pushBack( ch2 );
+
+ res = "<TT>";
+ m_isTypedText = true;
+ }
+ }
+ else
+ {
+ pushBack( ch );
+ }
+
+ return res;
+ }
+
+ /**
+ * Handles both }} and }}}
+ */
+ private String handleClosebrace()
+ throws IOException
+ {
+ String res = "}";
+
+ int ch2 = nextToken();
+
+ if( ch2 == '}' )
+ {
+ int ch3 = nextToken();
+
+ if( ch3 == '}' )
+ {
+ if( m_isPre )
+ {
+ m_isPre = false;
+ res = "</PRE>";
+ }
+ else
+ {
+ res = "}}}";
+ }
+ }
+ else
+ {
+ pushBack( ch3 );
+
+ if( !m_isPre )
+ {
+ res = "</TT>";
+ m_isTypedText = false;
+ }
+ else
+ {
+ pushBack( ch2 );
+ }
+ }
+ }
+ else
+ {
+ pushBack( ch2 );
+ }
+
+ return res;
+ }
+
+ private String handleDash()
+ throws IOException
+ {
+ int ch = nextToken();
+
+ if( ch == '-' )
+ {
+ int ch2 = nextToken();
+
+ if( ch2 == '-' )
+ {
+ int ch3 = nextToken();
+
+ if( ch3 == '-' )
+ {
+ // Empty away all the rest of the dashes.
+ // Do not forget to return the first non-match back.
+ while( (ch = nextToken()) == '-' );
+
+ pushBack(ch);
+ return "<HR />";
+ }
+
+ pushBack( ch3 );
+ }
+ pushBack( ch2 );
+ }
+
+ pushBack( ch );
+
+ return "-";
+ }
+
+ private String handleHeading()
+ throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+
+ int ch = nextToken();
+
+ if( ch == '!' )
+ {
+ int ch2 = nextToken();
+
+ if( ch2 == '!' )
+ {
+ buf.append("<H2>");
+ m_closeTag = "</H2>";
+ }
+ else
+ {
+ buf.append( "<H3>" );
+ m_closeTag = "</H3>";
+ pushBack( ch2 );
+ }
+ }
+ else
+ {
+ buf.append( "<H4>" );
+ m_closeTag = "</H4>";
+ pushBack( ch );
+ }
+
+ return buf.toString();
+ }
+ private String handleUnorderedList()
+ throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+
+ if( m_listlevel > 0 )
+ {
+ buf.append("</LI>\n");
+ }
+
+ int numBullets = countChars( m_in, '*' ) + 1;
+
+ if( numBullets > m_listlevel )
+ {
+ for( ; m_listlevel < numBullets; m_listlevel++ )
+ buf.append("<UL>\n");
+ }
+ else if( numBullets < m_listlevel )
+ {
+ for( ; m_listlevel > numBullets; m_listlevel-- )
+ buf.append("</UL>\n");
+ }
+
+ buf.append("<LI>");
+
+ return buf.toString();
+ }
+
+ private String handleOrderedList()
+ throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+
+ if( m_numlistlevel > 0 )
+ {
+ buf.append("</LI>\n");
+ }
+
+ int numBullets = countChars( m_in, '#' ) + 1;
+
+ if( numBullets > m_numlistlevel )
+ {
+ for( ; m_numlistlevel < numBullets; m_numlistlevel++ )
+ buf.append("<OL>\n");
+ }
+ else if( numBullets < m_numlistlevel )
+ {
+ for( ; m_numlistlevel > numBullets; m_numlistlevel-- )
+ buf.append("</OL>\n");
+ }
+
+ buf.append("<LI>");
+
+ return buf.toString();
+
+ }
+ private int countChars( PushbackReader in, char c )
+ throws IOException
+ {
+ int count = 0;
+ int ch;
+
+ while( (ch = in.read()) != -1 )
+ {
+ if( (char)ch == c )
+ {
+ count++;
+ }
+ else
+ {
+ in.unread( ch );
+ break;
+ }
+ }
+
+ return count;
+ }
+
+ private String handleDefinitionList()
+ throws IOException
+ {
+ if( !m_isdefinition )
+ {
+ m_isdefinition = true;
+
+ m_closeTag = "</DD>\n</DL>";
+
+ return "<DL>\n<DT>";
+ }
+
+ return ";";
+ }
+
+ private String handleOpenbracket()
+ throws IOException
+ {
+ StringBuffer sb = new StringBuffer();
+ int ch;
+ boolean isPlugin = false;
+
+ while( (ch = nextToken()) == '[' )
+ {
+ sb.append( (char)ch );
+ }
+
+ if( ch == '{' )
+ {
+ isPlugin = true;
+ }
+
+ pushBack( ch );
+
+ if( sb.length() > 0 )
+ {
+ return sb.toString();
+ }
+
+ //
+ // Find end of hyperlink
+ //
+
+ ch = nextToken();
+
+ while( ch != -1 )
+ {
+ if( ch == ']' && (!isPlugin || sb.charAt( sb.length()-1 ) == '}' ) )
+ {
+ break;
+ }
+
+ sb.append( (char) ch );
+
+ ch = nextToken();
+ }
+
+ if( ch == -1 )
+ {
+ log.info("Warning: unterminated link detected!");
+ return sb.toString();
+ }
+
+ return handleHyperlinks( sb.toString() );
+ }
+
+ private String handleBar( boolean newLine )
+ throws IOException
+ {
+ StringBuffer sb = new StringBuffer();
+
+ if( !m_istable && !newLine )
+ {
+ return "|";
+ }
+
+ if( newLine )
+ {
+ if( !m_istable )
+ {
+ sb.append("<TABLE CLASS=\"wikitable\" BORDER=\"1\">\n");
+ m_istable = true;
+ }
+
+ sb.append("<TR>");
+ m_closeTag = "</TD></TR>";
+ }
+
+ int ch = nextToken();
+
+ if( ch == '|' )
+ {
+ if( !newLine )
+ {
+ sb.append("</TH>");
+ }
+ sb.append("<TH>");
+ m_closeTag = "</TH></TR>";
+ }
+ else
+ {
+ if( !newLine )
+ {
+ sb.append("</TD>");
+ }
+ sb.append("<TD>");
+ pushBack( ch );
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Generic escape of next character or entity.
+ */
+ private String handleTilde()
+ throws IOException
+ {
+ int ch = nextToken();
+
+ if( ch == '|' )
+ return "|";
+
+ if( Character.isUpperCase( (char) ch ) )
+ {
+ return String.valueOf( (char)ch );
+ }
+
+ // No escape.
+ pushBack( ch );
+
+ return "~";
+ }
+
+ /**
+ * Gobbles up all hyperlinks that are encased in square brackets.
+ * [{$varname}] not supported, as well as plugin links, and attachments as
+ * well
+ */
+ private String handleHyperlinks(String link) {
+ StringBuffer sb = new StringBuffer();
+ String reallink;
+ int cutpoint;
+ link = replaceEntities(link);
+
+ if ((cutpoint = link.indexOf('|')) != -1) {
+ reallink = link.substring(cutpoint + 1).trim();
+ link = link.substring(0, cutpoint);
+ } else {
+ reallink = link.trim();//get rid of white spaces
+ }
+
+ int interwikipoint = -1;//not quite supported either
+ if (isExternalLink(reallink)) {
+ // It's an external link, out of this Wiki
+
+ //callMutatorChain( m_externalLinkMutatorChain, reallink );
+
+ if (isImageLink(reallink)) {
+ sb.append(handleImageLink(reallink, link, (cutpoint != -1)));
+ } else {
+ sb.append(makeLink(EXTERNAL, reallink, link));
+ sb.append(outlinkImage());
+ }
+ } else if ((interwikipoint = reallink.indexOf(":")) != -1) {
+ // It's an interwiki link
+ // InterWiki links also get added to external link chain
+ // after the links have been resolved.
+
+ // FIXME: There is an interesting issue here: We probably should
+ // URLEncode the wikiPage, but we can't since some of the
+ // Wikis use slashes (/), which won't survive URLEncoding.
+ // Besides, we don't know which character set the other Wiki
+ // is using, so you'll have to write the entire name as it appears
+ // in the URL. Bugger.
+
+ String extWiki = reallink.substring(0, interwikipoint);
+ String wikiPage = reallink.substring(interwikipoint + 1);
+
+ String urlReference = getInterWikiURL(extWiki);
+
+ if (urlReference != null) {
+ urlReference = replaceString(urlReference, "%s", wikiPage);
+ //callMutatorChain( m_externalLinkMutatorChain, urlReference );
+
+ sb.append(makeLink(INTERWIKI, urlReference, link));
+
+ if (isExternalLink(urlReference)) {
+ sb.append(outlinkImage());
+ }
+ } else {
+ sb
+ .append(link
+ + " <FONT COLOR=\"#FF0000\">(No InterWiki reference defined in properties for Wiki called '"
+ + extWiki + "'!)</FONT>");
+ }
+ } else if (reallink.startsWith("#")) {
+ // It defines a local footnote
+ sb.append(makeLink(LOCAL, reallink, link));
+ } else if (isNumber(reallink)) {
+ // It defines a reference to a local footnote
+ sb.append(makeLink(LOCALREF, reallink, link));
+ } else {
+ // It's an internal Wiki link
+ reallink = cleanLink(reallink);
+
+ // callMutatorChain( m_localLinkMutatorChain, reallink );
+
+ String matchedLink;
+ if ((matchedLink = linkExists(reallink)) != null) {
+ sb.append(makeLink(READ, matchedLink, link));
+ } else {
+ sb.append(makeLink(EDIT, reallink, link));
+ }
+ }
+ // }
+
+ return sb.toString();
+ }
+
+ /**
+ * @param extWiki
+ * @return
+ */
+ private String getInterWikiURL(String extWiki) {
+ // TODO Auto-generated method stub
+ return "inter";
+ }
+
+ /**
+ * Returns true, if the argument contains a number, otherwise false. In a
+ * quick test this is roughly the same speed as Integer.parseInt() if the
+ * argument is a number, and roughly ten times the speed, if the argument is
+ * NOT a number.
+ */
+
+ private boolean isNumber(String s) {
+ if (s == null)
+ return false;
+
+ if (s.length() > 1 && s.charAt(0) == '-')
+ s = s.substring(1);
+
+ for (int i = 0; i < s.length(); i++) {
+ if (!Character.isDigit(s.charAt(i)))
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Replaces the relevant entities inside the String. All >, < and
+ * " are replaced by their respective names.
+ *
+ * @since 1.6.1
+ */
+ public static String replaceEntities(String src) {
+ src = replaceString(src, "<", "<");
+ src = replaceString(src, ">", ">");
+ src = replaceString(src, "\"", """);
+
+ return src;
+ }
+
+ /**
+ * Replaces a string with an other string.
+ *
+ * @param orig
+ * Original string. Null is safe.
+ * @param src
+ * The string to find.
+ * @param dest
+ * The string to replace <I>src </I> with.
+ */
+
+ public static String replaceString(String orig, String src, String dest) {
+ if (orig == null)
+ return null;
+
+ StringBuffer res = new StringBuffer();
+ int start, end = 0, last = 0;
+
+ while ((start = orig.indexOf(src, end)) != -1) {
+ res.append(orig.substring(last, start));
+ res.append(dest);
+ end = start + src.length();
+ last = start + src.length();
+ }
+
+ res.append(orig.substring(end));
+
+ return res.toString();
+ }
+
+ /**
+ * Replaces a part of a string with a new String.
+ *
+ * @param start
+ * Where in the original string the replacing should start.
+ * @param end
+ * Where the replacing should end.
+ * @param orig
+ * Original string. Null is safe.
+ * @param text
+ * The new text to insert into the string.
+ */
+ public static String replaceString(String orig, int start, int end,
+ String text) {
+ if (orig == null)
+ return null;
+
+ StringBuffer buf = new StringBuffer(orig);
+
+ buf.replace(start, end, text);
+
+ return buf.toString();
+ }
+
+ public void close() {
+ }
+
+ public int read(char[] buf, int off, int len) throws IOException {
+ return m_data.read(buf, off, len);
+ }
+
+ public int read()
+ throws IOException
+{
+ int val = m_data.read();
+
+ if( val == -1 )
+ {
+ transform();
+ val = m_data.read();
+
+ if( val == -1 )
+ {
+ m_data = new StringReader( closeAll() );
+
+ val = m_data.read();
+ }
+ }
+
+ return val;
+}
+ /**
+ * Closes all annoying lists and things that the user might've
+ * left open.
+ */
+ private String closeAll()
+ {
+ StringBuffer buf = new StringBuffer();
+
+ if( m_isbold )
+ {
+ buf.append("</B>");
+ m_isbold = false;
+ }
+
+ if( m_isitalic )
+ {
+ buf.append("</I>");
+ m_isitalic = false;
+ }
+
+ if( m_isTypedText )
+ {
+ buf.append("</TT>");
+ m_isTypedText = false;
+ }
+
+ for( ; m_listlevel > 0; m_listlevel-- )
+ {
+ buf.append( "</UL>\n" );
+ }
+
+ for( ; m_numlistlevel > 0; m_numlistlevel-- )
+ {
+ buf.append( "</OL>\n" );
+ }
+
+ if( m_isPre )
+ {
+ buf.append("</PRE>\n");
+ m_isPre = false;
+ }
+
+ if( m_istable )
+ {
+ buf.append( "</TABLE>" );
+ m_istable = false;
+ }
+
+ return buf.toString();
+ }
+
+ private void transform() throws IOException {
+ StringBuffer buf = new StringBuffer();
+ StringBuffer word = null;
+ int previousCh = -2;
+ int start = 0;
+
+ boolean quitReading = false;
+ boolean newLine = true; // FIXME: not true if reading starts in middle
+ // of buffer
+
+ while (!quitReading) {
+ int ch = nextToken();
+ String s = null;
+
+ //
+ // Check if we're actually ending the preformatted mode.
+ // We still must do an entity transformation here.
+ //
+ if (m_isPre) {
+ if (ch == '}') {
+ buf.append(handleClosebrace());
+ } else if (ch == '<') {
+ buf.append("<");
+ } else if (ch == '>') {
+ buf.append(">");
+ } else if (ch == -1) {
+ quitReading = true;
+ } else {
+ buf.append((char) ch);
+ }
+
+ continue;
+ }
+ //
+ // CamelCase detection, a non-trivial endeavour.
+ // We keep track of all white-space separated entities, which we
+ // hereby refer to as "words". We then check for an existence
+ // of a CamelCase format text string inside the "word", and
+ // if one exists, we replace it with a proper link.
+ //
+
+ if( m_camelCaseLinks )
+ {
+ // Quick parse of start of a word boundary.
+
+ if( word == null &&
+ (Character.isWhitespace( (char)previousCh ) ||
+ WORD_SEPARATORS.indexOf( (char)previousCh ) != -1 ||
+ newLine ) &&
+ !Character.isWhitespace( (char) ch ) )
+ {
+ word = new StringBuffer();
+ }
+
+ // Are we currently tracking a word?
+ if( word != null )
+ {
+ //
+ // Check for the end of the word.
+ //
+
+ if( Character.isWhitespace( (char)ch ) ||
+ ch == -1 ||
+ WORD_SEPARATORS.indexOf( (char) ch ) != -1 )
+ {
+ String potentialLink = word.toString();
+
+ String camelCase = checkForCamelCaseLink(potentialLink);
+
+ if( camelCase != null )
+ {
+ // System.out.println("Buffer is "+buf);
+
+ // System.out.println(" Replacing "+camelCase+" with
+ // proper link.");
+ start = buf.toString().lastIndexOf( camelCase );
+ buf.replace(start,
+ start+camelCase.length(),
+ makeCamelCaseLink(camelCase) );
+
+ // System.out.println(" Resulting with "+buf);
+ }
+
+ // We've ended a word boundary, so time to reset.
+ word = null;
+ }
+ else
+ {
+ // This should only be appending letters and digits.
+ word.append( (char)ch );
+ } // if end of word
+ } // if word's not null
+
+ // Always set the previous character to test for word starts.
+ previousCh = ch;
+
+ } // if m_camelCaseLinks
+
+ //
+ // Check if any lists need closing down.
+ //
+
+ if( newLine && ch != '*' && ch != ' ' && m_listlevel > 0 )
+ {
+ buf.append("</LI>\n");
+ for( ; m_listlevel > 0; m_listlevel-- )
+ {
+ buf.append("</UL>\n");
+ }
+ }
+
+ if( newLine && ch != '#' && ch != ' ' && m_numlistlevel > 0 )
+ {
+ buf.append("</LI>\n");
+ for( ; m_numlistlevel > 0; m_numlistlevel-- )
+ {
+ buf.append("</OL>\n");
+ }
+ }
+
+ if( newLine && ch != '|' && m_istable )
+ {
+ buf.append("</TABLE>\n");
+ m_istable = false;
+ m_closeTag = null;
+ }
+
+ //
+ // Now, check the incoming token.
+ //
+ switch( ch )
+ {
+ case '\r':
+ // DOS linefeeds we forget
+ s = null;
+ break;
+
+ case '\n':
+ //
+ // Close things like headings, etc.
+ //
+ if( m_closeTag != null )
+ {
+ buf.append( m_closeTag );
+ m_closeTag = null;
+ }
+
+ m_isdefinition = false;
+
+ if( newLine )
+ {
+ // Paragraph change.
+
+ buf.append("<P>\n");
+ }
+ else
+ {
+ buf.append("\n");
+ newLine = true;
+ }
+
+ break;
+
+ case '\\':
+ s = handleBackslash();
+ break;
+
+ case '_':
+ s = handleUnderscore();
+ break;
+
+ case '\'':
+ s = handleApostrophe();
+ break;
+
+ case '{':
+ s = handleOpenbrace();
+ break;
+
+ case '}':
+ s = handleClosebrace();
+ break;
+
+ case '-':
+ s = handleDash();
+ break;
+
+ case '!':
+ if( newLine )
+ {
+ s = handleHeading();
+ }
+ else
+ {
+ s = "!";
+ }
+ break;
+
+ case ';':
+ if( newLine )
+ {
+ s = handleDefinitionList();
+ }
+ else
+ {
+ s = ";";
+ }
+ break;
+
+ case ':':
+ if( m_isdefinition )
+ {
+ s = "</DT><DD>";
+ m_isdefinition = false;
+ }
+ else
+ {
+ s = ":";
+ }
+ break;
+
+ case '[':
+ s = handleOpenbracket();
+ break;
+
+ case '*':
+ if( newLine )
+ {
+ s = handleUnorderedList();
+ }
+ else
+ {
+ s = "*";
+ }
+ break;
+
+ case '#':
+ if( newLine )
+ {
+ s = handleOrderedList();
+ }
+ else
+ {
+ s = "#";
+ }
+ break;
+
+ case '|':
+ s = handleBar( newLine );
+ break;
+
+ case '<':
+ s = m_allowHTML ? "<" : "<";
+ break;
+
+ case '>':
+ s = m_allowHTML ? ">" : ">";
+ break;
+
+ case '\"':
+ s = m_allowHTML ? "\"" : """;
+ break;
+
+ /*
+ * case '&': s = "&"; break;
+ */
+ case '~':
+ s = handleTilde();
+ break;
+
+ case -1:
+ if( m_closeTag != null )
+ {
+ buf.append( m_closeTag );
+ m_closeTag = null;
+ }
+ quitReading = true;
+ break;
+
+ default:
+ buf.append( (char)ch );
+ newLine = false;
+ break;
+ }
+
+ if( s != null )
+ {
+ buf.append( s );
+ newLine = false;
+ }
+
+ }
+ m_data = new StringReader( buf.toString() );
+ }
+}
\ No newline at end of file
|