Source Code Cross Referenced for TranslatorReader.java in  » Wiki-Engine » JSPWiki » com » ecyrd » jspwiki » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Wiki Engine » JSPWiki » com.ecyrd.jspwiki 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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 ] -&gt; 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 ? "<" : "&lt;";
2007:                        break;
2008:
2009:                    case '>':
2010:                        s = m_allowHTML ? ">" : "&gt;";
2011:                        break;
2012:
2013:                    case '\"':
2014:                        s = m_allowHTML ? "\"" : "&quot;";
2015:                        break;
2016:
2017:                    /*
2018:                    case '&':
2019:                    s = "&amp;";
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("&lt;");
2125:                    } else if (ch == '>') {
2126:                        buf.append("&gt;");
2127:                    } else if (ch == '&') {
2128:                        buf.append("&amp;");
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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.