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 |