0001: /*
0002: JSPWiki - a JSP-based WikiWiki clone.
0003:
0004: Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
0005:
0006: This program is free software; you can redistribute it and/or modify
0007: it under the terms of the GNU Lesser General Public License as published by
0008: the Free Software Foundation; either version 2.1 of the License, or
0009: (at your option) any later version.
0010:
0011: This program is distributed in the hope that it will be useful,
0012: but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0014: GNU Lesser General Public License for more details.
0015:
0016: You should have received a copy of the GNU Lesser General Public License
0017: along with this program; if not, write to the Free Software
0018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0019: */
0020: package com.ecyrd.jspwiki;
0021:
0022: import java.io.*;
0023: import java.text.MessageFormat;
0024: import java.util.*;
0025:
0026: import org.apache.log4j.Logger;
0027: import org.apache.oro.text.*;
0028: import org.apache.oro.text.regex.*;
0029:
0030: import com.ecyrd.jspwiki.i18n.InternationalizationManager;
0031: import com.ecyrd.jspwiki.parser.Heading;
0032: import com.ecyrd.jspwiki.parser.HeadingListener;
0033: import com.ecyrd.jspwiki.plugin.PluginManager;
0034: import com.ecyrd.jspwiki.plugin.PluginException;
0035: import com.ecyrd.jspwiki.plugin.WikiPlugin;
0036: import com.ecyrd.jspwiki.attachment.AttachmentManager;
0037: import com.ecyrd.jspwiki.attachment.Attachment;
0038: import com.ecyrd.jspwiki.providers.ProviderException;
0039: import com.ecyrd.jspwiki.auth.acl.Acl;
0040: import com.ecyrd.jspwiki.auth.WikiSecurityException;
0041:
0042: /**
0043: * Handles conversion from Wiki format into fully featured HTML.
0044: * This is where all the magic happens. It is CRITICAL that this
0045: * class is tested, or all Wikis might die horribly.
0046: * <P>
0047: * The output of the HTML has not yet been validated against
0048: * the HTML DTD. However, it is very simple.
0049: * <p>
0050: * This class is officially deprecated in 2.3, and will be replaced
0051: * with a dummy class later on. Please see MarkupParser.
0052: * @author Janne Jalkanen
0053: * @deprecated
0054: */
0055: //FIXME2.6: Make use JSPWikiMarkupParser
0056: //FIXME3.0: Remove
0057: public class TranslatorReader extends Reader {
0058: public static final int READ = 0;
0059: public static final int EDIT = 1;
0060: private static final int EMPTY = 2; // Empty message
0061: private static final int LOCAL = 3;
0062: private static final int LOCALREF = 4;
0063: private static final int IMAGE = 5;
0064: private static final int EXTERNAL = 6;
0065: private static final int INTERWIKI = 7;
0066: private static final int IMAGELINK = 8;
0067: private static final int IMAGEWIKILINK = 9;
0068: public static final int ATTACHMENT = 10;
0069: // private static final int ATTACHMENTIMAGE = 11;
0070:
0071: /** Lists all punctuation characters allowed in WikiMarkup. These
0072: will not be cleaned away. */
0073:
0074: private static final String PUNCTUATION_CHARS_ALLOWED = "._";
0075:
0076: /** Allow this many characters to be pushed back in the stream. In effect,
0077: this limits the size of a single heading line. */
0078: private static final int PUSHBACK_BUFFER_SIZE = 10 * 1024;
0079: private PushbackReader m_in;
0080:
0081: private StringReader m_data = new StringReader("");
0082:
0083: private static Logger log = Logger
0084: .getLogger(TranslatorReader.class);
0085:
0086: //private boolean m_iscode = false;
0087: private boolean m_isbold = false;
0088: private boolean m_isitalic = false;
0089: private boolean m_isTypedText = false;
0090: private boolean m_istable = false;
0091: private boolean m_isPre = false;
0092: private boolean m_isEscaping = false;
0093: private boolean m_isdefinition = false;
0094:
0095: /** Contains style information, in multiple forms. */
0096: private Stack m_styleStack = new Stack();
0097:
0098: // general list handling
0099: private int m_genlistlevel = 0;
0100: private StringBuffer m_genlistBulletBuffer = new StringBuffer(); // stores the # and * pattern
0101: private boolean m_allowPHPWikiStyleLists = true;
0102:
0103: private boolean m_isOpenParagraph = false;
0104:
0105: /** Tag that gets closed at EOL. */
0106: private String m_closeTag = null;
0107:
0108: private WikiEngine m_engine;
0109: private WikiContext m_context;
0110:
0111: /** Optionally stores internal wikilinks */
0112: private ArrayList m_localLinkMutatorChain = new ArrayList();
0113: private ArrayList m_externalLinkMutatorChain = new ArrayList();
0114: private ArrayList m_attachmentLinkMutatorChain = new ArrayList();
0115: private ArrayList m_headingListenerChain = new ArrayList();
0116:
0117: /** Keeps image regexp Patterns */
0118: private ArrayList m_inlineImagePatterns;
0119:
0120: private PatternMatcher m_inlineMatcher = new Perl5Matcher();
0121:
0122: private ArrayList m_linkMutators = new ArrayList();
0123:
0124: /**
0125: * This property defines the inline image pattern. It's current value
0126: * is jspwiki.translatorReader.inlinePattern
0127: */
0128: public static final String PROP_INLINEIMAGEPTRN = "jspwiki.translatorReader.inlinePattern";
0129:
0130: /** If true, consider CamelCase hyperlinks as well. */
0131: public static final String PROP_CAMELCASELINKS = "jspwiki.translatorReader.camelCaseLinks";
0132:
0133: /** If true, all hyperlinks are translated as well, regardless whether they
0134: are surrounded by brackets. */
0135: public static final String PROP_PLAINURIS = "jspwiki.translatorReader.plainUris";
0136:
0137: /** If true, all outward links (external links) have a small link image appended. */
0138: public static final String PROP_USEOUTLINKIMAGE = "jspwiki.translatorReader.useOutlinkImage";
0139:
0140: /** If set to "true", allows using raw HTML within Wiki text. Be warned,
0141: this is a VERY dangerous option to set - never turn this on in a publicly
0142: allowable Wiki, unless you are absolutely certain of what you're doing. */
0143: public static final String PROP_ALLOWHTML = "jspwiki.translatorReader.allowHTML";
0144:
0145: /** If set to "true", all external links are tagged with 'rel="nofollow"' */
0146: public static final String PROP_USERELNOFOLLOW = "jspwiki.translatorReader.useRelNofollow";
0147:
0148: /** If set to "true", enables plugins during parsing */
0149: public static final String PROP_RUNPLUGINS = "jspwiki.translatorReader.runPlugins";
0150:
0151: /** If true, then considers CamelCase links as well. */
0152: private boolean m_camelCaseLinks = false;
0153:
0154: /** If true, consider URIs that have no brackets as well. */
0155: // FIXME: Currently reserved, but not used.
0156: private boolean m_plainUris = false;
0157:
0158: /** If true, all outward links use a small link image. */
0159: private boolean m_useOutlinkImage = true;
0160:
0161: /** If true, allows raw HTML. */
0162: private boolean m_allowHTML = false;
0163:
0164: /** If true, executes plugins; otherwise ignores them. */
0165: private boolean m_enablePlugins = true;
0166:
0167: private boolean m_useRelNofollow = false;
0168:
0169: private boolean m_inlineImages = true;
0170:
0171: private PatternMatcher m_matcher = new Perl5Matcher();
0172: private PatternCompiler m_compiler = new Perl5Compiler();
0173: private Pattern m_camelCasePtrn;
0174:
0175: private TextRenderer m_renderer;
0176:
0177: /**
0178: * The default inlining pattern. Currently "*.png"
0179: */
0180: public static final String DEFAULT_INLINEPATTERN = "*.png";
0181:
0182: /**
0183: * These characters constitute word separators when trying
0184: * to find CamelCase links.
0185: */
0186: private static final String WORD_SEPARATORS = ",.|;+=&()";
0187:
0188: protected static final int BOLD = 0;
0189: protected static final int ITALIC = 1;
0190: protected static final int TYPED = 2;
0191:
0192: /**
0193: * This list contains all IANA registered URI protocol
0194: * types as of September 2004 + a few well-known extra types.
0195: *
0196: * JSPWiki recognises all of them as external links.
0197: */
0198: static final String[] c_externalLinks = { "http:", "ftp:",
0199: "https:", "mailto:", "news:", "file:", "rtsp:", "mms:",
0200: "ldap:", "gopher:", "nntp:", "telnet:", "wais:",
0201: "prospero:", "z39.50s", "z39.50r", "vemmi:", "imap:",
0202: "nfs:", "acap:", "tip:", "pop:", "dav:",
0203: "opaquelocktoken:", "sip:", "sips:", "tel:", "fax:",
0204: "modem:", "soap.beep:", "soap.beeps", "xmlrpc.beep",
0205: "xmlrpc.beeps", "urn:", "go:", "h323:", "ipp:", "tftp:",
0206: "mupdate:", "pres:", "im:", "mtqp", "smb:" };
0207:
0208: /**
0209: * Creates a TranslatorReader using the default HTML renderer.
0210: */
0211: public TranslatorReader(WikiContext context, Reader in) {
0212: initialize(context, in, new HTMLRenderer());
0213: }
0214:
0215: public TranslatorReader(WikiContext context, Reader in,
0216: TextRenderer renderer) {
0217: initialize(context, in, renderer);
0218: }
0219:
0220: /**
0221: * Replaces the current input character stream with a new one.
0222: * @param in New source for input. If null, this method does nothing.
0223: * @return the old stream
0224: */
0225: public Reader setInputReader(Reader in) {
0226: Reader old = m_in;
0227:
0228: if (in != null) {
0229: m_in = new PushbackReader(new BufferedReader(in),
0230: PUSHBACK_BUFFER_SIZE);
0231: }
0232:
0233: return old;
0234: }
0235:
0236: /**
0237: * @param m_engine The WikiEngine this reader is attached to. Is
0238: * used to figure out of a page exits.
0239: */
0240:
0241: // FIXME: TranslatorReaders should be pooled for better performance.
0242: private void initialize(WikiContext context, Reader in,
0243: TextRenderer renderer) {
0244: PatternCompiler compiler = new GlobCompiler();
0245: ArrayList compiledpatterns = new ArrayList();
0246:
0247: m_engine = context.getEngine();
0248: m_context = context;
0249:
0250: m_renderer = renderer;
0251:
0252: setInputReader(in);
0253:
0254: Collection ptrns = getImagePatterns(m_engine);
0255:
0256: //
0257: // Make them into Regexp Patterns. Unknown patterns
0258: // are ignored.
0259: //
0260: for (Iterator i = ptrns.iterator(); i.hasNext();) {
0261: try {
0262: compiledpatterns.add(compiler
0263: .compile((String) i.next()));
0264: } catch (MalformedPatternException e) {
0265: log.error("Malformed pattern in properties: ", e);
0266: }
0267: }
0268:
0269: m_inlineImagePatterns = compiledpatterns;
0270:
0271: try {
0272: m_camelCasePtrn = m_compiler
0273: .compile("^([[:^alnum:]]*)([[:upper:]]+[[:lower:]]+[[:upper:]]+[[:alnum:]]*)[[:^alnum:]]*$");
0274: } catch (MalformedPatternException e) {
0275: log.fatal(
0276: "Internal error: Someone put in a faulty pattern.",
0277: e);
0278: throw new InternalWikiException(
0279: "Faulty camelcasepattern in TranslatorReader");
0280: }
0281:
0282: //
0283: // Set the properties.
0284: //
0285: Properties props = m_engine.getWikiProperties();
0286:
0287: String cclinks = (String) m_context.getPage().getAttribute(
0288: PROP_CAMELCASELINKS);
0289:
0290: if (cclinks != null) {
0291: m_camelCaseLinks = TextUtil.isPositive(cclinks);
0292: } else {
0293: m_camelCaseLinks = TextUtil.getBooleanProperty(props,
0294: PROP_CAMELCASELINKS, m_camelCaseLinks);
0295: }
0296:
0297: m_plainUris = TextUtil.getBooleanProperty(props,
0298: PROP_PLAINURIS, m_plainUris);
0299: m_useOutlinkImage = TextUtil.getBooleanProperty(props,
0300: PROP_USEOUTLINKIMAGE, m_useOutlinkImage);
0301: m_allowHTML = TextUtil.getBooleanProperty(props,
0302: PROP_ALLOWHTML, m_allowHTML);
0303:
0304: m_useRelNofollow = TextUtil.getBooleanProperty(props,
0305: PROP_USERELNOFOLLOW, m_useRelNofollow);
0306:
0307: String runplugins = m_engine.getVariable(m_context,
0308: PROP_RUNPLUGINS);
0309: if (runplugins != null)
0310: enablePlugins(TextUtil.isPositive(runplugins));
0311:
0312: if (m_engine.getUserManager().getUserDatabase() == null
0313: || m_engine.getAuthorizationManager() == null) {
0314: disableAccessRules();
0315: }
0316:
0317: m_context.getPage().setHasMetadata();
0318: }
0319:
0320: /**
0321: * Sets the currently used renderer. This method is protected because
0322: * we only want to use it internally for now. The renderer interface
0323: * is not yet set to stone, so it's not expected that third parties
0324: * would use this.
0325: */
0326: protected void setRenderer(TextRenderer renderer) {
0327: m_renderer = renderer;
0328: }
0329:
0330: /**
0331: * Adds a hook for processing link texts. This hook is called
0332: * when the link text is written into the output stream, and
0333: * you may use it to modify the text. It does not affect the
0334: * actual link, only the user-visible text.
0335: *
0336: * @param mutator The hook to call. Null is safe.
0337: */
0338: public void addLinkTransmutator(StringTransmutator mutator) {
0339: if (mutator != null) {
0340: m_linkMutators.add(mutator);
0341: }
0342: }
0343:
0344: /**
0345: * Adds a hook for processing local links. The engine
0346: * transforms both non-existing and existing page links.
0347: *
0348: * @param mutator The hook to call. Null is safe.
0349: */
0350: public void addLocalLinkHook(StringTransmutator mutator) {
0351: if (mutator != null) {
0352: m_localLinkMutatorChain.add(mutator);
0353: }
0354: }
0355:
0356: /**
0357: * Adds a hook for processing external links. This includes
0358: * all http:// ftp://, etc. links, including inlined images.
0359: *
0360: * @param mutator The hook to call. Null is safe.
0361: */
0362: public void addExternalLinkHook(StringTransmutator mutator) {
0363: if (mutator != null) {
0364: m_externalLinkMutatorChain.add(mutator);
0365: }
0366: }
0367:
0368: /**
0369: * Adds a hook for processing attachment links.
0370: *
0371: * @param mutator The hook to call. Null is safe.
0372: */
0373: public void addAttachmentLinkHook(StringTransmutator mutator) {
0374: if (mutator != null) {
0375: m_attachmentLinkMutatorChain.add(mutator);
0376: }
0377: }
0378:
0379: public void addHeadingListener(HeadingListener listener) {
0380: if (listener != null) {
0381: m_headingListenerChain.add(listener);
0382: }
0383: }
0384:
0385: private boolean m_parseAccessRules = true;
0386:
0387: public void disableAccessRules() {
0388: m_parseAccessRules = false;
0389: }
0390:
0391: /**
0392: * Can be used to turn on plugin execution on a translator-reader basis
0393: */
0394: public void enablePlugins(boolean toggle) {
0395: m_enablePlugins = toggle;
0396: }
0397:
0398: /**
0399: * Use this to turn on or off image inlining.
0400: * @param toggle If true, images are inlined (as per set in jspwiki.properties)
0401: * If false, then images won't be inlined; instead, they will be
0402: * treated as standard hyperlinks.
0403: * @since 2.2.9
0404: */
0405: public void enableImageInlining(boolean toggle) {
0406: m_inlineImages = toggle;
0407: }
0408:
0409: /**
0410: * Figure out which image suffixes should be inlined.
0411: * @return Collection of Strings with patterns.
0412: */
0413:
0414: protected static Collection getImagePatterns(WikiEngine engine) {
0415: Properties props = engine.getWikiProperties();
0416: ArrayList ptrnlist = new ArrayList();
0417:
0418: for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
0419: String name = (String) e.nextElement();
0420:
0421: if (name.startsWith(PROP_INLINEIMAGEPTRN)) {
0422: String ptrn = props.getProperty(name);
0423:
0424: ptrnlist.add(ptrn);
0425: }
0426: }
0427:
0428: if (ptrnlist.size() == 0) {
0429: ptrnlist.add(DEFAULT_INLINEPATTERN);
0430: }
0431:
0432: return ptrnlist;
0433: }
0434:
0435: /**
0436: * Returns link name, if it exists; otherwise it returns null.
0437: */
0438: private String linkExists(String page) {
0439: try {
0440: if (page == null || page.length() == 0)
0441: return null;
0442:
0443: return m_engine.getFinalPageName(page);
0444: } catch (ProviderException e) {
0445: log.warn("TranslatorReader got a faulty page name!", e);
0446:
0447: return page; // FIXME: What would be the correct way to go back?
0448: }
0449: }
0450:
0451: /**
0452: * Calls a transmutator chain.
0453: *
0454: * @param list Chain to call
0455: * @param text Text that should be passed to the mutate() method
0456: * of each of the mutators in the chain.
0457: * @return The result of the mutation.
0458: */
0459:
0460: private String callMutatorChain(Collection list, String text) {
0461: if (list == null || list.size() == 0) {
0462: return text;
0463: }
0464:
0465: for (Iterator i = list.iterator(); i.hasNext();) {
0466: StringTransmutator m = (StringTransmutator) i.next();
0467:
0468: text = m.mutate(m_context, text);
0469: }
0470:
0471: return text;
0472: }
0473:
0474: private void callHeadingListenerChain(Heading param) {
0475: List list = m_headingListenerChain;
0476:
0477: for (Iterator i = list.iterator(); i.hasNext();) {
0478: HeadingListener h = (HeadingListener) i.next();
0479:
0480: h.headingAdded(m_context, param);
0481: }
0482: }
0483:
0484: /**
0485: * Write a HTMLized link depending on its type.
0486: * The link mutator chain is processed.
0487: *
0488: * @param type Type of the link.
0489: * @param link The actual link.
0490: * @param text The user-visible text for the link.
0491: */
0492: public String makeLink(int type, String link, String text) {
0493: if (text == null)
0494: text = link;
0495:
0496: text = callMutatorChain(m_linkMutators, text);
0497:
0498: return m_renderer.makeLink(type, link, text);
0499: }
0500:
0501: /**
0502: * Just like makeLink, but also adds the section reference (#sect...)
0503: */
0504: private String makeLink(int type, String link, String text,
0505: String sectref) {
0506: if (text == null)
0507: text = link;
0508:
0509: text = callMutatorChain(m_linkMutators, text);
0510:
0511: return m_renderer.makeLink(type, link, text, sectref);
0512: }
0513:
0514: /**
0515: * Cleans a Wiki name.
0516: * <P>
0517: * [ This is a link ] -> ThisIsALink
0518: *
0519: * @param link Link to be cleared. Null is safe, and causes this to return null.
0520: * @return A cleaned link.
0521: *
0522: * @since 2.0
0523: */
0524: public static String cleanLink(String link) {
0525: StringBuffer clean = new StringBuffer();
0526:
0527: if (link == null)
0528: return null;
0529:
0530: //
0531: // Compress away all whitespace and capitalize
0532: // all words in between.
0533: //
0534:
0535: StringTokenizer st = new StringTokenizer(link, " -");
0536:
0537: while (st.hasMoreTokens()) {
0538: StringBuffer component = new StringBuffer(st.nextToken());
0539:
0540: component.setCharAt(0, Character.toUpperCase(component
0541: .charAt(0)));
0542:
0543: //
0544: // We must do this, because otherwise compiling on JDK 1.4 causes
0545: // a downwards incompatibility to JDK 1.3.
0546: //
0547: clean.append(component.toString());
0548: }
0549:
0550: //
0551: // Remove non-alphanumeric characters that should not
0552: // be put inside WikiNames. Note that all valid
0553: // Unicode letters are considered okay for WikiNames.
0554: // It is the problem of the WikiPageProvider to take
0555: // care of actually storing that information.
0556: //
0557:
0558: for (int i = 0; i < clean.length(); i++) {
0559: char ch = clean.charAt(i);
0560:
0561: if (!(Character.isLetterOrDigit(ch) || PUNCTUATION_CHARS_ALLOWED
0562: .indexOf(ch) != -1)) {
0563: clean.deleteCharAt(i);
0564: --i; // We just shortened this buffer.
0565: }
0566: }
0567:
0568: return clean.toString();
0569: }
0570:
0571: /**
0572: * Figures out if a link is an off-site link. This recognizes
0573: * the most common protocols by checking how it starts.
0574: */
0575:
0576: // FIXME: Should really put the external link types to a sorted set,
0577: // then searching for them would be faster.
0578: private boolean isExternalLink(String link) {
0579: for (int i = 0; i < c_externalLinks.length; i++) {
0580: if (link.startsWith(c_externalLinks[i]))
0581: return true;
0582: }
0583:
0584: return false;
0585: }
0586:
0587: /**
0588: * Returns true, if the link in question is an access
0589: * rule.
0590: */
0591: private static boolean isAccessRule(String link) {
0592: return link.startsWith("{ALLOW") || link.startsWith("{DENY");
0593: }
0594:
0595: /**
0596: * Matches the given link to the list of image name patterns
0597: * to determine whether it should be treated as an inline image
0598: * or not.
0599: */
0600: private boolean isImageLink(String link) {
0601: if (m_inlineImages) {
0602: for (Iterator i = m_inlineImagePatterns.iterator(); i
0603: .hasNext();) {
0604: if (m_inlineMatcher.matches(link, (Pattern) i.next()))
0605: return true;
0606: }
0607: }
0608:
0609: return false;
0610: }
0611:
0612: private static boolean isMetadata(String link) {
0613: return link.startsWith("{SET");
0614: }
0615:
0616: /**
0617: * Returns true, if the argument contains a number, otherwise false.
0618: * In a quick test this is roughly the same speed as Integer.parseInt()
0619: * if the argument is a number, and roughly ten times the speed, if
0620: * the argument is NOT a number.
0621: */
0622:
0623: private boolean isNumber(String s) {
0624: if (s == null)
0625: return false;
0626:
0627: if (s.length() > 1 && s.charAt(0) == '-')
0628: s = s.substring(1);
0629:
0630: for (int i = 0; i < s.length(); i++) {
0631: if (!Character.isDigit(s.charAt(i)))
0632: return false;
0633: }
0634:
0635: return true;
0636: }
0637:
0638: /**
0639: * Checks for the existence of a traditional style CamelCase link.
0640: * <P>
0641: * We separate all white-space -separated words, and feed it to this
0642: * routine to find if there are any possible camelcase links.
0643: * For example, if "word" is "__HyperLink__" we return "HyperLink".
0644: *
0645: * @param word A phrase to search in.
0646: * @return The match within the phrase. Returns null, if no CamelCase
0647: * hyperlink exists within this phrase.
0648: */
0649: private String checkForCamelCaseLink(String word) {
0650: PatternMatcherInput input;
0651:
0652: input = new PatternMatcherInput(word);
0653:
0654: if (m_matcher.contains(input, m_camelCasePtrn)) {
0655: MatchResult res = m_matcher.getMatch();
0656:
0657: String link = res.group(2);
0658:
0659: if (res.group(1) != null) {
0660: if (res.group(1).endsWith("~")
0661: || res.group(1).indexOf('[') != -1) {
0662: // Delete the (~) from beginning.
0663: // We'll make '~' the generic kill-processing-character from
0664: // now on.
0665: return null;
0666: }
0667: }
0668:
0669: return link;
0670: } // if match
0671:
0672: return null;
0673: }
0674:
0675: /**
0676: * When given a link to a WikiName, we just return
0677: * a proper HTML link for it. The local link mutator
0678: * chain is also called.
0679: */
0680: private String makeCamelCaseLink(String wikiname) {
0681: String matchedLink;
0682: String link;
0683:
0684: callMutatorChain(m_localLinkMutatorChain, wikiname);
0685:
0686: if ((matchedLink = linkExists(wikiname)) != null) {
0687: link = makeLink(READ, matchedLink, wikiname);
0688: } else {
0689: link = makeLink(EDIT, wikiname, wikiname);
0690: }
0691:
0692: return link;
0693: }
0694:
0695: private String makeDirectURILink(String url) {
0696: String last = "";
0697: String result;
0698:
0699: if (url.endsWith(",") || url.endsWith(".")) {
0700: last = url.substring(url.length() - 1);
0701: url = url.substring(0, url.length() - 1);
0702: }
0703:
0704: callMutatorChain(m_externalLinkMutatorChain, url);
0705:
0706: if (isImageLink(url)) {
0707: result = handleImageLink(url, url, false);
0708: } else {
0709: result = makeLink(EXTERNAL, url, url)
0710: + m_renderer.outlinkImage();
0711: }
0712:
0713: result += last;
0714:
0715: return result;
0716: }
0717:
0718: /**
0719: * Image links are handled differently:
0720: * 1. If the text is a WikiName of an existing page,
0721: * it gets linked.
0722: * 2. If the text is an external link, then it is inlined.
0723: * 3. Otherwise it becomes an ALT text.
0724: *
0725: * @param reallink The link to the image.
0726: * @param link Link text portion, may be a link to somewhere else.
0727: * @param hasLinkText If true, then the defined link had a link text available.
0728: * This means that the link text may be a link to a wiki page,
0729: * or an external resource.
0730: */
0731:
0732: private String handleImageLink(String reallink, String link,
0733: boolean hasLinkText) {
0734: String possiblePage = cleanLink(link);
0735: String res = "";
0736:
0737: if (isExternalLink(link) && hasLinkText) {
0738: res = makeLink(IMAGELINK, reallink, link);
0739: } else if ((linkExists(possiblePage)) != null && hasLinkText) {
0740: // System.out.println("Orig="+link+", Matched: "+matchedLink);
0741: callMutatorChain(m_localLinkMutatorChain, possiblePage);
0742:
0743: res = makeLink(IMAGEWIKILINK, reallink, link);
0744: } else {
0745: res = makeLink(IMAGE, reallink, link);
0746: }
0747:
0748: return res;
0749: }
0750:
0751: private String handleAccessRule(String ruleLine) {
0752: if (!m_parseAccessRules)
0753: return "";
0754: Acl acl;
0755: WikiPage page = m_context.getPage();
0756: // UserDatabase db = m_context.getEngine().getUserDatabase();
0757:
0758: if (ruleLine.startsWith("{"))
0759: ruleLine = ruleLine.substring(1);
0760: if (ruleLine.endsWith("}"))
0761: ruleLine = ruleLine.substring(0, ruleLine.length() - 1);
0762:
0763: log.debug("page=" + page.getName() + ", ACL = " + ruleLine);
0764:
0765: try {
0766: acl = m_engine.getAclManager().parseAcl(page, ruleLine);
0767:
0768: page.setAcl(acl);
0769:
0770: log.debug(acl.toString());
0771: } catch (WikiSecurityException wse) {
0772: return m_renderer.makeError(wse.getMessage());
0773: }
0774:
0775: return "";
0776: }
0777:
0778: /**
0779: * Handles metadata setting [{SET foo=bar}]
0780: */
0781: private String handleMetadata(String link) {
0782: try {
0783: String args = link.substring(link.indexOf(' '), link
0784: .length() - 1);
0785:
0786: String name = args.substring(0, args.indexOf('='));
0787: String val = args.substring(args.indexOf('=') + 1, args
0788: .length());
0789:
0790: name = name.trim();
0791: val = val.trim();
0792:
0793: if (val.startsWith("'"))
0794: val = val.substring(1);
0795: if (val.endsWith("'"))
0796: val = val.substring(0, val.length() - 1);
0797:
0798: // log.debug("SET name='"+name+"', value='"+val+"'.");
0799:
0800: if (name.length() > 0 && val.length() > 0) {
0801: val = m_engine.getVariableManager().expandVariables(
0802: m_context, val);
0803:
0804: m_context.getPage().setAttribute(name, val);
0805: }
0806: } catch (Exception e) {
0807: ResourceBundle rb = m_context
0808: .getBundle(InternationalizationManager.CORE_BUNDLE);
0809: Object[] args = { link };
0810: m_renderer.makeError(MessageFormat.format(rb
0811: .getString("markupparser.error.invalidset"), args));
0812: }
0813:
0814: return "";
0815: }
0816:
0817: /**
0818: * Gobbles up all hyperlinks that are encased in square brackets.
0819: */
0820: private String handleHyperlinks(String link) {
0821: StringBuffer sb = new StringBuffer();
0822: String reallink;
0823: int cutpoint;
0824:
0825: if (isAccessRule(link)) {
0826: return handleAccessRule(link);
0827: }
0828:
0829: if (isMetadata(link)) {
0830: return handleMetadata(link);
0831: }
0832:
0833: if (PluginManager.isPluginLink(link)) {
0834: String included = "";
0835: try {
0836: if (m_enablePlugins) {
0837: included = m_engine.getPluginManager().execute(
0838: m_context, link);
0839: }
0840: } catch (PluginException e) {
0841: log.info("Failed to insert plugin", e);
0842: log.info("Root cause:", e.getRootThrowable());
0843:
0844: ResourceBundle rb = m_context
0845: .getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
0846: Object[] args = { e.getMessage() };
0847:
0848: included = m_renderer.makeError(MessageFormat.format(rb
0849: .getString("plugin.error.insertionfailed"),
0850: args));
0851: }
0852:
0853: sb.append(included);
0854:
0855: return sb.toString();
0856: }
0857:
0858: link = TextUtil.replaceEntities(link);
0859:
0860: if ((cutpoint = link.indexOf('|')) != -1) {
0861: reallink = link.substring(cutpoint + 1).trim();
0862: link = link.substring(0, cutpoint);
0863: } else {
0864: reallink = link.trim();
0865: }
0866:
0867: int interwikipoint = -1;
0868:
0869: //
0870: // Yes, we now have the components separated.
0871: // link = the text the link should have
0872: // reallink = the url or page name.
0873: //
0874: // In many cases these are the same. [link|reallink].
0875: //
0876: if (VariableManager.isVariableLink(link)) {
0877: String value;
0878:
0879: try {
0880: value = m_engine.getVariableManager().parseAndGetValue(
0881: m_context, link);
0882: } catch (NoSuchVariableException e) {
0883: value = m_renderer.makeError(e.getMessage());
0884: } catch (IllegalArgumentException e) {
0885: value = m_renderer.makeError(e.getMessage());
0886: }
0887:
0888: sb.append(value);
0889: } else if (isExternalLink(reallink)) {
0890: // It's an external link, out of this Wiki
0891:
0892: callMutatorChain(m_externalLinkMutatorChain, reallink);
0893:
0894: if (isImageLink(reallink)) {
0895: sb.append(handleImageLink(reallink, link,
0896: (cutpoint != -1)));
0897: } else {
0898: sb.append(makeLink(EXTERNAL, reallink, link));
0899: sb.append(m_renderer.outlinkImage());
0900: }
0901: } else if ((interwikipoint = reallink.indexOf(":")) != -1) {
0902: // It's an interwiki link
0903: // InterWiki links also get added to external link chain
0904: // after the links have been resolved.
0905:
0906: // FIXME: There is an interesting issue here: We probably should
0907: // URLEncode the wikiPage, but we can't since some of the
0908: // Wikis use slashes (/), which won't survive URLEncoding.
0909: // Besides, we don't know which character set the other Wiki
0910: // is using, so you'll have to write the entire name as it appears
0911: // in the URL. Bugger.
0912:
0913: String extWiki = reallink.substring(0, interwikipoint);
0914: String wikiPage = reallink.substring(interwikipoint + 1);
0915:
0916: String urlReference = m_engine.getInterWikiURL(extWiki);
0917:
0918: if (urlReference != null) {
0919: urlReference = TextUtil.replaceString(urlReference,
0920: "%s", wikiPage);
0921: callMutatorChain(m_externalLinkMutatorChain,
0922: urlReference);
0923:
0924: sb.append(makeLink(INTERWIKI, urlReference, link));
0925:
0926: if (isExternalLink(urlReference)) {
0927: sb.append(m_renderer.outlinkImage());
0928: }
0929: } else {
0930: ResourceBundle rb = m_context
0931: .getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
0932: Object[] args = { extWiki };
0933:
0934: sb
0935: .append(link
0936: + " "
0937: + m_renderer
0938: .makeError(MessageFormat
0939: .format(
0940: rb
0941: .getString("plugin.error.nointerwikiref"),
0942: args)));
0943: }
0944: } else if (reallink.startsWith("#")) {
0945: // It defines a local footnote
0946: sb.append(makeLink(LOCAL, reallink, link));
0947: } else if (isNumber(reallink)) {
0948: // It defines a reference to a local footnote
0949: sb.append(makeLink(LOCALREF, reallink, link));
0950: } else {
0951: int hashMark = -1;
0952:
0953: //
0954: // Internal wiki link, but is it an attachment link?
0955: //
0956: String attachment = findAttachment(reallink);
0957: if (attachment != null) {
0958: callMutatorChain(m_attachmentLinkMutatorChain,
0959: attachment);
0960:
0961: if (isImageLink(reallink)) {
0962: attachment = m_context.getURL(WikiContext.ATTACH,
0963: attachment);
0964: sb.append(handleImageLink(attachment, link,
0965: (cutpoint != -1)));
0966: } else {
0967: sb.append(makeLink(ATTACHMENT, attachment, link));
0968: }
0969: } else if ((hashMark = reallink.indexOf('#')) != -1) {
0970: // It's an internal Wiki link, but to a named section
0971:
0972: String namedSection = reallink.substring(hashMark + 1);
0973: reallink = reallink.substring(0, hashMark);
0974:
0975: reallink = cleanLink(reallink);
0976:
0977: callMutatorChain(m_localLinkMutatorChain, reallink);
0978:
0979: String matchedLink;
0980: if ((matchedLink = linkExists(reallink)) != null) {
0981: String sectref = "section-"
0982: + m_engine.encodeName(matchedLink) + "-"
0983: + namedSection;
0984: sectref = sectref.replace('%', '_');
0985: sb
0986: .append(makeLink(READ, matchedLink, link,
0987: sectref));
0988: } else {
0989: sb.append(makeLink(EDIT, reallink, link));
0990: }
0991: } else {
0992: // It's an internal Wiki link
0993: reallink = cleanLink(reallink);
0994:
0995: callMutatorChain(m_localLinkMutatorChain, reallink);
0996:
0997: String matchedLink = linkExists(reallink);
0998:
0999: if (matchedLink != null) {
1000: sb.append(makeLink(READ, matchedLink, link));
1001: } else {
1002: sb.append(makeLink(EDIT, reallink, link));
1003: }
1004: }
1005: }
1006:
1007: return sb.toString();
1008: }
1009:
1010: private String findAttachment(String link) {
1011: AttachmentManager mgr = m_engine.getAttachmentManager();
1012: Attachment att = null;
1013:
1014: try {
1015: att = mgr.getAttachmentInfo(m_context, link);
1016: } catch (ProviderException e) {
1017: log.warn("Finding attachments failed: ", e);
1018: return null;
1019: }
1020:
1021: if (att != null) {
1022: return att.getName();
1023: } else if (link.indexOf('/') != -1) {
1024: return link;
1025: }
1026:
1027: return null;
1028: }
1029:
1030: /**
1031: * Closes all annoying lists and things that the user might've
1032: * left open.
1033: */
1034: private String closeAll() {
1035: StringBuffer buf = new StringBuffer();
1036:
1037: if (m_isbold) {
1038: buf.append(m_renderer.closeTextEffect(BOLD));
1039: m_isbold = false;
1040: }
1041:
1042: if (m_isitalic) {
1043: buf.append(m_renderer.closeTextEffect(ITALIC));
1044: m_isitalic = false;
1045: }
1046:
1047: if (m_isTypedText) {
1048: buf.append(m_renderer.closeTextEffect(TYPED));
1049: m_isTypedText = false;
1050: }
1051:
1052: /*
1053: for( ; m_listlevel > 0; m_listlevel-- )
1054: {
1055: buf.append( "</ul>\n" );
1056: }
1057:
1058: for( ; m_numlistlevel > 0; m_numlistlevel-- )
1059: {
1060: buf.append( "</ol>\n" );
1061: }
1062: */
1063: // cleanup OL and UL lists
1064: buf.append(unwindGeneralList());
1065:
1066: if (m_isPre) {
1067: buf.append(m_renderer.closePreformatted());
1068: m_isEscaping = false;
1069: m_isPre = false;
1070: }
1071:
1072: if (m_istable) {
1073: buf.append(m_renderer.closeTable());
1074: m_istable = false;
1075: }
1076:
1077: if (m_isOpenParagraph) {
1078: buf.append(m_renderer.closeParagraph());
1079: m_isOpenParagraph = false;
1080: }
1081:
1082: return buf.toString();
1083: }
1084:
1085: private int nextToken() throws IOException {
1086: if (m_in == null)
1087: return -1;
1088: return m_in.read();
1089: }
1090:
1091: /**
1092: * Push back any character to the current input. Does not
1093: * push back a read EOF, though.
1094: */
1095: private void pushBack(int c) throws IOException {
1096: if (c != -1 && m_in != null) {
1097: m_in.unread(c);
1098: }
1099: }
1100:
1101: /**
1102: * Pushes back any string that has been read. It will obviously
1103: * be pushed back in a reverse order.
1104: *
1105: * @since 2.1.77
1106: */
1107: private void pushBack(String s) throws IOException {
1108: for (int i = s.length() - 1; i >= 0; i--) {
1109: pushBack(s.charAt(i));
1110: }
1111: }
1112:
1113: private String handleBackslash() throws IOException {
1114: int ch = nextToken();
1115:
1116: if (ch == '\\') {
1117: int ch2 = nextToken();
1118:
1119: if (ch2 == '\\') {
1120: return m_renderer.lineBreak(true);
1121: }
1122:
1123: pushBack(ch2);
1124:
1125: return m_renderer.lineBreak(false);
1126: }
1127:
1128: pushBack(ch);
1129:
1130: return "\\";
1131: }
1132:
1133: private String handleUnderscore() throws IOException {
1134: int ch = nextToken();
1135: String res = "_";
1136:
1137: if (ch == '_') {
1138: res = m_isbold ? m_renderer.closeTextEffect(BOLD)
1139: : m_renderer.openTextEffect(BOLD);
1140: m_isbold = !m_isbold;
1141: } else {
1142: pushBack(ch);
1143: }
1144:
1145: return res;
1146: }
1147:
1148: /**
1149: * For example: italics.
1150: */
1151: private String handleApostrophe() throws IOException {
1152: int ch = nextToken();
1153: String res = "'";
1154:
1155: if (ch == '\'') {
1156: res = m_isitalic ? m_renderer.closeTextEffect(ITALIC)
1157: : m_renderer.openTextEffect(ITALIC);
1158: m_isitalic = !m_isitalic;
1159: } else {
1160: pushBack(ch);
1161: }
1162:
1163: return res;
1164: }
1165:
1166: private String handleOpenbrace(boolean isBlock) throws IOException {
1167: int ch = nextToken();
1168: String res = "{";
1169:
1170: if (ch == '{') {
1171: int ch2 = nextToken();
1172:
1173: if (ch2 == '{') {
1174: res = startBlockLevel()
1175: + m_renderer.openPreformatted(isBlock);
1176: m_isPre = true;
1177: m_isEscaping = true;
1178: } else {
1179: pushBack(ch2);
1180:
1181: res = m_renderer.openTextEffect(TYPED);
1182: m_isTypedText = true;
1183: }
1184: } else {
1185: pushBack(ch);
1186: }
1187:
1188: return res;
1189: }
1190:
1191: /**
1192: * Handles both }} and }}}
1193: */
1194: private String handleClosebrace() throws IOException {
1195: String res = "}";
1196:
1197: int ch2 = nextToken();
1198:
1199: if (ch2 == '}') {
1200: int ch3 = nextToken();
1201:
1202: if (ch3 == '}') {
1203: if (m_isPre) {
1204: m_isPre = false;
1205: m_isEscaping = false;
1206: res = m_renderer.closePreformatted();
1207: } else {
1208: res = "}}}";
1209: }
1210: } else {
1211: pushBack(ch3);
1212:
1213: if (!m_isEscaping) {
1214: res = m_renderer.closeTextEffect(TYPED);
1215: m_isTypedText = false;
1216: } else {
1217: pushBack(ch2);
1218: }
1219: }
1220: } else {
1221: pushBack(ch2);
1222: }
1223:
1224: return res;
1225: }
1226:
1227: private String handleDash() throws IOException {
1228: int ch = nextToken();
1229:
1230: if (ch == '-') {
1231: int ch2 = nextToken();
1232:
1233: if (ch2 == '-') {
1234: int ch3 = nextToken();
1235:
1236: if (ch3 == '-') {
1237: // Empty away all the rest of the dashes.
1238: // Do not forget to return the first non-match back.
1239: while ((ch = nextToken()) == '-')
1240: ;
1241:
1242: pushBack(ch);
1243: return startBlockLevel() + m_renderer.makeRuler();
1244: }
1245:
1246: pushBack(ch3);
1247: }
1248: pushBack(ch2);
1249: }
1250:
1251: pushBack(ch);
1252:
1253: return "-";
1254: }
1255:
1256: /**
1257: * This method peeks ahead in the stream until EOL and returns the result.
1258: * It will keep the buffers untouched.
1259: *
1260: * @return The string from the current position to the end of line.
1261: */
1262:
1263: // FIXME: Always returns an empty line, even if the stream is full.
1264: private String peekAheadLine() throws IOException {
1265: String s = readUntilEOL().toString();
1266: pushBack(s);
1267:
1268: return s;
1269: }
1270:
1271: private String handleHeading() throws IOException {
1272: StringBuffer buf = new StringBuffer();
1273:
1274: int ch = nextToken();
1275:
1276: Heading hd = new Heading();
1277:
1278: if (ch == '!') {
1279: int ch2 = nextToken();
1280:
1281: if (ch2 == '!') {
1282: String title = peekAheadLine();
1283:
1284: buf.append(m_renderer.makeHeading(
1285: Heading.HEADING_LARGE, title, hd));
1286: } else {
1287: pushBack(ch2);
1288: String title = peekAheadLine();
1289: buf.append(m_renderer.makeHeading(
1290: Heading.HEADING_MEDIUM, title, hd));
1291: }
1292: } else {
1293: pushBack(ch);
1294: String title = peekAheadLine();
1295: buf.append(m_renderer.makeHeading(Heading.HEADING_SMALL,
1296: title, hd));
1297: }
1298:
1299: callHeadingListenerChain(hd);
1300:
1301: return buf.toString();
1302: }
1303:
1304: /**
1305: * Reads the stream until the next EOL or EOF. Note that it will also read the
1306: * EOL from the stream.
1307: */
1308: private StringBuffer readUntilEOL() throws IOException {
1309: int ch;
1310: StringBuffer buf = new StringBuffer();
1311:
1312: while (true) {
1313: ch = nextToken();
1314:
1315: if (ch == -1)
1316: break;
1317:
1318: buf.append((char) ch);
1319:
1320: if (ch == '\n')
1321: break;
1322: }
1323:
1324: return buf;
1325: }
1326:
1327: /**
1328: * Starts a block level element, therefore closing the
1329: * a potential open paragraph tag.
1330: */
1331: private String startBlockLevel() {
1332: if (m_isOpenParagraph) {
1333: m_isOpenParagraph = false;
1334: return m_renderer.closeParagraph();
1335: }
1336:
1337: return "";
1338: }
1339:
1340: /**
1341: * Like original handleOrderedList() and handleUnorderedList()
1342: * however handles both ordered ('#') and unordered ('*') mixed together.
1343: */
1344:
1345: // FIXME: Refactor this; it's a bit messy.
1346: private String handleGeneralList() throws IOException {
1347: StringBuffer buf = new StringBuffer();
1348:
1349: buf.append(startBlockLevel());
1350:
1351: String strBullets = readWhile("*#");
1352: // String strBulletsRaw = strBullets; // to know what was original before phpwiki style substitution
1353: int numBullets = strBullets.length();
1354:
1355: // override the beginning portion of bullet pattern to be like the previous
1356: // to simulate PHPWiki style lists
1357:
1358: if (m_allowPHPWikiStyleLists) {
1359: // only substitute if different
1360: if (!(strBullets.substring(0, Math.min(numBullets,
1361: m_genlistlevel))
1362: .equals(m_genlistBulletBuffer.substring(0, Math
1363: .min(numBullets, m_genlistlevel))))) {
1364: if (numBullets <= m_genlistlevel) {
1365: // Substitute all but the last character (keep the expressed bullet preference)
1366: strBullets = (numBullets > 1 ? m_genlistBulletBuffer
1367: .substring(0, numBullets - 1)
1368: : "")
1369: + strBullets.substring(numBullets - 1,
1370: numBullets);
1371: } else {
1372: strBullets = m_genlistBulletBuffer
1373: + strBullets.substring(m_genlistlevel,
1374: numBullets);
1375: }
1376: }
1377: }
1378:
1379: //
1380: // Check if this is still of the same type
1381: //
1382: if (strBullets.substring(0,
1383: Math.min(numBullets, m_genlistlevel)).equals(
1384: m_genlistBulletBuffer.substring(0, Math.min(numBullets,
1385: m_genlistlevel)))) {
1386: if (numBullets > m_genlistlevel) {
1387: buf.append(m_renderer.openList(strBullets
1388: .charAt(m_genlistlevel++)));
1389:
1390: for (; m_genlistlevel < numBullets; m_genlistlevel++) {
1391: // bullets are growing, get from new bullet list
1392: buf.append(m_renderer.openListItem());
1393: buf.append(m_renderer.openList(strBullets
1394: .charAt(m_genlistlevel)));
1395: }
1396: } else if (numBullets < m_genlistlevel) {
1397: // Close the previous list item.
1398: buf.append(m_renderer.closeListItem());
1399:
1400: for (; m_genlistlevel > numBullets; m_genlistlevel--) {
1401: // bullets are shrinking, get from old bullet list
1402: buf.append(m_renderer
1403: .closeList(m_genlistBulletBuffer
1404: .charAt(m_genlistlevel - 1)));
1405: if (m_genlistlevel > 0)
1406: buf.append(m_renderer.closeListItem());
1407:
1408: }
1409: } else {
1410: if (m_genlistlevel > 0)
1411: buf.append(m_renderer.closeListItem());
1412: }
1413: } else {
1414: //
1415: // The pattern has changed, unwind and restart
1416: //
1417: int numEqualBullets;
1418: int numCheckBullets;
1419:
1420: // find out how much is the same
1421: numEqualBullets = 0;
1422: numCheckBullets = Math.min(numBullets, m_genlistlevel);
1423:
1424: while (numEqualBullets < numCheckBullets) {
1425: // if the bullets are equal so far, keep going
1426: if (strBullets.charAt(numEqualBullets) == m_genlistBulletBuffer
1427: .charAt(numEqualBullets))
1428: numEqualBullets++;
1429: // otherwise giveup, we have found how many are equal
1430: else
1431: break;
1432: }
1433:
1434: //unwind
1435: for (; m_genlistlevel > numEqualBullets; m_genlistlevel--) {
1436: buf.append(m_renderer.closeList(m_genlistBulletBuffer
1437: .charAt(m_genlistlevel - 1)));
1438: if (m_genlistlevel > 0)
1439: buf.append(m_renderer.closeListItem());
1440: }
1441:
1442: //rewind
1443: buf.append(m_renderer.openList(strBullets
1444: .charAt(numEqualBullets++)));
1445: for (int i = numEqualBullets; i < numBullets; i++) {
1446: buf.append(m_renderer.openListItem());
1447: buf.append(m_renderer.openList(strBullets.charAt(i)));
1448: }
1449: m_genlistlevel = numBullets;
1450: }
1451: buf.append(m_renderer.openListItem());
1452:
1453: // work done, remember the new bullet list (in place of old one)
1454: m_genlistBulletBuffer.setLength(0);
1455: m_genlistBulletBuffer.append(strBullets);
1456:
1457: return buf.toString();
1458: }
1459:
1460: private String unwindGeneralList() {
1461: // String cStrShortName = "unwindGeneralList()";
1462:
1463: StringBuffer buf = new StringBuffer();
1464:
1465: //unwind
1466: for (; m_genlistlevel > 0; m_genlistlevel--) {
1467: buf.append(m_renderer.closeListItem());
1468: buf.append(m_renderer.closeList(m_genlistBulletBuffer
1469: .charAt(m_genlistlevel - 1)));
1470: }
1471:
1472: m_genlistBulletBuffer.setLength(0);
1473:
1474: return buf.toString();
1475: }
1476:
1477: private String handleDefinitionList() throws IOException {
1478: if (!m_isdefinition) {
1479: m_isdefinition = true;
1480:
1481: m_closeTag = m_renderer.closeDefinitionItem()
1482: + m_renderer.closeDefinitionList();
1483:
1484: return startBlockLevel() + m_renderer.openDefinitionList()
1485: + m_renderer.openDefinitionTitle();
1486: }
1487:
1488: return ";";
1489: }
1490:
1491: private String handleOpenbracket() throws IOException {
1492: StringBuffer sb = new StringBuffer();
1493: int ch;
1494: boolean isPlugin = false;
1495:
1496: while ((ch = nextToken()) == '[') {
1497: sb.append((char) ch);
1498: }
1499:
1500: if (ch == '{') {
1501: isPlugin = true;
1502: }
1503:
1504: pushBack(ch);
1505:
1506: if (sb.length() > 0) {
1507: return sb.toString();
1508: }
1509:
1510: //
1511: // Find end of hyperlink
1512: //
1513:
1514: ch = nextToken();
1515:
1516: while (ch != -1) {
1517: if (ch == ']'
1518: && (!isPlugin || sb.charAt(sb.length() - 1) == '}')) {
1519: break;
1520: }
1521:
1522: sb.append((char) ch);
1523:
1524: ch = nextToken();
1525: }
1526:
1527: if (ch == -1) {
1528: log.debug("Warning: unterminated link detected!");
1529: return sb.toString();
1530: }
1531:
1532: return handleHyperlinks(sb.toString());
1533: }
1534:
1535: /**
1536: * Reads the stream until the current brace is closed or stream end.
1537: */
1538: private String readBraceContent(char opening, char closing)
1539: throws IOException {
1540: StringBuffer sb = new StringBuffer();
1541: int braceLevel = 1;
1542: int ch;
1543: while ((ch = nextToken()) != -1) {
1544: if (ch == '\\') {
1545: continue;
1546: } else if (ch == opening) {
1547: braceLevel++;
1548: } else if (ch == closing) {
1549: braceLevel--;
1550: if (braceLevel == 0) {
1551: break;
1552: }
1553: }
1554: sb.append((char) ch);
1555: }
1556: return sb.toString();
1557: }
1558:
1559: /**
1560: * Reads the stream until it meets one of the specified
1561: * ending characters, or stream end. The ending character will be left
1562: * in the stream.
1563: */
1564: private String readUntil(String endChars) throws IOException {
1565: StringBuffer sb = new StringBuffer();
1566: int ch = nextToken();
1567:
1568: while (ch != -1) {
1569: if (ch == '\\') {
1570: ch = nextToken();
1571: if (ch == -1) {
1572: break;
1573: }
1574: } else {
1575: if (endChars.indexOf((char) ch) != -1) {
1576: pushBack(ch);
1577: break;
1578: }
1579: }
1580: sb.append((char) ch);
1581: ch = nextToken();
1582: }
1583:
1584: return sb.toString();
1585: }
1586:
1587: /**
1588: * Reads the stream while the characters that have been specified are
1589: * in the stream, returning then the result as a String.
1590: */
1591: private String readWhile(String endChars) throws IOException {
1592: StringBuffer sb = new StringBuffer();
1593: int ch = nextToken();
1594:
1595: while (ch != -1) {
1596: if (endChars.indexOf((char) ch) == -1) {
1597: pushBack(ch);
1598: break;
1599: }
1600:
1601: sb.append((char) ch);
1602: ch = nextToken();
1603: }
1604:
1605: return sb.toString();
1606: }
1607:
1608: /**
1609: * Handles constructs of type %%(style) and %%class
1610: * @param newLine
1611: * @return
1612: * @throws IOException
1613: */
1614: private String handleDiv(boolean newLine) throws IOException {
1615: int ch = nextToken();
1616:
1617: if (ch == '%') {
1618: StringBuffer sb = new StringBuffer();
1619:
1620: String style = null;
1621: String clazz = null;
1622:
1623: ch = nextToken();
1624:
1625: //
1626: // Style or class?
1627: //
1628: if (ch == '(') {
1629: style = readBraceContent('(', ')');
1630: } else if (Character.isLetter((char) ch)) {
1631: pushBack(ch);
1632: clazz = readUntil(" \t\n\r");
1633: ch = nextToken();
1634:
1635: //
1636: // Pop out only spaces, so that the upcoming EOL check does not check the
1637: // next line.
1638: //
1639: if (ch == '\n' || ch == '\r') {
1640: pushBack(ch);
1641: }
1642: } else {
1643: //
1644: // Anything else stops.
1645: //
1646:
1647: pushBack(ch);
1648:
1649: try {
1650: Boolean isSpan = (Boolean) m_styleStack.pop();
1651:
1652: if (isSpan == null) {
1653: // Fail quietly
1654: } else if (isSpan.booleanValue()) {
1655: sb.append(m_renderer.closeSpan());
1656: } else {
1657: sb.append(m_renderer.closeDiv());
1658: }
1659: } catch (EmptyStackException e) {
1660: log
1661: .debug("Page '"
1662: + m_context.getPage().getName()
1663: + "' closes a %%-block that has not been opened.");
1664: }
1665:
1666: return sb.toString();
1667: }
1668:
1669: //
1670: // Decide if we should open a div or a span?
1671: //
1672: String eol = peekAheadLine();
1673:
1674: if (eol.trim().length() > 0) {
1675: // There is stuff after the class
1676:
1677: sb.append(m_renderer.openSpan(style, clazz));
1678:
1679: m_styleStack.push(Boolean.TRUE);
1680: } else {
1681: sb.append(startBlockLevel());
1682: sb.append(m_renderer.openDiv(style, clazz));
1683: m_styleStack.push(Boolean.FALSE);
1684: }
1685:
1686: return sb.toString();
1687: }
1688:
1689: pushBack(ch);
1690:
1691: return "%";
1692: }
1693:
1694: private String handleBar(boolean newLine) throws IOException {
1695: StringBuffer sb = new StringBuffer();
1696:
1697: if (!m_istable && !newLine) {
1698: return "|";
1699: }
1700:
1701: if (newLine) {
1702: if (!m_istable) {
1703: sb.append(startBlockLevel());
1704: sb.append(m_renderer.openTable());
1705: m_istable = true;
1706: }
1707:
1708: sb.append(m_renderer.openTableRow());
1709: m_closeTag = m_renderer.closeTableItem()
1710: + m_renderer.closeTableRow();
1711: }
1712:
1713: int ch = nextToken();
1714:
1715: if (ch == '|') {
1716: if (!newLine) {
1717: sb.append(m_renderer.closeTableHeading());
1718: }
1719: sb.append(m_renderer.openTableHeading());
1720: m_closeTag = m_renderer.closeTableHeading()
1721: + m_renderer.closeTableRow();
1722: } else {
1723: if (!newLine) {
1724: sb.append(m_renderer.closeTableItem());
1725: }
1726: sb.append(m_renderer.openTableItem());
1727: pushBack(ch);
1728: }
1729:
1730: return sb.toString();
1731: }
1732:
1733: /**
1734: * Generic escape of next character or entity.
1735: */
1736: private String handleTilde() throws IOException {
1737: int ch = nextToken();
1738:
1739: if (ch == '|' || ch == '~' || ch == '\\' || ch == '*'
1740: || ch == '#' || ch == '-' || ch == '!' || ch == '\''
1741: || ch == '_' || ch == '[' || ch == '{' || ch == ']'
1742: || ch == '}') {
1743: StringBuffer sb = new StringBuffer();
1744: sb.append((char) ch);
1745: sb.append(readWhile("" + (char) ch));
1746: return sb.toString();
1747: }
1748:
1749: if (Character.isUpperCase((char) ch)) {
1750: pushBack(ch);
1751: return "";
1752: }
1753:
1754: // No escape.
1755: pushBack(ch);
1756:
1757: return "~";
1758: }
1759:
1760: private void fillBuffer() throws IOException {
1761: StringBuffer buf = new StringBuffer();
1762: StringBuffer word = null;
1763: int previousCh = -2;
1764: int start = 0;
1765:
1766: boolean quitReading = false;
1767: boolean newLine = true; // FIXME: not true if reading starts in middle of buffer
1768:
1769: while (!quitReading) {
1770: int ch = nextToken();
1771: String s = null;
1772:
1773: //
1774: // Check if we're actually ending the preformatted mode.
1775: // We still must do an entity transformation here.
1776: //
1777: if (m_isEscaping) {
1778: if (ch == '}') {
1779: buf.append(handleClosebrace());
1780: } else if (ch == -1) {
1781: quitReading = true;
1782: } else {
1783: m_renderer.doChar(buf, (char) ch);
1784: }
1785:
1786: continue;
1787: }
1788:
1789: //
1790: // CamelCase detection, a non-trivial endeavour.
1791: // We keep track of all white-space separated entities, which we
1792: // hereby refer to as "words". We then check for an existence
1793: // of a CamelCase format text string inside the "word", and
1794: // if one exists, we replace it with a proper link.
1795: //
1796:
1797: if (m_camelCaseLinks) {
1798: // Quick parse of start of a word boundary.
1799:
1800: if (word == null
1801: && (Character.isWhitespace((char) previousCh)
1802: || WORD_SEPARATORS
1803: .indexOf((char) previousCh) != -1 || newLine)
1804: && !Character.isWhitespace((char) ch)) {
1805: word = new StringBuffer();
1806: }
1807:
1808: // Are we currently tracking a word?
1809: if (word != null) {
1810: //
1811: // Check for the end of the word.
1812: //
1813:
1814: if (Character.isWhitespace((char) ch) || ch == -1
1815: || WORD_SEPARATORS.indexOf((char) ch) != -1) {
1816: String potentialLink = word.toString();
1817:
1818: String camelCase = checkForCamelCaseLink(potentialLink);
1819:
1820: if (camelCase != null) {
1821: // System.out.println("Buffer is "+buf);
1822:
1823: // System.out.println(" Replacing "+camelCase+" with proper link.");
1824: start = buf.toString().lastIndexOf(
1825: camelCase);
1826: buf.replace(start, start
1827: + camelCase.length(),
1828: makeCamelCaseLink(camelCase));
1829:
1830: // System.out.println(" Resulting with "+buf);
1831: } else {
1832: // System.out.println("Checking for potential URI: "+potentialLink);
1833: if (isExternalLink(potentialLink)) {
1834: // System.out.println("buf="+buf);
1835: start = buf.toString().lastIndexOf(
1836: potentialLink);
1837:
1838: if (start >= 0) {
1839: String link = readUntil(" \t()[]{}!\"'\n|");
1840:
1841: link = potentialLink + (char) ch
1842: + link; // Do not forget the start.
1843:
1844: // System.out.println("start="+start+", pl="+potentialLink);
1845:
1846: buf.replace(start, start
1847: + potentialLink.length(),
1848: makeDirectURILink(link));
1849:
1850: // System.out.println("Resulting with "+buf);
1851:
1852: ch = nextToken();
1853: }
1854: }
1855: }
1856:
1857: // We've ended a word boundary, so time to reset.
1858: word = null;
1859: } else {
1860: // This should only be appending letters and digits.
1861: word.append((char) ch);
1862: } // if end of word
1863: } // if word's not null
1864:
1865: // Always set the previous character to test for word starts.
1866: previousCh = ch;
1867:
1868: } // if m_camelCaseLinks
1869:
1870: //
1871: // An empty line stops a list
1872: //
1873: if (newLine && ch != '*' && ch != '#' && ch != ' '
1874: && m_genlistlevel > 0) {
1875: buf.append(unwindGeneralList());
1876: }
1877:
1878: if (newLine && ch != '|' && m_istable) {
1879: buf.append(m_renderer.closeTable());
1880: m_istable = false;
1881: m_closeTag = null;
1882: }
1883:
1884: //
1885: // Now, check the incoming token.
1886: //
1887: switch (ch) {
1888: case '\r':
1889: // DOS linefeeds we forget
1890: s = null;
1891: break;
1892:
1893: case '\n':
1894: //
1895: // Close things like headings, etc.
1896: //
1897: if (m_closeTag != null) {
1898: buf.append(m_closeTag);
1899: m_closeTag = null;
1900: }
1901:
1902: m_isdefinition = false;
1903:
1904: if (newLine) {
1905: // Paragraph change.
1906: buf.append(startBlockLevel());
1907:
1908: //
1909: // Figure out which elements cannot be enclosed inside
1910: // a <p></p> pair according to XHTML rules.
1911: //
1912: String nextLine = peekAheadLine();
1913: if (nextLine.length() == 0
1914: || (nextLine.length() > 0
1915: && !nextLine.startsWith("{{{")
1916: && !nextLine.startsWith("----")
1917: && !nextLine.startsWith("%%") && "*#!;"
1918: .indexOf(nextLine.charAt(0)) == -1)) {
1919: buf.append(m_renderer.openParagraph());
1920: m_isOpenParagraph = true;
1921: }
1922: } else {
1923: buf.append("\n");
1924: newLine = true;
1925: }
1926:
1927: break;
1928:
1929: case '\\':
1930: s = handleBackslash();
1931: break;
1932:
1933: case '_':
1934: s = handleUnderscore();
1935: break;
1936:
1937: case '\'':
1938: s = handleApostrophe();
1939: break;
1940:
1941: case '{':
1942: s = handleOpenbrace(newLine);
1943: break;
1944:
1945: case '}':
1946: s = handleClosebrace();
1947: break;
1948:
1949: case '-':
1950: s = handleDash();
1951: break;
1952:
1953: case '!':
1954: if (newLine) {
1955: s = handleHeading();
1956: } else {
1957: s = "!";
1958: }
1959: break;
1960:
1961: case ';':
1962: if (newLine) {
1963: s = handleDefinitionList();
1964: } else {
1965: s = ";";
1966: }
1967: break;
1968:
1969: case ':':
1970: if (m_isdefinition) {
1971: s = m_renderer.closeDefinitionTitle()
1972: + m_renderer.openDefinitionItem();
1973: m_isdefinition = false;
1974: } else {
1975: s = ":";
1976: }
1977: break;
1978:
1979: case '[':
1980: s = handleOpenbracket();
1981: break;
1982:
1983: case '*':
1984: if (newLine) {
1985: pushBack('*');
1986: s = handleGeneralList();
1987: } else {
1988: s = "*";
1989: }
1990: break;
1991:
1992: case '#':
1993: if (newLine) {
1994: pushBack('#');
1995: s = handleGeneralList();
1996: } else {
1997: s = "#";
1998: }
1999: break;
2000:
2001: case '|':
2002: s = handleBar(newLine);
2003: break;
2004:
2005: case '<':
2006: s = m_allowHTML ? "<" : "<";
2007: break;
2008:
2009: case '>':
2010: s = m_allowHTML ? ">" : ">";
2011: break;
2012:
2013: case '\"':
2014: s = m_allowHTML ? "\"" : """;
2015: break;
2016:
2017: /*
2018: case '&':
2019: s = "&";
2020: break;
2021: */
2022: case '~':
2023: s = handleTilde();
2024: break;
2025:
2026: case '%':
2027: s = handleDiv(newLine);
2028: break;
2029:
2030: case -1:
2031: if (m_closeTag != null) {
2032: buf.append(m_closeTag);
2033: m_closeTag = null;
2034: }
2035: quitReading = true;
2036: break;
2037:
2038: default:
2039: buf.append((char) ch);
2040: newLine = false;
2041: break;
2042: }
2043:
2044: if (s != null) {
2045: buf.append(s);
2046: newLine = false;
2047: }
2048:
2049: }
2050:
2051: m_data = new StringReader(buf.toString());
2052: }
2053:
2054: public int read() throws IOException {
2055: int val = m_data.read();
2056:
2057: if (val == -1) {
2058: fillBuffer();
2059: val = m_data.read();
2060:
2061: if (val == -1) {
2062: m_data = new StringReader(closeAll());
2063:
2064: val = m_data.read();
2065: }
2066: }
2067:
2068: return val;
2069: }
2070:
2071: public int read(char[] buf, int off, int len) throws IOException {
2072: return m_data.read(buf, off, len);
2073: }
2074:
2075: public boolean ready() throws IOException {
2076: log.debug("ready ? " + m_data.ready());
2077: if (!m_data.ready()) {
2078: fillBuffer();
2079: }
2080:
2081: return m_data.ready();
2082: }
2083:
2084: public void close() {
2085: }
2086:
2087: /**
2088: * All HTML output stuff is here. This class is a helper class, and will
2089: * be spawned later on with a proper API of its own so that we can have
2090: * different kinds of renderers.
2091: */
2092:
2093: // FIXME: Not everything is yet, and in the future this class will be spawned
2094: // out to be its own class.
2095: private class HTMLRenderer extends TextRenderer {
2096: private boolean m_isPreBlock = false;
2097: private TranslatorReader m_cleanTranslator;
2098:
2099: /*
2100: FIXME: It's relatively slow to create two TranslatorReaders each time.
2101: */
2102: public HTMLRenderer() {
2103: }
2104:
2105: /**
2106: * Does a lazy init. Otherwise, we would get into a situation
2107: * where HTMLRenderer would try and boot a TranslatorReader before
2108: * the TranslatorReader it is contained by is up.
2109: */
2110: private TranslatorReader getCleanTranslator() {
2111: if (m_cleanTranslator == null) {
2112: WikiContext dummyContext = new WikiContext(m_engine,
2113: m_context.getPage());
2114: m_cleanTranslator = new TranslatorReader(dummyContext,
2115: null, new TextRenderer());
2116: m_cleanTranslator.m_allowHTML = true;
2117: }
2118:
2119: return m_cleanTranslator;
2120: }
2121:
2122: public void doChar(StringBuffer buf, char ch) {
2123: if (ch == '<') {
2124: buf.append("<");
2125: } else if (ch == '>') {
2126: buf.append(">");
2127: } else if (ch == '&') {
2128: buf.append("&");
2129: } else {
2130: buf.append(ch);
2131: }
2132: }
2133:
2134: public String openDiv(String style, String clazz) {
2135: StringBuffer sb = new StringBuffer();
2136:
2137: sb.append("<div");
2138: sb.append(style != null ? " style=\"" + style + "\"" : "");
2139: sb.append(clazz != null ? " class=\"" + clazz + "\"" : "");
2140: sb.append(">");
2141:
2142: return sb.toString();
2143: }
2144:
2145: public String openSpan(String style, String clazz) {
2146: StringBuffer sb = new StringBuffer();
2147:
2148: sb.append("<span");
2149: sb.append(style != null ? " style=\"" + style + "\"" : "");
2150: sb.append(clazz != null ? " class=\"" + clazz + "\"" : "");
2151: sb.append(">");
2152:
2153: return sb.toString();
2154: }
2155:
2156: public String closeDiv() {
2157: return "</div>";
2158: }
2159:
2160: public String closeSpan() {
2161: return "</span>";
2162: }
2163:
2164: public String openParagraph() {
2165: return "<p>";
2166: }
2167:
2168: public String closeParagraph() {
2169: return "</p>\n";
2170: }
2171:
2172: /**
2173: * Writes out a text effect
2174: */
2175: public String openTextEffect(int effect) {
2176: switch (effect) {
2177: case BOLD:
2178: return "<b>";
2179: case ITALIC:
2180: return "<i>";
2181: case TYPED:
2182: return "<tt>";
2183: }
2184:
2185: return "";
2186: }
2187:
2188: public String closeTextEffect(int effect) {
2189: switch (effect) {
2190: case BOLD:
2191: return "</b>";
2192: case ITALIC:
2193: return "</i>";
2194: case TYPED:
2195: return "</tt>";
2196: }
2197:
2198: return "";
2199: }
2200:
2201: public String openDefinitionItem() {
2202: return "<dd>";
2203: }
2204:
2205: public String closeDefinitionItem() {
2206: return "</dd>\n";
2207: }
2208:
2209: public String openDefinitionTitle() {
2210: return "<dt>";
2211: }
2212:
2213: public String closeDefinitionTitle() {
2214: return "</dt>";
2215: }
2216:
2217: public String openDefinitionList() {
2218: return "<dl>\n";
2219: }
2220:
2221: public String closeDefinitionList() {
2222: return "</dl>";
2223: }
2224:
2225: /**
2226: * Write a HTMLized link depending on its type.
2227: *
2228: * <p>This jsut calls makeLink() with "section" set to null.
2229: */
2230: public String makeLink(int type, String link, String text) {
2231: return makeLink(type, link, text, null);
2232: }
2233:
2234: private final String getURL(String context, String link) {
2235: return m_context.getURL(context, link, null);
2236: }
2237:
2238: /**
2239: * Write a HTMLized link depending on its type.
2240: *
2241: * @param type Type of the link.
2242: * @param link The actual link.
2243: * @param text The user-visible text for the link.
2244: * @param section Which named anchor to point to. This may not have any
2245: * effect on certain link types. If null, will ignore it.
2246: */
2247: public String makeLink(int type, String link, String text,
2248: String section) {
2249: String result;
2250:
2251: if (text == null)
2252: text = link;
2253:
2254: section = (section != null) ? ("#" + section) : "";
2255:
2256: // Make sure we make a link name that can be accepted
2257: // as a valid URL.
2258:
2259: String encodedlink = m_engine.encodeName(link);
2260:
2261: if (encodedlink.length() == 0) {
2262: type = EMPTY;
2263: }
2264:
2265: switch (type) {
2266: case READ:
2267: result = "<a class=\"wikipage\" href=\""
2268: + getURL(WikiContext.VIEW, link) + section
2269: + "\">" + text + "</a>";
2270: break;
2271:
2272: case EDIT:
2273: result = "<a class=\"createpage\" title=\"Create '"
2274: + link + "'\" href=\""
2275: + getURL(WikiContext.EDIT, link) + "\">" + text
2276: + "</a>";
2277: break;
2278:
2279: case EMPTY:
2280: result = "<u>" + text + "</u>";
2281: break;
2282:
2283: //
2284: // These two are for local references - footnotes and
2285: // references to footnotes.
2286: // We embed the page name (or whatever WikiContext gives us)
2287: // to make sure the links are unique across Wiki.
2288: //
2289: case LOCALREF:
2290: result = "<a class=\"footnoteref\" href=\"#ref-"
2291: + m_context.getPage().getName() + "-" + link
2292: + "\">[" + text + "]</a>";
2293: break;
2294:
2295: case LOCAL:
2296: result = "<a class=\"footnote\" name=\"ref-"
2297: + m_context.getPage().getName() + "-"
2298: + link.substring(1) + "\">[" + text + "]</a>";
2299: break;
2300:
2301: //
2302: // With the image, external and interwiki types we need to
2303: // make sure nobody can put in Javascript or something else
2304: // annoying into the links themselves. We do this by preventing
2305: // a haxor from stopping the link name short with quotes in
2306: // fillBuffer().
2307: //
2308: case IMAGE:
2309: result = "<img class=\"inline\" src=\"" + link
2310: + "\" alt=\"" + text + "\" />";
2311: break;
2312:
2313: case IMAGELINK:
2314: result = "<a href=\"" + text
2315: + "\"><img class=\"inline\" src=\"" + link
2316: + "\" alt=\"" + text + "\"/></a>";
2317: break;
2318:
2319: case IMAGEWIKILINK:
2320: String pagelink = getURL(WikiContext.VIEW, text);
2321: result = "<a class=\"wikipage\" href=\"" + pagelink
2322: + "\"><img class=\"inline\" src=\"" + link
2323: + "\" alt=\"" + text + "\" /></a>";
2324: break;
2325:
2326: case EXTERNAL:
2327: result = "<a class=\"external\" "
2328: + (m_useRelNofollow ? "rel=\"nofollow\" " : "")
2329: + "href=\"" + link + section + "\">" + text
2330: + "</a>";
2331: break;
2332:
2333: case INTERWIKI:
2334: result = "<a class=\"interwiki\" href=\"" + link
2335: + section + "\">" + text + "</a>";
2336: break;
2337:
2338: case ATTACHMENT:
2339: String attlink = getURL(WikiContext.ATTACH, link);
2340:
2341: String infolink = getURL(WikiContext.INFO, link);
2342:
2343: String imglink = getURL(WikiContext.NONE,
2344: "images/attachment_small.png");
2345:
2346: result = "<a class=\"attachment\" href=\"" + attlink
2347: + "\">" + text + "</a>" + "<a href=\""
2348: + infolink + "\"><img src=\"" + imglink
2349: + "\" alt=\"(info)\"/></a>";
2350: break;
2351:
2352: default:
2353: result = "";
2354: break;
2355: }
2356:
2357: return result;
2358: }
2359:
2360: /**
2361: * Writes HTML for error message.
2362: */
2363:
2364: public String makeError(String error) {
2365: return "<span class=\"error\">" + error + "</span>";
2366: }
2367:
2368: /**
2369: * Emits a vertical line.
2370: */
2371:
2372: public String makeRuler() {
2373: return "<hr />";
2374: }
2375:
2376: /**
2377: * Modifies the "hd" parameter to contain proper values. Because
2378: * an "id" tag may only contain [a-zA-Z0-9:_-], we'll replace the
2379: * % after url encoding with '_'.
2380: */
2381: private String makeHeadingAnchor(String baseName, String title,
2382: Heading hd) {
2383: hd.m_titleText = title;
2384: title = cleanLink(title);
2385: hd.m_titleSection = m_engine.encodeName(title);
2386: hd.m_titleAnchor = "section-"
2387: + m_engine.encodeName(baseName) + "-"
2388: + hd.m_titleSection;
2389:
2390: hd.m_titleAnchor = hd.m_titleAnchor.replace('%', '_');
2391: return hd.m_titleAnchor;
2392: }
2393:
2394: private String makeSectionTitle(String title) {
2395: title = title.trim();
2396:
2397: StringWriter outTitle = new StringWriter();
2398:
2399: try {
2400: TranslatorReader read = getCleanTranslator();
2401: read.setInputReader(new StringReader(title));
2402: FileUtil.copyContents(read, outTitle);
2403: } catch (IOException e) {
2404: log.fatal("CleanTranslator not working", e);
2405: throw new InternalWikiException(
2406: "CleanTranslator not working as expected, when cleaning title"
2407: + e.getMessage());
2408: }
2409:
2410: return outTitle.toString();
2411: }
2412:
2413: /**
2414: * Returns XHTML for the start of the heading. Also sets the
2415: * line-end emitter.
2416: * @param level
2417: * @param headings A List to which heading should be added.
2418: */
2419: public String makeHeading(int level, String title, Heading hd) {
2420: String res = "";
2421:
2422: String pageName = m_context.getPage().getName();
2423:
2424: String outTitle = makeSectionTitle(title);
2425:
2426: hd.m_level = level;
2427:
2428: switch (level) {
2429: case Heading.HEADING_SMALL:
2430: res = "<h4 id='"
2431: + makeHeadingAnchor(pageName, outTitle, hd)
2432: + "'>";
2433: m_closeTag = "</h4>";
2434: break;
2435:
2436: case Heading.HEADING_MEDIUM:
2437: res = "<h3 id='"
2438: + makeHeadingAnchor(pageName, outTitle, hd)
2439: + "'>";
2440: m_closeTag = "</h3>";
2441: break;
2442:
2443: case Heading.HEADING_LARGE:
2444: res = "<h2 id='"
2445: + makeHeadingAnchor(pageName, outTitle, hd)
2446: + "'>";
2447: m_closeTag = "</h2>";
2448: break;
2449: }
2450:
2451: return res;
2452: }
2453:
2454: /**
2455: * @param bullet A character detailing which kind of a list
2456: * we are dealing with here. Options are '#' and '*'.
2457: */
2458: public String openList(char bullet) {
2459: String res = "";
2460:
2461: if (bullet == '#')
2462: res = "<ol>\n";
2463: else if (bullet == '*')
2464: res = "<ul>\n";
2465: else
2466: log.info("Warning: unknown bullet character '" + bullet
2467: + "' at (+)");
2468:
2469: return res;
2470: }
2471:
2472: public String openListItem() {
2473: return "<li>";
2474: }
2475:
2476: public String closeListItem() {
2477: return "</li>\n";
2478: }
2479:
2480: /**
2481: * @param bullet A character detailing which kind of a list
2482: * we are dealing with here. Options are '#' and '*'.
2483: */
2484: public String closeList(char bullet) {
2485: String res = "";
2486:
2487: if (bullet == '#') {
2488: res = "</ol>\n";
2489: } else if (bullet == '*') {
2490: res = "</ul>\n";
2491: } else {
2492: //FIXME unknown character -> error
2493: log.info("Warning: unknown character in unwind '"
2494: + bullet + "'");
2495: }
2496:
2497: return res;
2498: }
2499:
2500: public String openTable() {
2501: return "<table class=\"wikitable\" border=\"1\">\n";
2502: }
2503:
2504: public String closeTable() {
2505: return "</table>\n";
2506: }
2507:
2508: public String openTableRow() {
2509: return "<tr>";
2510: }
2511:
2512: public String closeTableRow() {
2513: return "</tr>";
2514: }
2515:
2516: public String openTableItem() {
2517: return "<td>";
2518: }
2519:
2520: public String closeTableItem() {
2521: return "</td>";
2522: }
2523:
2524: public String openTableHeading() {
2525: return "<th>";
2526: }
2527:
2528: public String closeTableHeading() {
2529: return "</th>";
2530: }
2531:
2532: public String openPreformatted(boolean isBlock) {
2533: m_isPreBlock = isBlock;
2534:
2535: if (isBlock) {
2536: return "<pre>";
2537: }
2538:
2539: return "<span style=\"font-family:monospace; whitespace:pre;\">";
2540: }
2541:
2542: public String closePreformatted() {
2543: if (m_isPreBlock)
2544: return "</pre>\n";
2545:
2546: return "</span>";
2547: }
2548:
2549: /**
2550: * If outlink images are turned on, returns a link to the outward
2551: * linking image.
2552: */
2553: public String outlinkImage() {
2554: if (m_useOutlinkImage) {
2555: return "<img class=\"outlink\" src=\""
2556: + getURL(WikiContext.NONE, "images/out.png")
2557: + "\" alt=\"\" />";
2558: }
2559:
2560: return "";
2561: }
2562:
2563: /**
2564: * @param clear If true, then flushes all thingies.
2565: */
2566: public String lineBreak(boolean clear) {
2567: if (clear)
2568: return "<br clear=\"all\" />";
2569:
2570: return "<br />";
2571: }
2572:
2573: } // HTMLRenderer
2574:
2575: /**
2576: * A very simple class for outputting plain text with no
2577: * formatting.
2578: */
2579: public class TextRenderer {
2580: public TextRenderer() {
2581: }
2582:
2583: public void doChar(StringBuffer buf, char ch) {
2584: buf.append(ch);
2585: }
2586:
2587: public String openDiv(String style, String clazz) {
2588: return "";
2589: }
2590:
2591: public String closeDiv() {
2592: return "";
2593: }
2594:
2595: public String openSpan(String style, String clazz) {
2596: return "";
2597: }
2598:
2599: public String closeSpan() {
2600: return "";
2601: }
2602:
2603: public String openParagraph() {
2604: return "";
2605: }
2606:
2607: public String closeParagraph() {
2608: return "\n\n";
2609: }
2610:
2611: /**
2612: * Writes out a text effect
2613: */
2614: public String openTextEffect(int effect) {
2615: return "";
2616: }
2617:
2618: public String closeTextEffect(int effect) {
2619: return "";
2620: }
2621:
2622: public String openDefinitionItem() {
2623: return " : ";
2624: }
2625:
2626: public String closeDefinitionItem() {
2627: return "\n";
2628: }
2629:
2630: public String openDefinitionTitle() {
2631: return "";
2632: }
2633:
2634: public String closeDefinitionTitle() {
2635: return "";
2636: }
2637:
2638: public String openDefinitionList() {
2639: return "";
2640: }
2641:
2642: public String closeDefinitionList() {
2643: return "\n";
2644: }
2645:
2646: /**
2647: * Write a HTMLized link depending on its type.
2648: *
2649: * <p>This jsut calls makeLink() with "section" set to null.
2650: */
2651: public String makeLink(int type, String link, String text) {
2652: return text;
2653: }
2654:
2655: public String makeLink(int type, String link, String text,
2656: String section) {
2657: return text;
2658: }
2659:
2660: /**
2661: * Writes HTML for error message.
2662: */
2663:
2664: public String makeError(String error) {
2665: return "ERROR: " + error;
2666: }
2667:
2668: /**
2669: * Emits a vertical line.
2670: */
2671:
2672: public String makeRuler() {
2673: return "----------------------------------";
2674: }
2675:
2676: /**
2677: * Returns XHTML for the start of the heading. Also sets the
2678: * line-end emitter.
2679: * @param level
2680: */
2681: public String makeHeading(int level, String title, Heading hd) {
2682: String res = "";
2683:
2684: title = title.trim();
2685:
2686: hd.m_level = level;
2687: hd.m_titleText = title;
2688: hd.m_titleSection = "";
2689: hd.m_titleAnchor = "";
2690:
2691: switch (level) {
2692: case Heading.HEADING_SMALL:
2693: res = title;
2694: m_closeTag = "\n\n";
2695: break;
2696:
2697: case Heading.HEADING_MEDIUM:
2698: res = title;
2699: m_closeTag = "\n"
2700: + TextUtil.repeatString("-", title.length())
2701: + "\n\n";
2702: break;
2703:
2704: case Heading.HEADING_LARGE:
2705: res = title.toUpperCase();
2706: m_closeTag = "\n"
2707: + TextUtil.repeatString("=", title.length())
2708: + "\n\n";
2709: break;
2710: }
2711:
2712: return res;
2713: }
2714:
2715: /**
2716: * @param bullet A character detailing which kind of a list
2717: * we are dealing with here. Options are '#' and '*'.
2718: */
2719: // FIXME: Should really start a different kind of list depending
2720: // on the bullet type
2721: public String openList(char bullet) {
2722: return "\n";
2723: }
2724:
2725: public String openListItem() {
2726: return "- ";
2727: }
2728:
2729: public String closeListItem() {
2730: return "\n";
2731: }
2732:
2733: /**
2734: * @param bullet A character detailing which kind of a list
2735: * we are dealing with here. Options are '#' and '*'.
2736: */
2737: public String closeList(char bullet) {
2738: return "\n\n";
2739: }
2740:
2741: public String openTable() {
2742: return "\n";
2743: }
2744:
2745: public String closeTable() {
2746: return "\n";
2747: }
2748:
2749: public String openTableRow() {
2750: return "";
2751: }
2752:
2753: public String closeTableRow() {
2754: return "\n";
2755: }
2756:
2757: public String openTableItem() {
2758: return "\t";
2759: }
2760:
2761: public String closeTableItem() {
2762: return "";
2763: }
2764:
2765: public String openTableHeading() {
2766: return "\t";
2767: }
2768:
2769: public String closeTableHeading() {
2770: return "";
2771: }
2772:
2773: public String openPreformatted(boolean isBlock) {
2774: return "";
2775: }
2776:
2777: public String closePreformatted() {
2778: return "\n";
2779: }
2780:
2781: /**
2782: * If outlink images are turned on, returns a link to the outward
2783: * linking image.
2784: */
2785: public String outlinkImage() {
2786: return "";
2787: }
2788:
2789: /**
2790: * @param clear If true, then flushes all thingies.
2791: */
2792: public String lineBreak(boolean clear) {
2793: return "\n";
2794: }
2795:
2796: } // TextRenderer
2797: }
|