android-ibc-forum

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 07176d79cb0d5d43ce17c1e1cb5d103f1d7e8490
parent 5477820adcf51875a413bf8fa01fbf1fc7c4f789
Author: Jan Dankert <devnull@localhost>
Date:   Tue,  7 Feb 2012 00:51:40 +0100

Den russischen BB-Code-Parser entfernt und durch eine Klasse aus JavaBB ersetzt.

Diffstat:
src/de/mtbnews/android/TopicActivity.java | 70+++++++++++++++++++++++++++++++++++-----------------------------------
src/de/mtbnews/android/adapter/ListEntryContentAdapter.java | 37+++++++++++++++++++++++++++++++------
src/de/mtbnews/android/adapter/ProcessBBCode.java | 547+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/org/xmlrpc/android/XMLRPCClient.java | 2--
src/ru/perm/kefir/bbcode/AbstractCode.java | 71-----------------------------------------------------------------------
src/ru/perm/kefir/bbcode/BBProcessor.java | 114-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/BBProcessorFactory.java | 86-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/Code.java | 72------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/Configuration.java | 138-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/ConfigurationFactory.java | 885-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/Constant.java | 91-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/ConstantCode.java | 44--------------------------------------------
src/ru/perm/kefir/bbcode/Context.java | 248-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/EscapeProcessor.java | 86-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/EscapeXmlProcessorFactory.java | 59-----------------------------------------------------------
src/ru/perm/kefir/bbcode/IntSet.java | 94-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/NamedElement.java | 39---------------------------------------
src/ru/perm/kefir/bbcode/NamedValue.java | 26--------------------------
src/ru/perm/kefir/bbcode/Pattern.java | 74--------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/PatternElement.java | 35-----------------------------------
src/ru/perm/kefir/bbcode/Scope.java | 145-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/Source.java | 208-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/Template.java | 44--------------------------------------------
src/ru/perm/kefir/bbcode/TemplateElement.java | 14--------------
src/ru/perm/kefir/bbcode/Text.java | 91-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/TextProcessor.java | 44--------------------------------------------
src/ru/perm/kefir/bbcode/TextProcessorAdapter.java | 53-----------------------------------------------------
src/ru/perm/kefir/bbcode/TextProcessorChain.java | 35-----------------------------------
src/ru/perm/kefir/bbcode/TextProcessorFactory.java | 16----------------
src/ru/perm/kefir/bbcode/TextProcessorFactoryException.java | 24------------------------
src/ru/perm/kefir/bbcode/Util.java | 38--------------------------------------
src/ru/perm/kefir/bbcode/Variable.java | 115-------------------------------------------------------------------------------
src/ru/perm/kefir/bbcode/default.xml | 237-------------------------------------------------------------------------------
33 files changed, 613 insertions(+), 3269 deletions(-)

diff --git a/src/de/mtbnews/android/TopicActivity.java b/src/de/mtbnews/android/TopicActivity.java @@ -41,7 +41,7 @@ public class TopicActivity extends EndlessListActivity<Post> private String topicTitle; private int totalSize; - + @Override protected void onCreate(Bundle savedInstanceState) { @@ -53,7 +53,7 @@ public class TopicActivity extends EndlessListActivity<Post> setContentView(R.layout.listing); ListAdapter adapter = new ListEntryContentAdapter(TopicActivity.this, - entries); + entries, true, false); setListAdapter(adapter); initialLoad(); @@ -152,49 +152,49 @@ public class TopicActivity extends EndlessListActivity<Post> return true; case R.id.menu_subscribe: - + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.subscribe_topic); builder.setItems(R.array.subscription_modes, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, - final int item) - { - new ServerAsyncTask(TopicActivity.this, - R.string.mark_topic_read) { - - @Override - protected void callServer() - throws IOException + public void onClick(DialogInterface dialog, + final int item) { - TapatalkClient client = ((IBCApplication) getApplication()) - .getTapatalkClient(); - try - { - client.subscribeTopic(topicId, - item - 1); - } - catch (TapatalkException e) + new ServerAsyncTask(TopicActivity.this, + R.string.mark_topic_read) { - throw new RuntimeException(e); - } - } - - @Override - protected void doOnSuccess() - { - Toast.makeText(getApplicationContext(), - R.string.subscription_saved, - Toast.LENGTH_SHORT).show(); + + @Override + protected void callServer() + throws IOException + { + TapatalkClient client = ((IBCApplication) getApplication()) + .getTapatalkClient(); + try + { + client.subscribeTopic(topicId, + item - 1); + } + catch (TapatalkException e) + { + throw new RuntimeException(e); + } + } + + @Override + protected void doOnSuccess() + { + Toast.makeText(getApplicationContext(), + R.string.subscription_saved, + Toast.LENGTH_SHORT).show(); + } + }.execute(); } - }.execute(); - } - }); + }); builder.create().show(); return true; - + case R.id.menu_mark_read: new ServerAsyncTask(TopicActivity.this, diff --git a/src/de/mtbnews/android/adapter/ListEntryContentAdapter.java b/src/de/mtbnews/android/adapter/ListEntryContentAdapter.java @@ -5,8 +5,6 @@ package de.mtbnews.android.adapter; import java.util.List; -import ru.perm.kefir.bbcode.BBProcessorFactory; -import ru.perm.kefir.bbcode.TextProcessor; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; @@ -41,6 +39,9 @@ public class ListEntryContentAdapter extends BaseAdapter private List<? extends ListEntry> list; + private boolean containsBBCode; + private boolean containsHtml; + public ListEntryContentAdapter(Context context, List<? extends ListEntry> list) { @@ -50,6 +51,18 @@ public class ListEntryContentAdapter extends BaseAdapter this.list = list; } + public ListEntryContentAdapter(Context context, + List<? extends ListEntry> list, boolean containsBBCode, + boolean containsHtml) + { + mContext = context; + inflator = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + this.list = list; + this.containsBBCode = containsBBCode; + this.containsHtml = containsHtml; + } + public int getCount() { return list.size(); @@ -137,13 +150,25 @@ public class ListEntryContentAdapter extends BaseAdapter { SharedPreferences prefs = ((IBCApplication) ((Activity) mContext) .getApplication()).prefs; - if (prefs.getBoolean("parse_bbcode", false)) + if (containsBBCode && prefs.getBoolean("parse_bbcode", false)) { + String html; + // TextProcessor create = BBProcessorFactory.getInstance() + // .create(); + // html = create.process(e.getContent()); + html = e.getContent(); + html = new ProcessBBCode().preparePostText(html); - TextProcessor create = BBProcessorFactory.getInstance() - .create(); - CharSequence html = create.process(e.getContent()); + ImageGetter imageGetter = null; + if (prefs.getBoolean("load_images", false)) + imageGetter = new ImageGetter(); + viewHolder.desc.setText(Html.fromHtml(html.toString(), + imageGetter, null)); + } + else if (containsHtml) + { + final CharSequence html = e.getContent(); ImageGetter imageGetter = null; if (prefs.getBoolean("load_images", false)) imageGetter = new ImageGetter(); diff --git a/src/de/mtbnews/android/adapter/ProcessBBCode.java b/src/de/mtbnews/android/adapter/ProcessBBCode.java @@ -0,0 +1,547 @@ +package de.mtbnews.android.adapter; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/* + * Copyright 2004 JavaFree.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * $Id: ProcessBBCode.java,v 1.18.2.2.4.5 2008/10/07 23:06:16 daltoncamargo Exp + * $ + * + * @author Dalton Camargo - <a href="mailto:dalton@javabb.org">dalton@javabb.org + * </a> <br> + * @author Ronald Tetsuo Miura + */ +public class ProcessBBCode implements Serializable +{ + /** + * + */ + private static final long serialVersionUID = 1L; + + private static final String CR_LF = "(?:\r\n|\r|\n)?"; + + /** */ + private boolean acceptHTML = false; + + /** */ + private boolean acceptBBCode = true; + + /** + * @return acceptBBCode + */ + public boolean getAcceptBBCode() + { + return acceptBBCode; + } + + /** + * @param acceptBBCode + * the new acceptBBCode value + */ + public void setAcceptBBCode(boolean acceptBBCode) + { + this.acceptBBCode = acceptBBCode; + } + + /** + * @return htmlAccepted + */ + public boolean getAcceptHTML() + { + return acceptHTML; + } + + /** + * @param acceptHTML + * the new acceptHTML value + */ + public void setAcceptHTML(boolean acceptHTML) + { + this.acceptHTML = acceptHTML; + } + + /** + * @param texto + * @return TODO unuseful parameters. + */ + public String preparePostText(String texto) + { + if (!getAcceptHTML()) + { + texto = escapeHtml(texto); + } + if (getAcceptBBCode()) + { + texto = process(texto); + } + return texto; + } + + /** + * @param string + * @return HTML-formated message + */ + private String process(String string) + { + string = string.replaceAll("(\r\n|\n\r|\n|\r)", "<br>"); + + StringBuffer buffer = new StringBuffer(string); + processCode(buffer); + + /* + * processNestedTags( buffer, "quote", + * "<table width='90%' cellspacing='1' cellpadding='1' border='0' " + + * "align='center'><tr><td><span class='genmed'><b>{BBCODE_PARAM}:</b></span></td></tr></table><table width='90%' cellspacing='1' cellpadding='1' border='0' align='center'><tr><td class='quote' bgcolor='#F3F5FF'>" + * , " &nbsp;</td></tr></table>", + * "<table width='90%' cellspacing='1' cellpadding='3' border='0' " + + * "align='center'><tr><td><span class='genmed'><b>Quote:</b></span></td></tr><tr><td class='quote'>" + * , " &nbsp;</td></tr></table>", "[*]", false, true, true); + */ + + processNestedTags( + buffer, + "quote", + "</p><blockquote><div class='term'><b>{BBCODE_PARAM}:</b></div><div class='definition'><div><div>", + "</div></div></div></blockquote><p>", + "</p><blockquote><div class='term'><b>Quote:</b></div><div class='definition'><div><div>", + " </div></div></div></blockquote><p>", "[*]", false, true, true); + + processNestedTags(buffer, "list", "<ol type=\"{BBCODE_PARAM}\">", + "</ol>", "<ul>", "</ul>", "<li>", true, true, true); + + String str = buffer.toString(); + + // str = str.replaceAll("(\r\n|\n\r|\n|\r)", "<br>"); + + // [color] + str = str.replaceAll( + "\\[color=['\"]?(.*?[^'\"])['\"]?\\](.*?)\\[/color\\]", + "<span style='color:$1'>$2</span>"); + + // [size] + str = str.replaceAll( + "\\[size=['\"]?([0-9]|[1-2][0-9])['\"]?\\](.*?)\\[/size\\]", + "<span style='font-size:$1px'>$2</span>"); + + // [b][u][i] + str = str.replaceAll("\\[b\\](.*?)\\[/b\\]", "<b>$1</b>"); + str = str.replaceAll("\\[u\\](.*?)\\[/u\\]", "<u>$1</u>"); + str = str.replaceAll("\\[i\\](.*?)\\[/i\\]", "<i>$1</i>"); + + // [img] + str = str.replaceAll("\\[img\\](.*?)\\[/img\\]", + "<img src='$1' border='0' alt='0'>"); + + // [url] + str = str.replaceAll("\\[url\\](.*?)\\[/url\\]", + "<a href='$1' target='_blank'>$1</a>"); + str = str.replaceAll( + "\\[url=['\"]?(.*?[^'\"])['\"]?\\](.*?)\\[/url\\]", + "<a href=\"$1\" target=\"_new\">$2</a>"); + + // [email] + str = str.replaceAll("\\[email\\](.*?)\\[/email\\]", + "<a href='mailto:$1'>$1</a>"); + + return str; + } + + private static void processCode(StringBuffer buffer) + { + int start = buffer.indexOf("[code]"); + int end; + + for (; (start >= 0) && (start < buffer.length()); start = buffer + .indexOf("[code]", end)) + { + + end = buffer.indexOf("[/code]", start); + + if (end < 0) + { + break; + } + + end += "[/code]".length(); + + String content = buffer.substring(start + "[code]".length(), end + - "[/code]".length()); + content = escapeBBcode(content); + + /* + * String replacement = "<!-- [ -code- ] --></span>" // + + * "<table width='90%' cellspacing='1' cellpadding='3' border='0' align='center'>" + * + "<tr><td><span class='genmed'><b>Code:</b></span></td></tr>" + + * "<tr><td class='code'>" + content + + * "</td></tr></table><span class='postbody'><!-- [/ -code- ] -->"; + */ + + content = content.replaceAll("<br>", "\n"); + + String replacement = "<!-- [ -code- ] -->" // + + "<textarea name=\"code\" id=\"code\" class=\"java:nocontrols:nogutter\" rows=\"15\" cols=\"100\">" + + content + "</textarea><!-- [/ -code- ] -->"; + + /* + * String replacement = "</span>" // + + * "<!-- [ -code- ] --><pre class=\"brush: java;\">" + content + + * "</pre><span class='postbody'><!-- [/ -code- ] -->"; + */ + + buffer.replace(start, end, replacement); + + end = start + replacement.length(); + } + } + + /** + * @param content + * @return - + */ + public static String escapeBBcode(String content) + { + // escaping single characters + content = replaceAll(content, "[]\t".toCharArray(), new String[] { + "&#91;", "&#93;", "&nbsp; &nbsp;" }); + + // taking off start and end line breaks + content = content.replaceAll("\\A\r\n|\\A\r|\\A\n|\r\n\\z|\r\\z|\n\\z", + ""); + + // replacing spaces for &nbsp; to keep indentation + content = content.replaceAll(" ", "&nbsp; "); + content = content.replaceAll(" ", " &nbsp;"); + + return content; + } + + /** + * @param content + * @return - + */ + private static String escapeHtml(String content) + { + // escaping single characters + content = replaceAll(content, "&<>".toCharArray(), new String[] { + "&amp;", "&lt;", "&gt;" }); + + // replacing line breaks for <br> + // content = content.replaceAll("\r\n", "<br>"); + // content = replaceAll(content, "\n\r".toCharArray(), new String[] { + // "<br>", "<br>" }); + + return content; + } + + private static String replaceAll(String str, char[] chars, + String[] replacement) + { + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + boolean matched = false; + for (int j = 0; j < chars.length; j++) + { + if (c == chars[j]) + { + buffer.append(replacement[j]); + matched = true; + } + } + if (!matched) + { + buffer.append(c); + } + } + return buffer.toString(); + } + + /** + * @param buffer + * @param tagName + * @param openSubstWithParam + * @param closeSubstWithParam + * @param openSubstWithoutParam + * @param closeSubstWithoutParam + * @param internalSubst + * @param processInternalTags + * @param acceptParam + * @param requiresQuotedParam + */ + private static void processNestedTags(StringBuffer buffer, String tagName, + String openSubstWithParam, String closeSubstWithParam, + String openSubstWithoutParam, String closeSubstWithoutParam, + String internalSubst, boolean processInternalTags, + boolean acceptParam, boolean requiresQuotedParam) + { + String str = buffer.toString(); + + Stack openStack = new Stack(); + Set subsOpen = new HashSet(); + Set subsClose = new HashSet(); + Set subsInternal = new HashSet(); + + String openTag = CR_LF + + "\\[" + + tagName + + (acceptParam ? (requiresQuotedParam ? "(?:=\"(.*?)\")?" + : "(?:=\"?(.*?)\"?)?") : "") + "\\]" + CR_LF; + String closeTag = CR_LF + "\\[/" + tagName + "\\]" + CR_LF; + String internTag = CR_LF + "\\[\\*\\]" + CR_LF; + + String patternString = "(" + openTag + ")|(" + closeTag + ")"; + + if (processInternalTags) + { + patternString += "|(" + internTag + ")"; + } + + Pattern tagsPattern = Pattern.compile(patternString); + Matcher matcher = tagsPattern.matcher(str); + + int openTagGroup; + int paramGroup; + int closeTagGroup; + int internalTagGroup; + + if (acceptParam) + { + openTagGroup = 1; + paramGroup = 2; + closeTagGroup = 3; + internalTagGroup = 4; + } + else + { + openTagGroup = 1; + paramGroup = -1; // INFO + closeTagGroup = 2; + internalTagGroup = 3; + } + + while (matcher.find()) + { + int length = matcher.end() - matcher.start(); + MutableCharSequence matchedSeq = new MutableCharSequence(str, + matcher.start(), length); + + // test opening tags + if (matcher.group(openTagGroup) != null) + { + if (acceptParam && (matcher.group(paramGroup) != null)) + { + matchedSeq.param = matcher.group(paramGroup); + } + + openStack.push(matchedSeq); + + // test closing tags + } + else if ((matcher.group(closeTagGroup) != null) + && !openStack.isEmpty()) + { + MutableCharSequence openSeq = (MutableCharSequence) openStack + .pop(); + + if (acceptParam) + { + matchedSeq.param = openSeq.param; + } + + subsOpen.add(openSeq); + subsClose.add(matchedSeq); + + // test internal tags + } + else if (processInternalTags + && (matcher.group(internalTagGroup) != null) + && (!openStack.isEmpty())) + { + subsInternal.add(matchedSeq); + } + else + { + // assert (false); + } + } + + LinkedList subst = new LinkedList(); + subst.addAll(subsOpen); + subst.addAll(subsClose); + subst.addAll(subsInternal); + + Collections.sort(subst, new Comparator() + { + public int compare(Object o1, Object o2) + { + MutableCharSequence s1 = (MutableCharSequence) o1; + MutableCharSequence s2 = (MutableCharSequence) o2; + return -(s1.start - s2.start); + } + }); + + buffer.delete(0, buffer.length()); + + int start = 0; + + while (!subst.isEmpty()) + { + MutableCharSequence seq = (MutableCharSequence) subst.removeLast(); + buffer.append(str.substring(start, seq.start)); + + if (subsClose.contains(seq)) + { + if (seq.param != null) + { + buffer.append(closeSubstWithParam); + } + else + { + buffer.append(closeSubstWithoutParam); + } + } + else if (subsInternal.contains(seq)) + { + buffer.append(internalSubst); + } + else if (subsOpen.contains(seq)) + { + Matcher m = Pattern.compile(openTag).matcher( + str.substring(seq.start, seq.start + seq.length)); + + if (m.matches()) + { + if (acceptParam && (seq.param != null)) + { + buffer.append( // + openSubstWithParam.replaceAll( + "\\{BBCODE_PARAM\\}", seq.param + .replaceAll("\\$", ""))); + } + else + { + buffer.append(openSubstWithoutParam); + } + } + } + + start = seq.start + seq.length; + } + + buffer.append(str.substring(start)); + } + + static class MutableCharSequence implements CharSequence + { + /** */ + public CharSequence base; + + /** */ + public int start; + + /** */ + public int length; + + /** */ + public String param = null; + + /** + */ + public MutableCharSequence() + { + // + } + + /** + * @param base + * @param start + * @param length + */ + public MutableCharSequence(CharSequence base, int start, int length) + { + reset(base, start, length); + } + + /** + * @see java.lang.CharSequence#length() + */ + public int length() + { + return this.length; + } + + /** + * @see java.lang.CharSequence#charAt(int) + */ + public char charAt(int index) + { + return this.base.charAt(this.start + index); + } + + /** + * @see java.lang.CharSequence#subSequence(int, int) + */ + public CharSequence subSequence(int pStart, int end) + { + return new MutableCharSequence(this.base, this.start + pStart, + this.start + (end - pStart)); + } + + /** + * @param pBase + * @param pStart + * @param pLength + * @return - + */ + public CharSequence reset(CharSequence pBase, int pStart, int pLength) + { + this.base = pBase; + this.start = pStart; + this.length = pLength; + + return this; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + + for (int i = this.start; i < (this.start + this.length); i++) + { + sb.append(this.base.charAt(i)); + } + + return sb.toString(); + } + + } + +} diff --git a/src/org/xmlrpc/android/XMLRPCClient.java b/src/org/xmlrpc/android/XMLRPCClient.java @@ -337,8 +337,6 @@ public class XMLRPCClient extends XMLRPCCommon { pullParser.nextTag(); // either Tag.PARAMS (<params>) or Tag.FAULT (<fault>) String tag = pullParser.getName(); - Log.d("XMLRPC-Tag",tag); - if (tag.equals(Tag.PARAMS)) { // normal response pullParser.nextTag(); // Tag.PARAM (<param>) diff --git a/src/ru/perm/kefir/bbcode/AbstractCode.java b/src/ru/perm/kefir/bbcode/AbstractCode.java @@ -1,71 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; - -/** - * Abstract bb-code - * - * @author Vitaliy Samolovskih aka Kefir - */ -public abstract class AbstractCode implements Comparable<AbstractCode> { - protected static final int DEFAULT_PRIORITY = 0; - - /** - * Priority. If priority higher then code be checking early. - */ - private final int priority; - - /** - * The code name. - */ - private final String name; - - /** - * template for build result char sequence - */ - protected final Template template; - - /** - * Abstract constructor for the bb-code with priority - * - * @param template template - * @param name name of code - * @param priority priority. If priority higher then code be checking early. - */ - protected AbstractCode(Template template, String name, int priority) { - this.template = template; - this.priority = priority; - this.name = name; - } - - /** - * Process code: parse source and generate result string - * - * @param context current context - * @return true if next sequence in source is valid code - * @throws IOException append result to target - */ - public abstract boolean process(Context context) throws IOException; - - /** - * @param source source of text - * @return true if next sequence can be this code - */ - public abstract boolean suspicious(Source source); - - /** - * Compare by priorities - */ - public int compareTo(AbstractCode code) { - return this.priority - code.priority; - } - - /** - * Get code name - * - * @return code name - */ - public String getName() { - return name; - } -} diff --git a/src/ru/perm/kefir/bbcode/BBProcessor.java b/src/ru/perm/kefir/bbcode/BBProcessor.java @@ -1,114 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; -import java.util.Collections; -import java.util.Map; - -/** - * The bbcode processor. You can use the standard code set or define other. - * - * @author Kefir - */ -final class BBProcessor extends TextProcessorAdapter { - /** - * BB-codes - */ - private Scope scope = null; - private Template prefix = null; - private Template suffix = null; - private Map<String, Object> params = null; - - /** - * Create the bbcode processor - */ - BBProcessor() { - } - - /** - * Process bbcodes <br/> - * 1. Escape the xml special symbols<br/> - * 2. replace bbcodes to HTML-tags<br/> - * 3. replace symbols \r\n to HTML-tag "&lt;br/&gt;"<br/> - * - * @param source the source string - * @return result string - * @see TextProcessor#process(CharSequence) - */ - public CharSequence process(CharSequence source){ - Context context = new Context(); - StringBuilder target = new StringBuilder(); - context.setTarget(target); - context.setSource(new Source(source)); - context.setScope(scope); - if (params != null) { - for (Map.Entry<String, Object> entry : params.entrySet()) { - context.setAttribute(entry.getKey(), entry.getValue()); - } - } - - try { - prefix.generate(context); - context.parse(); - suffix.generate(context); - } catch (IOException e) { - // Never because StringBuilder not throw IOException - } - - return target; - } - - /** - * Set the root scope of text processor. - * - * @param scope root code scope - * @throws IllegalStateException if scope already setted - */ - void setScope(Scope scope) throws IllegalStateException { - if (this.scope == null) { - this.scope = scope; - } else { - throw new IllegalStateException("Can't change the root scope."); - } - } - - /** - * Set the prefix for text processor - * - * @param prefix template wich uses to create prefix - * @throws IllegalStateException If prefix already setted - */ - void setPrefix(Template prefix) throws IllegalStateException { - if (this.prefix == null) { - this.prefix = prefix; - } else { - throw new IllegalStateException("Can't change the prefix."); - } - } - - /** - * Set the suffix for text processor - * - * @param suffix template wich uses to create prefix - * @throws IllegalStateException If suffix already setted - */ - void setSuffix(Template suffix) { - if (this.suffix == null) { - this.suffix = suffix; - } else { - throw new IllegalStateException("Can't change the suffix."); - } - } - - /** - * Set text processor parameters map. - * - * @param params parameters - */ - void setParams(Map<String, Object> params) { - if (this.params == null) { - this.params = Collections.unmodifiableMap(params); - } else { - throw new IllegalStateException("Can't change parameters."); - } - } -} diff --git a/src/ru/perm/kefir/bbcode/BBProcessorFactory.java b/src/ru/perm/kefir/bbcode/BBProcessorFactory.java @@ -1,86 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.File; -import java.io.InputStream; - -/** - * Factory for creating BBProcessor from Stream, File, Resource with configuration or default bb-processor. - * - * @author Kefir - */ -public final class BBProcessorFactory implements TextProcessorFactory { - /** - * Instance of this class. See the Singleton pattern - */ - private static final BBProcessorFactory instance = new BBProcessorFactory(); - private final ConfigurationFactory configurationFactory = ConfigurationFactory.getInstance(); - - /** - * Return instance of BBProcessorFactory - * - * @return factory instance - */ - public static BBProcessorFactory getInstance() { - return instance; - } - - /** - * Private constructor. Because this is a singleton. - */ - private BBProcessorFactory() { - } - - /** - * Create the default bb-code processor. - * - * @return Default bb-code processor - * @throws TextProcessorFactoryException when can't read the default code set resource - */ - public TextProcessor create() { - return configurationFactory.create().create(); - } - - /** - * Create the bb-processor using xml-configuration resource - * - * @param resourceName name of resource file - * @return bb-code processor - * @throws TextProcessorFactoryException when can't find or read the resource or illegal config file - */ - public TextProcessor createFromResource(String resourceName) { - return configurationFactory.createFromResource(resourceName).create(); - } - - /** - * Create the bb-processor from XML InputStream - * - * @param stream the input stream with XML - * @return bb-code processor - * @throws TextProcessorFactoryException when can't build Document - */ - public TextProcessor create(InputStream stream) { - return configurationFactory.create(stream).create(); - } - - /** - * Create the bb-code processor from file with XML-configuration. - * - * @param file file with configuration - * @return bb-code processor - * @throws TextProcessorFactoryException any problems - */ - public TextProcessor create(File file) { - return configurationFactory.create(file).create(); - } - - /** - * Create the bb-code processor from file with XML-configuration. - * - * @param fileName name of file with configuration - * @return bb-code processor - * @throws TextProcessorFactoryException any problems - */ - public TextProcessor create(String fileName) { - return create(new File(fileName)); - } -} diff --git a/src/ru/perm/kefir/bbcode/Code.java b/src/ru/perm/kefir/bbcode/Code.java @@ -1,72 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; - -/** - * The bbcode class - * - * @author Kefir - */ -public class Code extends AbstractCode { - /** - * Pattern for parsing code - */ - private final Pattern pattern; - - /** - * Create bb-code with default name and zero priority. - * - * @param pattern parse pattern - * @param template building tamplate - */ - public Code(Pattern pattern, Template template) { - super(template, Util.generateRandomName(), DEFAULT_PRIORITY); - this.pattern = pattern; - } - - /** - * Create the bb-code with priority - * - * @param pattern pattern to parse the source text - * @param template template to build target text - * @param name name of code - * @param priority priority. If priority higher then code be checking early. - */ - public Code(Pattern pattern, Template template, String name, int priority) { - super(template, name, priority); - this.pattern = pattern; - } - - /** - * Parse bb-code - * - * Before invocation suspicious method must be call - * - * @param context the bb-processing context - * @return true - if parse source - * false - if can't parse code - * @throws java.io.IOException if can't append to target - */ - public boolean process(Context context) throws IOException { - Context codeContext = new Context(context); - if (pattern.parse(codeContext)) { - codeContext.mergeWithParent(); - template.generate(context); - return true; - } - - return false; - } - - /** - * Check if next sequence can be parsed with this code. - * It's most called method in this project. - * - * @param source text source - * @return true - if next sequence can be parsed with this code; - * false - only if next sequence can't be parsed with this code. - */ - public boolean suspicious(Source source) { - return pattern.suspicious(source); - } -} diff --git a/src/ru/perm/kefir/bbcode/Configuration.java b/src/ru/perm/kefir/bbcode/Configuration.java @@ -1,138 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * Configuration of bbcode processor. - * It's thread safe class. - * - * @author Vitaliy Samolovskih aka Kefir - */ -public class Configuration implements TextProcessorFactory { - private Scope scope = null; - private Template prefix = Template.EMPTY; - private Template suffix = Template.EMPTY; - private final Map<String, Object> params = new HashMap<String, Object>(); - - /** - * Create the configuration - */ - public Configuration() { - } - - /** - * Create text processor. - * - * @return text processor - * @throws IllegalStateException if scope not setted - */ - public synchronized TextProcessor create() throws IllegalStateException { - if (scope == null) { - throw new IllegalStateException("Scope is null."); - } - BBProcessor processor = new BBProcessor(); - processor.setScope(scope); - processor.setPrefix(prefix); - processor.setSuffix(suffix); - processor.setParams(params); - return processor; - } - - /** - * Set root scope for text processor. - * - * @param scope root scope, text will be parse with this scope - */ - public synchronized void setScope(Scope scope) { - this.scope = scope; - } - - /** - * Set prefix template. Prefix append to start of processed text. - * - * @param prefix template for prefix - */ - public synchronized void setPrefix(Template prefix) { - if (prefix != null) { - this.prefix = prefix; - } else { - this.prefix = Template.EMPTY; - } - } - - /** - * Set suffix template. Suffix append to end of processed text. - * - * @param suffix template for suffix - */ - public synchronized void setSuffix(Template suffix) { - if (suffix != null) { - this.suffix = suffix; - } else { - this.suffix = Template.EMPTY; - } - } - - /** - * Add param with name <code>name</code> and value <code>value</code> to root context. - * Call addParam(String, object) - * - * @param name name of context parameter - * @param value value of context parameter - * @see #addParam(String, Object) - */ - public synchronized void setParam(String name, Object value) { - addParam(name, value); - } - - /** - * Add param with name <code>name</code> and value <code>value</code> to root context. - * - * @param name name of context parameter - * @param value value of context parameter - */ - public synchronized void addParam(String name, Object value) { - params.put(name, value); - } - - /** - * Add param from map to root context. - * - * @param params Map contained params - */ - public synchronized void addParams(Map<String, ?> params) { - this.params.putAll(params); - } - - /** - * Add param from properties to root context. - * - * @param properties Properties object - */ - public synchronized void addParams(Properties properties) { - for (Map.Entry<Object, Object> entry : properties.entrySet()) { - Object key = entry.getKey(); - if (key != null) { - this.params.put(key.toString(), entry.getValue()); - } - } - } - - /** - * Remove parameter with name <code>name</code> from context. - * - * @param name name of parameter - */ - public synchronized void removeParam(String name) { - this.params.remove(name); - } - - /** - * Remove all parameters from context. - */ - public synchronized void clearParams() { - this.params.clear(); - } -} diff --git a/src/ru/perm/kefir/bbcode/ConfigurationFactory.java b/src/ru/perm/kefir/bbcode/ConfigurationFactory.java @@ -1,885 +0,0 @@ -package ru.perm.kefir.bbcode; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.*; -import java.util.*; - -/** - * Create the text processor configuration - * - * @author Vitaliy Samolovskih aka Kefir - */ -public final class ConfigurationFactory -{ - // Helper constants - private static final String DEFAULT_CONFIGURATION = "ru/perm/kefir/bbcode/default"; - private static final String DEFAULT_USER_CONFIGURATION = "kefirbb"; - private static final String CONFIGURATION_EXTENSION = ".xml"; - - // Configuration paths - public static final String DEFAULT_USER_CONFIGURATION_FILE = DEFAULT_USER_CONFIGURATION - + CONFIGURATION_EXTENSION; - public static final String DEFAULT_CONFIGURATION_FILE = DEFAULT_CONFIGURATION - + CONFIGURATION_EXTENSION; - public static final String DEFAULT_PROPERTIES_FILE = "kefirbb.properties"; - public static final String DEFAULT_PROPERTIES_XML_FILE = "kefirbb.properties.xml"; - - /** - * Schema location - */ - private static final String SCHEMA_LOCATION = "http://kefir-bb.sourceforge.net/schemas"; - - /** - * Constants wich uses when parse XML-configuration - */ - private static final String TAG_CODE = "code"; - private static final String TAG_CODE_ATTR_NAME = "name"; - private static final String TAG_CODE_ATTR_PRIORITY = "priority"; - private static final String TAG_PATTERN = "pattern"; - private static final String TAG_VAR = "var"; - private static final String TAG_VAR_ATTR_NAME = "name"; - private static final String DEFAULT_VARIABLE_NAME = "variable"; - private static final String TAG_VAR_ATTR_PARSE = "parse"; - private static final boolean DEFAULT_PARSE_VALUE = true; - private static final String TAG_VAR_ATTR_INHERIT = "inherit"; - private static final boolean DEFAULT_INHERIT_VALUE = false; - private static final String TAG_VAR_ATTR_REGEX = "regex"; - private static final String TAG_VAR_ATTR_TRANSPARENT = "transparent"; - private static final String TAG_TEMPLATE = "template"; - private static final String TAG_SCOPE = "scope"; - private static final String TAG_SCOPE_ATTR_NAME = "name"; - private static final String TAG_SCOPE_ATTR_PARENT = "parent"; - private static final String TAG_SCOPE_ATTR_IGNORE_TEXT = "ignoreText"; - private static final boolean DEFAULT_IGNORE_TEXT = false; - private static final String TAG_CODEREF = "coderef"; - private static final String TAG_CODEREF_ATTR_NAME = TAG_CODE_ATTR_NAME; - private static final String TAG_PREFIX = "prefix"; - private static final String TAG_SUFFIX = "suffix"; - private static final String TAG_PARAMS = "params"; - private static final String TAG_PARAM = "param"; - private static final String TAG_PARAM_ATTR_NAME = "name"; - private static final String TAG_PARAM_ATTR_VALUE = "value"; - - /** - * Singletone class instance - */ - private static final ConfigurationFactory instance = new ConfigurationFactory(); - - /** - * private constructor - */ - private ConfigurationFactory() - { - } - - /** - * Return instance of class ConfigurationFactory - * - * @return configuration factory - */ - public static ConfigurationFactory getInstance() - { - return instance; - } - - /** - * Create the default bb-code processor. - * - * @return Default bb-code processor - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * when can't read the default code set resource - */ - public Configuration create() - { - Configuration configuration; - try - { - InputStream stream = null; - try - { - // Search the user configuration - stream = Util - .openResourceStream(DEFAULT_USER_CONFIGURATION_FILE); - - // If user configuration not found then use default - if (stream == null) - { - stream = Util - .openResourceStream(DEFAULT_CONFIGURATION_FILE); - } - - if (stream != null) - { - configuration = create(stream); - } - else - { - throw new TextProcessorFactoryException( - "Can't find or open resource."); - } - } - finally - { - if (stream != null) - { - stream.close(); - } - } - - Properties properties = new Properties(); - - // Load properties from .property file - InputStream propertiesStream = null; - try - { - propertiesStream = Util - .openResourceStream(DEFAULT_PROPERTIES_FILE); - if (propertiesStream != null) - { - properties.load(propertiesStream); - } - } - finally - { - if (propertiesStream != null) - { - propertiesStream.close(); - } - } - - // Load properties from xml file - InputStream xmlPropertiesStream = null; - try - { - xmlPropertiesStream = Util - .openResourceStream(DEFAULT_PROPERTIES_XML_FILE); - if (xmlPropertiesStream != null) - { - properties.loadFromXML(xmlPropertiesStream); - } - } - finally - { - if (xmlPropertiesStream != null) - { - xmlPropertiesStream.close(); - } - } - - configuration.addParams(properties); - } - catch (IOException e) - { - throw new TextProcessorFactoryException(e); - } - return configuration; - } - - /** - * Create the bb-processor using xml-configuration resource - * - * @param resourceName - * name of resource file - * @return bb-code processor - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * when can't find or read the resource or illegal config file - */ - public Configuration createFromResource(String resourceName) - { - if (resourceName == null) - { - throw new IllegalArgumentException( - "The resource name is not setted."); - } - - Configuration configuration; - try - { - InputStream stream = null; - try - { - stream = Util.openResourceStream(resourceName); - - if (stream != null) - { - configuration = create(stream); - } - else - { - throw new TextProcessorFactoryException( - "Can't find or open resource \"" + resourceName - + "\"."); - } - } - finally - { - if (stream != null) - { - stream.close(); - } - } - } - catch (IOException e) - { - throw new TextProcessorFactoryException(e); - } - - return configuration; - } - - /** - * Create the bb-code processor from file with XML-configuration. - * - * @param fileName - * name of file with configuration - * @return bb-code processor - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * any problems - */ - public Configuration create(String fileName) - { - return create(new File(fileName)); - } - - /** - * Create the bb-code processor from file with XML-configuration. - * - * @param file - * file with configuration - * @return bb-code processor - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * any problems - */ - public Configuration create(File file) - { - try - { - Configuration configuration; - InputStream stream = new BufferedInputStream(new FileInputStream( - file)); - try - { - configuration = create(stream); - } - finally - { - stream.close(); - } - return configuration; - } - catch (IOException e) - { - throw new TextProcessorFactoryException(e); - } - } - - /** - * Create the bb-processor from XML InputStream - * - * @param stream - * the input stream with XML - * @return bb-code processor - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * when can't build Document - */ - public Configuration create(InputStream stream) - { - try - { - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); - factory.setValidating(false); - factory.setIgnoringElementContentWhitespace(true); - factory.setNamespaceAware(true); - DocumentBuilder documentBuilder = factory.newDocumentBuilder(); - Document document = documentBuilder.parse(stream); - return create(document); - } - catch (ParserConfigurationException e) - { - throw new TextProcessorFactoryException(e); - } - catch (IOException e) - { - throw new TextProcessorFactoryException(e); - } - catch (SAXException e) - { - throw new TextProcessorFactoryException(e); - } - } - - /** - * Create the bb-code processor from DOM Document - * - * @param dc - * document - * @return bb-code processor - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * If invalid Document - */ - private Configuration create(Document dc) - { - // Create configuration - Configuration configuration = new Configuration(); - - // Parse parameters - configuration.addParams(parseParams(dc)); - - // Parse prefix and suffix - configuration.setPrefix(parseFix(dc, TAG_PREFIX)); - configuration.setSuffix(parseFix(dc, TAG_SUFFIX)); - - // Parse codes and scope and set this to configuration - // Parse scopes - NodeList scopeNodeList = dc.getDocumentElement() - .getElementsByTagNameNS(SCHEMA_LOCATION, TAG_SCOPE); - Map<String, Scope> scopes = parseScopes(scopeNodeList); - - boolean fillRoot = false; - Scope root; - if (!scopes.containsKey(Scope.ROOT)) - { - root = new Scope(Scope.ROOT); - scopes.put(Scope.ROOT, root); - fillRoot = true; - } - else - { - root = scopes.get(Scope.ROOT); - } - - // Parse codes - Map<String, AbstractCode> codes = parseCodes(dc, scopes); - - // include codes in scopes - fillScopeCodes(scopeNodeList, scopes, codes); - - // If root scope not defined in configuration file, then root scope - // fills all codes - if (fillRoot) - { - root.setScopeCodes(new HashSet<AbstractCode>(codes.values())); - } - - // set root scope - configuration.setScope(root); - - // return configuration - return configuration; - } - - private Map<String, String> parseParams(Document dc) - { - Map<String, String> params = new HashMap<String, String>(); - NodeList paramsElements = dc.getElementsByTagName(TAG_PARAMS); - if (paramsElements.getLength() > 0) - { - Element paramsElement = (Element) paramsElements.item(0); - NodeList paramElements = paramsElement - .getElementsByTagName(TAG_PARAM); - for (int i = 0; i < paramElements.getLength(); i++) - { - Node paramElement = paramElements.item(i); - String name = nodeAttribute(paramElement, TAG_PARAM_ATTR_NAME, - ""); - String value = nodeAttribute(paramElement, - TAG_PARAM_ATTR_VALUE, ""); - if (name != null && name.length() > 0) - { - params.put(name, value); - } - } - } - return params; - } - - @SuppressWarnings( { "unchecked" }) - private Template parseFix(Document dc, String tagname) - { - Template fix; - NodeList prefixElementList = dc.getElementsByTagName(tagname); - if (prefixElementList.getLength() > 0) - { - fix = parseTemplate(prefixElementList.item(0)); - } - else - { - fix = Template.EMPTY; - } - return fix; - } - - /** - * Fill codes of scopes. - * - * @param scopeNodeList - * node list with scopes definitions - * @param scopes - * scopes - * @param codes - * codes - * @throws TextProcessorFactoryException - * any problem - */ - private void fillScopeCodes(NodeList scopeNodeList, - Map<String, Scope> scopes, Map<String, AbstractCode> codes) - { - int initCount; - int notInitCount; - do - { - initCount = 0; - notInitCount = 0; - for (int i = 0; i < scopeNodeList.getLength(); i++) - { - Element scopeElement = (Element) scopeNodeList.item(i); - Scope scope = scopes.get(scopeElement - .getAttribute(TAG_SCOPE_ATTR_NAME)); - if (!scope.isInitialized()) - { - // parent - boolean canBeInit = true; - if (scopeElement.hasAttribute(TAG_SCOPE_ATTR_PARENT)) - { - String parentName = scopeElement - .getAttribute(TAG_SCOPE_ATTR_PARENT); - Scope parent = scopes.get(parentName); - if (parent == null) - { - throw new TextProcessorFactoryException( - "Can't find parent scope \"" + parentName - + "\"."); - } - if (parent.isInitialized()) - { - scope.setParent(parent); - } - else - { - canBeInit = false; - } - } - - if (canBeInit) - { - // Add codes to scope - Set<AbstractCode> scopeCodes = new HashSet<AbstractCode>(); - - // bind exists codes - NodeList coderefs = scopeElement - .getElementsByTagNameNS(SCHEMA_LOCATION, - TAG_CODEREF); - for (int j = 0; j < coderefs.getLength(); j++) - { - Element ref = (Element) coderefs.item(j); - String codeName = ref - .getAttribute(TAG_CODEREF_ATTR_NAME); - AbstractCode code = codes.get(codeName); - if (code == null) - { - throw new TextProcessorFactoryException( - "Can't find code \"" + codeName + "\"."); - } - scopeCodes.add(code); - } - - // Add inline codes - NodeList inlineCodes = scopeElement - .getElementsByTagNameNS(SCHEMA_LOCATION, - TAG_CODE); - for (int j = 0; j < inlineCodes.getLength(); j++) - { - // Inline element code - Element ice = (Element) inlineCodes.item(j); - scopeCodes.add(parseCode(ice, scopes)); - } - - // Set codes to scope - scope.setScopeCodes(scopeCodes); - initCount++; - } - else - { - notInitCount++; - } - } - } - } - while (initCount > 0 && notInitCount > 0); - - if (notInitCount > 0) - { - throw new TextProcessorFactoryException("Can't init scopes."); - } - } - - /** - * Parse scopes from XML - * - * @param scopeNodeList - * list with scopes definitions - * @return scopes - * @throws TextProcessorFactoryException - * any problems - */ - private Map<String, Scope> parseScopes(NodeList scopeNodeList) - { - Map<String, Scope> scopes = new HashMap<String, Scope>(); - for (int i = 0; i < scopeNodeList.getLength(); i++) - { - Element scopeElement = (Element) scopeNodeList.item(i); - String name = scopeElement.getAttribute(TAG_SCOPE_ATTR_NAME); - if (name.length() == 0) - { - throw new TextProcessorFactoryException("Illegal scope name."); - } - Scope scope = new Scope(name); - scope.setIgnoreText(nodeAttribute(scopeElement, - TAG_SCOPE_ATTR_IGNORE_TEXT, DEFAULT_IGNORE_TEXT)); - scopes.put(scope.getName(), scope); - } - return scopes; - } - - /** - * Parse codes from XML - * - * @param dc - * DOM document with configuration - * @param scopes - * scope set - * @return codes - * @throws TextProcessorFactoryException - * any problem - */ - private Map<String, AbstractCode> parseCodes(Document dc, - Map<String, Scope> scopes) - { - Map<String, AbstractCode> codes = new HashMap<String, AbstractCode>(); - NodeList codeNodeList = dc.getDocumentElement().getElementsByTagNameNS( - SCHEMA_LOCATION, TAG_CODE); - for (int i = 0; i < codeNodeList.getLength(); i++) - { - AbstractCode code = parseCode((Element) codeNodeList.item(i), - scopes); - codes.put(code.getName(), code); - } - return codes; - } - - /** - * Parse bb-code from DOM Node - * - * @param codeElement - * node, represent code wich - * @param scopes - * mapping names of scope to scope - * @return bb-code - * @throws ru.perm.kefir.bbcode.TextProcessorFactoryException - * if error format - */ - private AbstractCode parseCode(Element codeElement, - Map<String, Scope> scopes) - { - // Code name - String name = nodeAttribute(codeElement, TAG_CODE_ATTR_NAME, Util - .generateRandomName()); - - // Code priority - int priority = nodeAttribute(codeElement, TAG_CODE_ATTR_PRIORITY, - AbstractCode.DEFAULT_PRIORITY); - - // Template to building - Template template; - NodeList templateElements = codeElement.getElementsByTagNameNS( - SCHEMA_LOCATION, TAG_TEMPLATE); - if (templateElements.getLength() > 0) - { - template = parseTemplate(templateElements.item(0)); - } - else - { - throw new TextProcessorFactoryException( - "Illegal configuration. Can't find template of code."); - } - - // Pattern to parsing - NodeList patternElements = codeElement.getElementsByTagNameNS( - SCHEMA_LOCATION, TAG_PATTERN); - if (patternElements.getLength() <= 0) - { - throw new TextProcessorFactoryException( - "Illegal configuration. Can't find pattern of code."); - } - - AbstractCode code = null; - - // Attempt parse constant code - NodeList patternList = patternElements.item(0).getChildNodes(); - if (patternList.getLength() == 1) - { - Node node = patternList.item(0); - if (node.getNodeType() == Node.TEXT_NODE) - { - code = new ConstantCode(node.getNodeValue(), template, name, - priority); - } - } - - if (code == null) - { - // Create code - code = new Code(parsePattern(patternElements.item(0), scopes), - template, name, priority); - } - - // return code - return code; - } - - /** - * Parse code pattern for parse text. - * - * @param node - * pattern node with pattern description - * @param scopes - * mapping names of scope to scope - * @return list of pattern elements - * @throws TextProcessorFactoryException - * If invalid pattern format - */ - private Pattern parsePattern(Node node, Map<String, Scope> scopes) - { - List<PatternElement> elements = new LinkedList<PatternElement>(); - NodeList patternList = node.getChildNodes(); - int patternLength = patternList.getLength(); - if (patternLength <= 0) - { - throw new TextProcessorFactoryException("Invalid pattern"); - } - for (int k = 0; k < patternLength; k++) - { - Node el = patternList.item(k); - if (el.getNodeType() == Node.TEXT_NODE) - { - elements.add(new Constant(el.getNodeValue())); - } - else if (el.getNodeType() == Node.ELEMENT_NODE - && el.getLocalName().equals(TAG_VAR) - && (k != 0 || nodeHasAttribute(el, TAG_VAR_ATTR_REGEX))) - { - elements.add(parseNamedElement(el, scopes)); - } - else if (el.getNodeType() == Node.ENTITY_REFERENCE_NODE) - { - elements.add(new Constant("?")); - } - else - { - throw new TextProcessorFactoryException("Invalid pattern, Nodetype="+el.getNodeType()); - } - } - return new Pattern(elements); - } - - private PatternElement parseNamedElement(Node el, Map<String, Scope> scopes) - { - String name = nodeAttribute(el, TAG_VAR_ATTR_NAME, - DEFAULT_VARIABLE_NAME); - PatternElement namedElement; - if (nodeAttribute(el, TAG_VAR_ATTR_PARSE, DEFAULT_PARSE_VALUE) - && !nodeHasAttribute(el, TAG_VAR_ATTR_REGEX)) - { - namedElement = parseText(el, name, scopes); - } - else - { - namedElement = parseVariable(el, name); - } - return namedElement; - } - - private Text parseText(Node el, String name, Map<String, Scope> scopes) - { - Text text; - if (nodeAttribute(el, TAG_VAR_ATTR_INHERIT, DEFAULT_INHERIT_VALUE)) - { - text = new Text(name, nodeAttribute(el, TAG_VAR_ATTR_TRANSPARENT, - false)); - } - else - { - text = new Text(name, scopes.get(nodeAttribute(el, TAG_SCOPE, - Scope.ROOT)), nodeAttribute(el, TAG_VAR_ATTR_TRANSPARENT, - false)); - } - return text; - } - - private Variable parseVariable(Node el, String name) - { - Variable variable; - if (nodeHasAttribute(el, TAG_VAR_ATTR_REGEX)) - { - variable = new Variable(name, java.util.regex.Pattern - .compile(nodeAttribute(el, TAG_VAR_ATTR_REGEX))); - } - else - { - variable = new Variable(name); - } - return variable; - } - - /** - * Parse template fo generate text. - * - * @param node - * template node - * @return list of template elements - */ - private Template parseTemplate(Node node) - { - List<TemplateElement> elements = new LinkedList<TemplateElement>(); - NodeList templateList = node.getChildNodes(); - for (int k = 0; k < templateList.getLength(); k++) - { - Node el = templateList.item(k); - if (el.getNodeType() == Node.ELEMENT_NODE - && el.getLocalName().equals(TAG_VAR)) - { - elements.add(new NamedValue(nodeAttribute(el, - TAG_VAR_ATTR_NAME, DEFAULT_VARIABLE_NAME))); - } - else - { - elements.add(new Constant(el.getNodeValue())); - } - } - return new Template(elements); - } - - /** - * Return node attribute value, if exists or default attibute value - * - * @param node - * XML-node - * @param attributeName - * attributeName - * @param defaultValue - * attribute default value - * @return attribute value or default value - */ - private boolean nodeAttribute(Node node, String attributeName, - boolean defaultValue) - { - boolean value = defaultValue; - if (node.hasAttributes()) - { - Node attribute = node.getAttributes().getNamedItem(attributeName); - if (attribute != null) - { - value = Boolean.valueOf(attribute.getNodeValue()); - } - } - return value; - } - - /** - * Return node attribute value, if exists or default attibute value - * - * @param node - * XML-node - * @param attributeName - * attributeName - * @param defaultValue - * attribute default value - * @return attribute value or default value - */ - private String nodeAttribute(Node node, String attributeName, - String defaultValue) - { - String value = defaultValue; - if (node.hasAttributes()) - { - Node attribute = node.getAttributes().getNamedItem(attributeName); - if (attribute != null) - { - value = attribute.getNodeValue(); - } - } - return value; - } - - /** - * Return node attribute value, if exists or null value - * - * @param node - * XML-node - * @param attributeName - * attributeName - * @return attribute value or default value - */ - private String nodeAttribute(Node node, String attributeName) - { - String value = null; - if (node.hasAttributes()) - { - Node attribute = node.getAttributes().getNamedItem(attributeName); - if (attribute != null) - { - value = attribute.getNodeValue(); - } - } - return value; - } - - /** - * Return node attribute value, if exists or default attibute value - * - * @param node - * XML-node - * @param attributeName - * attributeName - * @param defaultValue - * attribute default value - * @return attribute value or default value - */ - private int nodeAttribute(Node node, String attributeName, int defaultValue) - { - int value = defaultValue; - if (node.hasAttributes()) - { - Node attribute = node.getAttributes().getNamedItem(attributeName); - if (attribute != null) - { - value = Integer.decode(attribute.getNodeValue()); - } - } - return value; - } - - /** - * Check node attribute. - * - * @param node - * XML-node - * @param attributeName - * name of attribute - * @return true if node has attribute with specified name false if has not - */ - private boolean nodeHasAttribute(Node node, String attributeName) - { - return node.hasAttributes() - && node.getAttributes().getNamedItem(attributeName) != null; - } -} diff --git a/src/ru/perm/kefir/bbcode/Constant.java b/src/ru/perm/kefir/bbcode/Constant.java @@ -1,91 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Constant element of pattern or template - * - * @author Kefir - */ -public class Constant implements PatternElement, TemplateElement { - /** - * Constant value - */ - private final String value; - - /** - * First char of constant. It need for better performance. - */ - private final char firstChar; - - /** - * Length of constant value - */ - private final int valueLength; - - /** - * Create constant element. - * - * @param value constant value - */ - public Constant(String value) { - this.value = value; - this.valueLength = value.length(); - this.firstChar = value.charAt(0); - } - - /** - * Parse constant - * - * @param context current context - * @param terminator not used - * @return true - if next sequence in source equals to this constant value, - * false - other - */ - public boolean parse(Context context, PatternElement terminator) { - if (isNextIn(context.getSource())) { - context.getSource().incOffset(valueLength); - return true; - } else { - return false; - } - } - - /** - * Return constant value - * - * @param context context. Not used. - */ - public CharSequence generate(Context context) { - return value; - } - - /** - * Check equals next sequence in source to this constant - * - * @param source source text - * @return true if next subsequence is equals - * false other - */ - public boolean isNextIn(Source source) { - return firstChar == source.current() - && source.hasNext(valueLength) - && value.contentEquals(source.subTo(valueLength)); - } - - /** - * Find this constant. - * - * @param source text source - * @return смещение константы - */ - public int findIn(Source source) { - return source.find(value); - } - - /** - * @return string representation of this object. - */ - @Override - public String toString() { - return "constant:" + value; - } -} diff --git a/src/ru/perm/kefir/bbcode/ConstantCode.java b/src/ru/perm/kefir/bbcode/ConstantCode.java @@ -1,44 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; - -/** - * Code with constant string pattern. For basic escaping. - * - * @author Vitaliy Samolovskih aka Kefir - */ -public class ConstantCode extends AbstractCode { - private final String value; - private final char firstChar; - private final int valueLength; - - /** - * Create bb-code with constant pattern - * - * @param value pattern value - * @param template template - * @param name name of code - * @param priority priority. If priority higher then code be checking early. - */ - public ConstantCode(String value, Template template, String name, int priority) { - super(template, name, priority); - - this.value = value; - this.firstChar = value.charAt(0); - this.valueLength = value.length(); - } - - @Override - public boolean process(Context context) throws IOException { - context.getSource().incOffset(valueLength); - template.generate(context); - return true; - } - - @Override - public boolean suspicious(Source source) { - return firstChar == source.current() - && source.hasNext(valueLength) - && value.contentEquals(source.subTo(valueLength)); - } -} diff --git a/src/ru/perm/kefir/bbcode/Context.java b/src/ru/perm/kefir/bbcode/Context.java @@ -1,248 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * The bb-processing context - * - * @author Kefir - */ -public class Context { - /** - * Parent context - */ - private final Context parent; - - /** - * source text - */ - private Source source; - - /** - * Target builder - */ - private Appendable target = null; - - /** - * Text terminator,this mark stop text processing - */ - private PatternElement terminator = null; - - /** - * Code scope - */ - private Scope scope; - - /** - * Codes array for performance - */ - private AbstractCode[] codes = new AbstractCode[0]; - - /** - * Context attributes - */ - private final Map<String, Object> attributes = new HashMap<String, Object>(); - - /** - * тэги с ошибками. т.е. позиции в которых тэги с ошибками - */ - private final Map<Scope, IntSet> falseMemo; - private IntSet scopeFalseMemo; - - /** - * Default constructor - */ - public Context() { - parent = null; - falseMemo = new HashMap<Scope, IntSet>(); - } - - /** - * Constructor of child-context - * - * @param parent parent context - */ - public Context(Context parent) { - this.parent = parent; - this.source = parent.source; - this.target = parent.target; - this.falseMemo = parent.falseMemo; - this.terminator = parent.terminator; - - this.setScope(parent.scope); - } - - /** - * Парсит тект с BB-кодами - * - * @throws java.io.IOException if can't append chars to target - */ - public void parse() throws IOException { - while (hasNextAdjustedForTerminator()) { - if (!process()) { - if (scope.isIgnoreText()) { - source.incOffset(); - } else { - getTarget().append(source.next()); - } - } - } - } - - /** - * Обрабатывает BB-коды - * - * @return true если найден BB-код - * @throws java.io.IOException if can't append to target - */ - private boolean process() throws IOException { - int offset = source.getOffset(); - if (checkBadTag(offset)) { - return false; - } - - boolean suspicious = false; - boolean parsed = false; - for (AbstractCode code : codes) { - if (code.suspicious(source)) { - suspicious = true; - if (code.process(this)) { - parsed = true; - break; - } - } - } - - if (suspicious && !parsed) { - addBadTag(offset); - } - - return parsed; - } - - /** - * Add the bag tag position - * - * @param offset offset of bad tag in source - */ - private void addBadTag(int offset) { - scopeFalseMemo.add(offset); - } - - /** - * Check the bag tag - * - * @param offset offset of tag - * @return true if at this ofsset tag is bad - */ - private boolean checkBadTag(int offset) { - return scopeFalseMemo.contains(offset); - } - - /** - * Check has chars in source before terminator or not - * - * @return true if chars exists - * false if chars canceled - */ - private boolean hasNextAdjustedForTerminator() { - return source.hasNext() && (terminator == null || !terminator.isNextIn(source)); - } - - /** - * Put all local attributes to parent context - */ - public void mergeWithParent() { - parent.attributes.putAll(this.attributes); - } - - /** - * Add or set context attribute - * - * @param name attribute name - * @param value attribute value - */ - public void setAttribute(String name, Object value) { - attributes.put(name, value); - } - - /** - * Get the context attribute. If attribute not exists in current context, - * then context search the sttribute in parent context - * - * @param name attribute name - * @return attribute value - */ - public Object getAttribute(String name) { - Object value = getLocalAttribute(name); - if (value == null && parent != null) { - value = parent.getAttribute(name); - } - return value; - } - - /** - * Return attribute from this context, not parent - * - * @param name attribute name - * @return attribute value - */ - public Object getLocalAttribute(String name) { - return attributes.get(name); - } - - /** - * Set list of codes in current context - * - * @param scope code scope - */ - public void setScope(Scope scope) { - this.scope = scope; - - // codes - List<AbstractCode> codeList = scope.getCodes(); - codes = codeList.toArray(new AbstractCode[codeList.size()]); - - // Scope false memo - scopeFalseMemo = falseMemo.get(scope); - if (scopeFalseMemo == null) { - scopeFalseMemo = new IntSet(); - falseMemo.put(scope, scopeFalseMemo); - } - } - - public Source getSource() { - return source; - } - - public void setSource(Source source) { - this.source = source; - } - - public Appendable getTarget() { - return target; - } - - public void setTarget(Appendable target) { - this.target = target; - } - - /** - * get Text terminator,this mark stop text processing - * - * @return terminator - */ - public PatternElement getTerminator() { - return terminator; - } - - /** - * @param terminator Text terminator,this mark stop text processing - */ - public void setTerminator(PatternElement terminator) { - this.terminator = terminator; - } -} diff --git a/src/ru/perm/kefir/bbcode/EscapeProcessor.java b/src/ru/perm/kefir/bbcode/EscapeProcessor.java @@ -1,86 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.util.Arrays; - -/** - * Class for escape processing. For example, EscapeXmlProcessorFactory use this class for create EscapeXmlProcessor. - * - * @author Kefir - */ -public class EscapeProcessor extends TextProcessorAdapter { - /** - * Escape symbols with replacement. - */ - private final String[][] escape; - - /** - * Construct the escape processor with special escape symbols. - * - * @param escape escape symbols with replacement. This is a array of array wich consist of two strings - * the pattern and the replacement - */ - public EscapeProcessor(String[][] escape) { - this.escape = escape; - } - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - * @see TextProcessor#process(CharSequence) - */ - public CharSequence process(CharSequence source) { - StringBuilder result = new StringBuilder(); - if (source != null && source.length() > 0) { - String stringSource; - if (source instanceof String) { - stringSource = (String) source; - } else { - stringSource = source.toString(); - } - - // Array to cache founded indexes of sequences - int[] indexes = new int[escape.length]; - Arrays.fill(indexes, -1); - - int length = source.length(); - int offset = 0; - while (offset < length) { - // Find next escape sequence - int escPosition = -1; - int escIndex = -1; - for (int i = 0; i < escape.length; i++) { - int index; - if (indexes[i] < offset) { - index = stringSource.indexOf(escape[i][0], offset); - indexes[i] = index; - } else { - index = indexes[i]; - } - - if (index >= 0 && (index < escPosition || escPosition < 0)) { - escPosition = index; - escIndex = i; - } - } - - // If escape secuence is found - if (escPosition >= 0) { - // replace chars before escape sequence - result.append(stringSource, offset, escPosition); - - // Replace sequence - result.append(escape[escIndex][1]); - offset = escPosition + escape[escIndex][0].length(); - } else { - // Put other string to result sequence - result.append(stringSource, offset, length); - offset = length; - } - } - } - - return result; - } -} diff --git a/src/ru/perm/kefir/bbcode/EscapeXmlProcessorFactory.java b/src/ru/perm/kefir/bbcode/EscapeXmlProcessorFactory.java @@ -1,59 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * The class for creating the escape xml special symbols processor. It's processor change: - * <p/> - * &amp; to &amp;amp; - * &apos; to &amp;apos; - * &lt; to &amp;lt; - * &gt; to &amp;gt; - * &quot; to &amp;quot; - * - * @author Kefir - */ -public final class EscapeXmlProcessorFactory implements TextProcessorFactory { - /** - * The default XML escape symbols - */ - private static final String[][] DEFAULT_ESCAPE_XML = { - {"&", "&amp;"}, - {"'", "&apos;"}, - {">", "&gt;"}, - {"<", "&lt;"}, - {"\"", "&quot;"} - }; - - /** - * Instance of processor. - */ - private static final TextProcessor processor = new EscapeProcessor(DEFAULT_ESCAPE_XML); - - /** - * Instance of factory - */ - private static final TextProcessorFactory instance = new EscapeXmlProcessorFactory(); - - /** - * Private constructor. Because this class is singleton. - */ - private EscapeXmlProcessorFactory() { - } - - /** - * Return instance of this class. - * - * @return instance of escape xml processor factory - */ - public static TextProcessorFactory getInstance() { - return instance; - } - - /** - * Create the new XML escape symbols processor. - * - * @see ru.perm.kefir.bbcode.TextProcessorFactory#create() - */ - public TextProcessor create() { - return processor; - } -} diff --git a/src/ru/perm/kefir/bbcode/IntSet.java b/src/ru/perm/kefir/bbcode/IntSet.java @@ -1,94 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Best performance set of primitive type int - * - * @author Vitaliy Samolovskih aka Kefir - */ -class IntSet { - private static final int TABLE_SIZE = 256; - private static final int MASK = 255; - private static final int INITIAL_CAPACITY = 16; - - private final int[][] table = new int[TABLE_SIZE][]; // Init null values by default - private final int lengths[] = new int[TABLE_SIZE]; // Init 0 values by default - - IntSet() { - } - - public void add(int value) { - int rowIndex = rowIndex(value); - - int[] row = table[rowIndex]; - if (row == null) { - row = new int[INITIAL_CAPACITY]; - table[rowIndex] = row; - row[0] = value; - lengths[rowIndex]++; - } else { - int length = lengths[rowIndex]; - - // Enlarge array if necessary - if (length >= row.length) { - int newLength = 2 * row.length; - int[] copyRow = new int[newLength]; - System.arraycopy(row, 0, copyRow, 0, row.length); - row = copyRow; - table[rowIndex] = copyRow; - } - - int index = binarySearch(row, length, value); - - // If can't find value - if (index < 0) { - // Insert value - int temp = value; - for (int i = -index - 1; i < length; i++) { - int temp1 = row[i]; - row[i] = temp; - temp = temp1; - } - row[length] = temp; - lengths[rowIndex]++; - } - } - } - - /** - * Realisation of binary search algorithm. It is in JDK 1.6.0 but for - * JDK 1.5.0 compatibility I added it there. - * - * @param array array of integers values ordered by ascending - * @param toIndex top break of array - * @param key searched value - * @return value index or -(index of position) - * @see java.util.Arrays#binarySearch(int[], int, int, int) - */ - private static int binarySearch(int[] array, int toIndex, int key) { - int low = 0; - int high = toIndex - 1; - - while (low <= high) { - int mid = (low + high) >>> 1; - int midVal = array[mid]; - - if (midVal < key) - low = mid + 1; - else if (midVal > key) - high = mid - 1; - else - return mid; // key found - } - return -(low + 1); // key not found. - } - - public boolean contains(int value) { - int rowIndex = rowIndex(value); - int length = lengths[rowIndex]; - return length > 0 && binarySearch(table[rowIndex], length, value) >= 0; - } - - private int rowIndex(int value) { - return value & MASK; - } -} diff --git a/src/ru/perm/kefir/bbcode/NamedElement.java b/src/ru/perm/kefir/bbcode/NamedElement.java @@ -1,39 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Named element are variable and text PatternElement or TemplateElement - */ -public class NamedElement { - /** - * Variable name - */ - private final String name; - - /** - * Create named element - * - * @param name name of element - */ - public NamedElement(String name) { - this.name = name; - } - - /** - * Get element name - * - * @return element name - */ - public String getName() { - return name; - } - - /** - * Add attribute with name of this element name and value <code>value</code> to <code>context</code>. - * - * @param context context - * @param value variable value - */ - protected void setAttribute(Context context, CharSequence value) { - context.setAttribute(name, value); - } -} diff --git a/src/ru/perm/kefir/bbcode/NamedValue.java b/src/ru/perm/kefir/bbcode/NamedValue.java @@ -1,26 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Named value to build target text - */ -public class NamedValue extends NamedElement implements TemplateElement { - public NamedValue(String name) { - super(name); - } - - /** - * Добавляет элемент в новую строку - * - * @param context контекст - */ - public CharSequence generate(Context context) { - Object attribute = context.getAttribute(getName()); - if (attribute == null) { - return "null"; - } else if (attribute instanceof CharSequence) { - return (CharSequence) attribute; - } else { - return attribute.toString(); - } - } -} diff --git a/src/ru/perm/kefir/bbcode/Pattern.java b/src/ru/perm/kefir/bbcode/Pattern.java @@ -1,74 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.util.Collections; -import java.util.List; - -/** - * Represents the pattern - * - * @author Vitaliy Samolovskih aka Kefir - */ -public class Pattern { - /** - * Pattern elements - */ - private final List<? extends PatternElement> elements; - - // Performance optimization - private final PatternElement firstElement; - - /** - * Construct pattern. - * - * @param elements pattern elements - */ - public Pattern(List<? extends PatternElement> elements) { - this.elements = Collections.unmodifiableList(elements); - - // Performance optimization - if (!this.elements.isEmpty()) { - firstElement = this.elements.get(0); - } else { - throw new IllegalArgumentException("Parameter \"elements\" can't be empty."); - } - } - - /** - * Указывает на то что следующая последовательность вполне может оказаться данным тэгом - * - * @param source источник - * @return true - если следующие несколько символов совпадают с первой константой в коде - * false - означает, что это точно не тот код - */ - public boolean suspicious(Source source) { - return firstElement.isNextIn(source); - } - - /** - * Parse context with this pattern - * - * @param context current context - * @return true if next subsequence is valid to this pattern, - * false others - */ - public boolean parse(Context context) { - boolean flag = true; - int start = context.getSource().getOffset(); - int patternSize = elements.size(); - for (int i = 0; i < patternSize && flag; i++) { - PatternElement current = elements.get(i); - PatternElement next; - if (i < patternSize - 1) { - next = elements.get(i + 1); - } else { - next = context.getTerminator(); - } - flag = context.getSource().hasNext() && current.parse(context, next); - } - - if (!flag) { - context.getSource().setOffset(start); - } - return flag; - } -} diff --git a/src/ru/perm/kefir/bbcode/PatternElement.java b/src/ru/perm/kefir/bbcode/PatternElement.java @@ -1,35 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Pattern element for parse part of bbcode - * - * @author Kefir - */ -public interface PatternElement { - /** - * Parse element - * - * @param context context - * @param terminator teminator to stop text process - * @return true - subsequence is valid to this pattern - * false - not valid - */ - public boolean parse(Context context, PatternElement terminator); - - /** - * Check next subsequence - * - * @param source source text - * @return true pattern sequence equals with next subsequence - * false not equals - */ - public boolean isNextIn(Source source); - - /** - * Find constant - * - * @param source text source - * @return constant offset - */ - public int findIn(Source source); -} diff --git a/src/ru/perm/kefir/bbcode/Scope.java b/src/ru/perm/kefir/bbcode/Scope.java @@ -1,145 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.util.*; - -/** - * bb-code scope. Required for tables, for example. - * Scope contains code set for parsing text. - * - * @author Vitaliy Samolovskih aka Kefir - */ -public class Scope { - /** - * Default name for root scope. If ROOT scope not defined in configuration - * then all codes add to defqult ROOT scope. - */ - public static final String ROOT = "ROOT"; - - /** - * Name of scope - */ - private final String name; - - /** - * Parent scope for inherit codes - */ - private Scope parent = null; - - /** - * Code set for current scope without parent scope codes - */ - private Set<? extends AbstractCode> scopeCodes = null; - - /** - * Mark that not parsiable text must not append to result - */ - private boolean ignoreText = false; - - /** - * Code of scope include the parent codes - */ - private List<AbstractCode> cachedCodes = null; - - /** - * Mark that this scope is initialized - */ - private boolean initialized = false; - - /** - * Create scope - * - * @param name name of scope - */ - public Scope(String name) { - this.name = name; - } - - /** - * Get scope name - * - * @return scope name - */ - public String getName() { - return name; - } - - /** - * Set parent scope - * - * @param parent parent scope. All parent scope code added to scope codes. - */ - public void setParent(Scope parent) { - this.parent = parent; - cacheCodes(); - } - - /** - * Add codes to scope - * - * @param codes code set - */ - public void setScopeCodes(Set<? extends AbstractCode> codes) { - this.scopeCodes = codes; - cacheCodes(); - initialized = true; - } - - /** - * Return all scope codes include parent codes. - * - * @return list of codes in priority order. - */ - public List<AbstractCode> getCodes() { - if (!initialized) { - throw new IllegalStateException("Scope is not initialized."); - } - return cachedCodes; - } - - /** - * Cache scope codes. Join scope codes with parent scope codes. - */ - private void cacheCodes() { - List<AbstractCode> list = new ArrayList<AbstractCode>(); - if (parent != null) { - list.addAll(parent.getCodes()); - } - if (scopeCodes != null) { - list.addAll(scopeCodes); - } - Collections.sort(list, - new Comparator<AbstractCode>() { - public int compare(AbstractCode code1, AbstractCode code2) { - return code2.compareTo(code1); - } - } - ); - cachedCodes = Collections.unmodifiableList(list); - } - - /** - * By default it is false. - * - * @return true if not parsiable text mustn't append to result. - * false if not parsiable text append to result as is. - */ - public boolean isIgnoreText() { - return ignoreText; - } - - /** - * Set flag marked that not parsiable text mustn't append to result. By default it is false. - * - * @param ignoreText flag value - */ - public void setIgnoreText(boolean ignoreText) { - this.ignoreText = ignoreText; - } - - /** - * @return true if scope was initialised, false otherwise - */ - public boolean isInitialized() { - return initialized; - } -} diff --git a/src/ru/perm/kefir/bbcode/Source.java b/src/ru/perm/kefir/bbcode/Source.java @@ -1,208 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Класс источник для парсинга BB-кодов - * - * @author Kefir - */ -public class Source { - private static final int BUFF_SIZE = 4096; - - /** - * Текст ждля парсинга - */ - private final CharSequence text; - private final int textLength; - - /** - * Смещение - */ - private int offset = 0; - private char currentChar; - - /** - * Создает класс источник - * - * @param text исходный текст - */ - public Source(CharSequence text) { - this.text = text; - textLength = text.length(); - updateCurrentChar(); - } - - public int find(String value) { - if (text instanceof String) { - return ((String) text).indexOf(value, offset); - } else if (text instanceof StringBuilder) { - return ((StringBuilder) text).indexOf(value, offset); - } else if (text instanceof StringBuffer) { - return ((StringBuffer) text).indexOf(value, offset); - } else { - int inCharSequence = findInCharSequence(text.subSequence(offset, textLength), value); - if (inCharSequence >= 0) { - return offset + inCharSequence; - } else { - return -1; - } - } - } - - /** - * Find value in character sequence - * - * @param sequence character sequence - * @param value searched value - * @return index of value in sequence - */ - private int findInCharSequence(CharSequence sequence, String value) { - if (value.length()==0) { - throw new IllegalArgumentException("Argument value can't be empty."); - } - - final int seqLength = sequence.length(); - final int valLength = value.length(); - - if (seqLength < valLength) { - return -1; - } - - int index; - int size; - - int nextSize = Math.max(BUFF_SIZE, valLength); - do { - size = nextSize; - if (size > seqLength) { - size = seqLength; - } - - index = sequence.subSequence(0, size).toString().indexOf(value); - nextSize = 2 * size; - } while (index <= 0 && size < seqLength); - - return index; - } - - /** - * Возвращает следующий симвойл и увеличивает смещение - * - * @return символ - */ - public char next() { - char c = current(); - offset++; - updateCurrentChar(); - return c; - } - - public char current() { - return currentChar; - } - - /** - * Возвращает текущее смещение - * - * @return смещение от начала - */ - public int getOffset() { - return offset; - } - - /** - * Increament offset - */ - public void incOffset() { - offset++; - updateCurrentChar(); - } - - private void updateCurrentChar() { - if (offset < textLength) { - currentChar = text.charAt(offset); - } - } - - /** - * Увеличивает смещение - * - * @param increment на сколько нужно увеличить смещение - */ - public void incOffset(int increment) { - offset += increment; - updateCurrentChar(); - } - - /** - * Устанавливает смещение - * - * @param offset смещение - */ - public void setOffset(int offset) { - this.offset = offset; - updateCurrentChar(); - } - - /** - * Есть ли еще что-то в строке - * - * @return true - если есть - * false если достигнут конец строки - */ - public boolean hasNext() { - return offset < textLength; - } - - /** - * Есть ли еще count символов в строке - * - * @param count количчество символов которое должно остаться в строке - * @return true - если есть - * false если достигнут конец строки - */ - public boolean hasNext(int count) { - return (textLength - offset) >= count; - } - - /** - * Return length of sorce text - * - * @return length of source text - */ - public int getLength() { - return textLength; - } - - /** - * Получает строку от текущего смещения до значения <code>end</code> - * - * @param end последний индекс - * @return подстрока - */ - public CharSequence sub(int end) { - return text.subSequence(getOffset(), end); - } - - /** - * Get String from offset to offset+valueLength - * - * @param count length of extracted string - * @return string - */ - public CharSequence subTo(int count) { - return sub(getOffset() + count); - } - - /** - * Get String from offset to end - * - * @return string - */ - public CharSequence subToEnd() { - return sub(textLength); - } - - public String toString() { - return "ru.perm.kefir.bbcode.Source,length:" + String.valueOf(textLength); - } -} diff --git a/src/ru/perm/kefir/bbcode/Template.java b/src/ru/perm/kefir/bbcode/Template.java @@ -1,44 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -/** - * Code template - * - * @author Vitaliy Samolovskih aka Kefir - */ -public class Template { - /** - * Empty template - */ - @SuppressWarnings({"unchecked"}) - public static final Template EMPTY = new Template(Collections.EMPTY_LIST); - - /** - * Template elemnts - */ - private final List<? extends TemplateElement> elements; - - /** - * Create neq template with elements. - * - * @param elements template elements. - */ - public Template(List<? extends TemplateElement> elements) { - this.elements = Collections.unmodifiableList(elements); - } - - /** - * Append to result string processed text. - * - * @param context current context. - * @throws IOException if can't append. - */ - public void generate(Context context) throws IOException { - for (TemplateElement element : elements) { - context.getTarget().append(element.generate(context)); - } - } -} diff --git a/src/ru/perm/kefir/bbcode/TemplateElement.java b/src/ru/perm/kefir/bbcode/TemplateElement.java @@ -1,14 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * The element of template to build target text - */ -public interface TemplateElement { - /** - * Append template element to source of context - * - * @param context контекст - * @return builded text - */ - public CharSequence generate(Context context); -} diff --git a/src/ru/perm/kefir/bbcode/Text.java b/src/ru/perm/kefir/bbcode/Text.java @@ -1,91 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.IOException; - -/** - * Класс текста, который подлежит парсингу - * - * @author Kefir - */ -public class Text extends NamedElement implements PatternElement { - /** - * Scope defina the codeset for parsing this text - */ - private final Scope scope; - - /** - * Mark that variables getted in element context will be put into parent context - */ - private final boolean transparent; - - /** - * Создает именованный элемент - * - * @param name имя переменной - * @param transparent mark that scope variable must be accessible from parent context - */ - public Text(String name, boolean transparent) { - super(name); - scope = null; - this.transparent = transparent; - } - - public Text(String name, Scope scope, boolean transparent) { - super(name); - this.scope = scope; - this.transparent = transparent; - } - - /** - * Парсит элемент - * - * @param context контекст - * @return true - если удалось распарсить константу - * false - если не удалось - */ - public boolean parse(Context context, PatternElement terminator) { - Context child = new Context(context); - StringBuilder target = new StringBuilder(); - child.setTarget(target); - if (scope != null) { - child.setScope(scope); - } - child.setTerminator(terminator); - try { - child.parse(); - } catch (IOException e) { - // Never because StringBuilder don't throw IOException - } - if(transparent){ - child.mergeWithParent(); - } - setAttribute(context, target); - return true; - } - - /** - * Определяет, что дальше в разбираемой строке находится нужная последовательность - * - * @param source source text - * @return true если следующие символы в строке совпадают с pattern - * false если не совпадают или строка кончилась - */ - public boolean isNextIn(Source source) { - return false; - } - - /** - * Find this element - * - * @param source text source - * @return start offset - */ - public int findIn(Source source) { - return -1; - } - - @Override - public String toString() { - return "text:" + getName(); - } -} diff --git a/src/ru/perm/kefir/bbcode/TextProcessor.java b/src/ru/perm/kefir/bbcode/TextProcessor.java @@ -1,44 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * The interface of text processors - * - * @author Kefir - */ -public interface TextProcessor { - /** - * Process the text. - * <p/> - * ATTENTION!!! Do not use java.nio.CharBuffer. - * CharBuffer has invalid realization of subSequence methos from interface java.lang.CharSequence - * since 1.6.0_10 version of JRE. http://bugs.sun.com/view_bug.do?bug_id=6795561 - * - * @param source the sourcetext - * @return the result of text processing - */ - public CharSequence process(CharSequence source); - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - */ - public String process(String source); - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - */ - public StringBuilder process(StringBuilder source); - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - */ - public StringBuffer process(StringBuffer source); -} diff --git a/src/ru/perm/kefir/bbcode/TextProcessorAdapter.java b/src/ru/perm/kefir/bbcode/TextProcessorAdapter.java @@ -1,53 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Text Processor adapter implement methods for String, StringBuffer, StringBuilder - * - * @author Vitaliy Samolovskih aka Kefir - */ -public abstract class TextProcessorAdapter implements TextProcessor { - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - */ - public String process(String source) { - CharSequence result = process((CharSequence) source); - if (result instanceof String) { - return (String) result; - } else { - return result.toString(); - } - } - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - */ - public StringBuilder process(StringBuilder source) { - CharSequence result = process((CharSequence) source); - if (result instanceof StringBuilder) { - return (StringBuilder) result; - } else { - return new StringBuilder(result); - } - } - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - */ - public StringBuffer process(StringBuffer source) { - CharSequence result = process((CharSequence) source); - if (result instanceof StringBuffer) { - return (StringBuffer) result; - } else { - return new StringBuffer(result); - } - } -} diff --git a/src/ru/perm/kefir/bbcode/TextProcessorChain.java b/src/ru/perm/kefir/bbcode/TextProcessorChain.java @@ -1,35 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.util.Collections; -import java.util.List; - -/** - * Chain of text processors wich process text serially - * - * @author Kefir - */ -public class TextProcessorChain extends TextProcessorAdapter { - /** - * List of processors - */ - private final List<? extends TextProcessor> processors; - - public TextProcessorChain(List<? extends TextProcessor> processors) { - this.processors = Collections.unmodifiableList(processors); - } - - /** - * Process the text - * - * @param source the sourcetext - * @return the result of text processing - * @see TextProcessor#process(CharSequence) - */ - public CharSequence process(CharSequence source) { - CharSequence target = source; - for (TextProcessor processor : processors) { - target = processor.process(target); - } - return target; - } -} diff --git a/src/ru/perm/kefir/bbcode/TextProcessorFactory.java b/src/ru/perm/kefir/bbcode/TextProcessorFactory.java @@ -1,16 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * The TextProcessor factory interface - * - * @author Kefir - */ -public interface TextProcessorFactory { - /** - * Create the TextProcessor instance - * - * @return instance of TextProcessor interface - * @throws TextProcessorFactoryException when factory can't create the TextProcessor instance - */ - public TextProcessor create(); -} diff --git a/src/ru/perm/kefir/bbcode/TextProcessorFactoryException.java b/src/ru/perm/kefir/bbcode/TextProcessorFactoryException.java @@ -1,24 +0,0 @@ -package ru.perm.kefir.bbcode; - -/** - * Exception if TextProcessorFactory can't create the TextProcessor instance - * - * @author Kefir - */ -public class TextProcessorFactoryException extends RuntimeException { - public TextProcessorFactoryException() { - super(); - } - - public TextProcessorFactoryException(String message) { - super(message); - } - - public TextProcessorFactoryException(String message, Throwable cause) { - super(message, cause); - } - - public TextProcessorFactoryException(Throwable cause) { - super(cause); - } -} diff --git a/src/ru/perm/kefir/bbcode/Util.java b/src/ru/perm/kefir/bbcode/Util.java @@ -1,38 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.io.InputStream; -import java.util.UUID; - -/** - * Util class - * - * @author Vitaliy Samolovskih aka Kefir - */ -final class Util { - private Util() { - } - - /** - * Open the resource stream for named resource. - * Stream must be closed by user after usage. - * - * @param resourceName resource name - * @return input stream - */ - static InputStream openResourceStream(String resourceName) { - InputStream stream = null; - ClassLoader classLoader = Util.class.getClassLoader(); - if (classLoader != null) { - stream = classLoader.getResourceAsStream(resourceName); - } - - if (stream == null) { - stream = ClassLoader.getSystemResourceAsStream(resourceName); - } - return stream; - } - - static String generateRandomName() { - return UUID.randomUUID().toString(); - } -} diff --git a/src/ru/perm/kefir/bbcode/Variable.java b/src/ru/perm/kefir/bbcode/Variable.java @@ -1,115 +0,0 @@ -package ru.perm.kefir.bbcode; - -import java.util.regex.Matcher; - -/** - * Класс переменной - * - * @author Kefir - */ -public class Variable extends NamedElement implements PatternElement { - private final java.util.regex.Pattern regex; - - /** - * Создает именованную переменную - * - * @param name название переменной - */ - public Variable(String name) { - super(name); - regex = null; - } - - /** - * Create named variable - * - * @param name variable name - * @param regex regular expression pattern - */ - public Variable(String name, java.util.regex.Pattern regex) { - super(name); - this.regex = regex; - } - - /** - * Парсит элемент - * - * @param context контекст - * @return true - если удалось распарсить константу - * false - если не удалось - */ - public boolean parse(Context context, PatternElement terminator) { - int end; - if (terminator != null) { - end = terminator.findIn(context.getSource()); - } else { - end = context.getSource().getLength(); - } - - if (end < 0) { - return false; - } - - Source source = context.getSource(); - CharSequence value = source.sub(end); - - // If define regex, then find this regex in value - if (regex != null) { - Matcher matcher = regex.matcher(value); - if (matcher.lookingAt()) { - int lend = matcher.end(); - end = source.getOffset() + lend; - value = value.subSequence(0, lend); - } else { - return false; - } - } - - // Test this variable already defined and equals with this in this code scope - Object attr = context.getLocalAttribute(getName()); - if (attr == null || attr.equals(value)) { - if(attr==null){ - setAttribute(context, value); - } - source.setOffset(end); - return true; - } else { - return false; - } - } - - /** - * Определяет, что дальше в разбираемой строке находится нужная последовательность - * - * @param source source text - * @return true если следующие символы в строке совпадают с pattern - * false если не совпадают или строка кончилась - */ - public boolean isNextIn(Source source) { - return regex != null && regex.matcher(source.subToEnd()).lookingAt(); - } - - /** - * Find this element - * - * @param source text source - * @return start offset - */ - public int findIn(Source source) { - if (regex != null) { - Matcher matcher = regex.matcher(source.subToEnd()); - if (matcher.find()) { - return matcher.start(); - } else { - return -1; - } - } else { - return -1; - } - } - - @Override - public String toString() { - return "variable:" + getName(); - } -} diff --git a/src/ru/perm/kefir/bbcode/default.xml b/src/ru/perm/kefir/bbcode/default.xml @@ -1,236 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns="http://kefir-bb.sourceforge.net/schemas" - xsi:schemaLocation="http://kefir-bb.sourceforge.net/schemas http://kefir-bb.sourceforge.net/schemas/kefir-bb-0.5.xsd"> - <!-- XML escape symbols --> - <scope name="escapeXml"> - <code priority="100"> - <pattern>&amp;</pattern> - <template>&amp;amp;</template> - </code> - <code priority="100"> - <pattern>&apos;</pattern> - <template>&amp;apos;</template> - </code> - <code priority="100"> - <pattern>&lt;</pattern> - <template>&amp;lt;</template> - </code> - <code priority="100"> - <pattern>&gt;</pattern> - <template>&amp;gt;</template> - </code> - <code priority="100"> - <pattern>&quot;</pattern> - <template>&amp;quot;</template> - </code> - </scope> - - <!-- Scope for escaping bb spec chars --> - <scope name="escapeBb" parent="escapeXml"> - <!-- - Escape bb-code symbols - double slash to slash - slash + square bracket to square bracket - --> - <code name="slash" priority="10"> - <pattern>\\</pattern> - <template>\</template> - </code> - <code name="left_square_bracket" priority="9"> - <pattern>\[</pattern> - <template>[</template> - </code> - <code name="right_square_bracket" priority="9"> - <pattern>\]</pattern> - <template>]</template> - </code> - <coderef name="comment"/> - </scope> - - <!-- Comment --> - <code name="comment"> - <pattern>[*<var parse="false"/>*]</pattern> - <template/> - </code> - - <!-- Escape basic HTML char sequences --> - <scope name="basic" parent="escapeBb"> - <!-- line feed characters --> - <code name="br1" priority="3"> - <pattern>&#x0A;&#x0D;</pattern> - <template>&lt;br/&gt;</template> - </code> - <code name="br2" priority="2"> - <pattern>&#x0D;&#x0A;</pattern> - <template>&lt;br/&gt;</template> - </code> - <code name="br3" priority="1"> - <pattern>&#x0A;</pattern> - <template>&lt;br/&gt;</template> - </code> - <code name="br4" priority="0"> - <pattern>&#x0D;</pattern> - <template>&lt;br/&gt;</template> - </code> - - <!-- Special html symbols --> - <code name="symbol"> - <pattern>[symbol=<var scope="escapeXml"/>/]</pattern> - <template>&amp;<var/>;</template> - </code> - - <!-- angle quotes --> - <code name="aquote"> - <pattern>[aquote]<var inherit="true"/>[/aquote]</pattern> - <template>&amp;laquo;<var/>&amp;raquo;</template> - </code> - </scope> - - <!-- Root scope. This scope uses when processor started work and by default, if not set other scope --> - <scope name="ROOT" parent="basic"> - <!-- Formatting --> - <coderef name="bold"/> - <coderef name="u"/> - <coderef name="s"/> - <coderef name="i"/> - <coderef name="color"/> - <coderef name="size"/> - - <!-- Quotes --> - <coderef name="code"/> - <coderef name="quote"/> - - <!-- Images --> - <coderef name="img1"/> - <coderef name="img2"/> - - <!-- links --> - <coderef name="url1"/> - <coderef name="url2"/> - <coderef name="url3"/> - <coderef name="url4"/> - <coderef name="url5"/> - <coderef name="url6"/> - - <!-- Table --> - <coderef name="table"/> - </scope> - - <!-- Simple formatting --> - <code name="bold"> - <pattern>[b]<var inherit="true"/>[/b]</pattern> - <template>&lt;span style=&quot;font-weight:bold;&quot;&gt;<var/>&lt;/span&gt;</template> - </code> - <code name="u"> - <pattern>[u]<var inherit="true"/>[/u]</pattern> - <template>&lt;span style=&quot;text-decoration:underline;&quot;&gt;<var/>&lt;/span&gt;</template> - </code> - <code name="s"> - <pattern>[s]<var inherit="true"/>[/s]</pattern> - <template>&lt;span style=&quot;text-decoration:line-through;&quot;&gt;<var/>&lt;/span&gt;</template> - </code> - <code name="i"> - <pattern>[i]<var inherit="true"/>[/i]</pattern> - <template>&lt;span style=&quot;font-style:italic;&quot;&gt;<var/>&lt;/span&gt;</template> - </code> - - <!-- Font color --> - <code name="color"> - <pattern>[color=<var name="color" scope="escapeXml"/>]<var name="text" inherit="true"/>[/color]</pattern> - <template>&lt;span style=&quot;color:<var name="color"/>;&quot;&gt;<var name="text"/>&lt;/span&gt;</template> - </code> - - <!-- Font size --> - <code name="size"> - <pattern>[size=<var name="size" scope="escapeXml"/>]<var name="text" inherit="true"/>[/size]</pattern> - <template>&lt;span style=&quot;font-size:<var name="size"/>;&quot;&gt;<var name="text"/>&lt;/span&gt;</template> - </code> - - <!-- Insert image --> - <code name="img1" priority="2"> - <pattern>[img]<var name="protocol" regex="((ht|f)tps?:|\.{1,2})?"/>/<var name="addr" scope="escapeXml"/>[/img]</pattern> - <template>&lt;img src=&quot;<var name="protocol"/>/<var name="addr"/>&quot;/&gt;</template> - </code> - <code name="img2" priority="1"> - <pattern>[img]<var name="addr" scope="escapeXml"/>[/img]</pattern> - <template>&lt;img src=&quot;http://<var name="addr"/>&quot;/&gt;</template> - </code> - - <!-- Links. http, https, malto protocols --> - <scope name="url" parent="basic"> - <coderef name="bold"/> - <coderef name="u"/> - <coderef name="s"/> - <coderef name="i"/> - <coderef name="color"/> - <coderef name="size"/> - - <coderef name="img1"/> - <coderef name="img2"/> - </scope> - - <!-- HTTP --> - <code name="url1" priority="2"> - <pattern>[url=<var name="protocol" regex="((ht|f)tps?:|\.{1,2})?"/>/<var name="url" scope="escapeXml"/>]<var name="text" scope="url"/>[/url]</pattern> - <template>&lt;a href=&quot;<var name="protocol"/>/<var name="url"/>&quot;&gt;<var name="text"/>&lt;/a&gt;</template> - </code> - <code name="url2" priority="2"> - <pattern>[url]<var name="protocol" regex="((ht|f)tps?:|\.{1,2})?"/>/<var name="url" scope="escapeXml"/>[/url]</pattern> - <template>&lt;a href=&quot;<var name="protocol"/>/<var name="url"/>&quot;&gt;<var name="protocol"/>/<var name="url"/>&lt;/a&gt;</template> - </code> - <code name="url3" priority="1"> - <pattern>[url=<var name="url" scope="escapeXml"/>]<var name="text" scope="url"/>[/url]</pattern> - <template>&lt;a href=&quot;http://<var name="url"/>&quot;&gt;<var name="text"/>&lt;/a&gt;</template> - </code> - <code name="url4" priority="1"> - <pattern>[url]<var name="url" scope="escapeXml"/>[/url]</pattern> - <template>&lt;a href=&quot;http://<var name="url"/>&quot;&gt;<var name="url"/>&lt;/a&gt;</template> - </code> - - <!-- MAILTO --> - <code name="url5" priority="2"> - <pattern>[url=mailto:<var name="url" scope="escapeXml"/>]<var name="text" scope="url"/>[/url]</pattern> - <template>&lt;a href=&quot;mailto:<var name="url"/>&quot;&gt;<var name="text"/>&lt;/a&gt;</template> - </code> - <code name="url6" priority="2"> - <pattern>[url]mailto:<var name="url" scope="escapeXml"/>[/url]</pattern> - <template>&lt;a href=&quot;mailto:<var name="url"/>&quot;&gt;mailto:<var name="url"/>&lt;/a&gt;</template> - </code> - - <!-- Qote block --> - <code name="quote"> - <pattern>[quote]<var inherit="true"/>[/quote]</pattern> - <template>&lt;em&gt;<var/>&lt;/em&gt;</template> - </code> - - <!-- Quote code block --> - <code name="code"> - <pattern>[code]<var scope="basic"/>[/code]</pattern> - <template>&lt;pre&gt;<var/>&lt;/pre&gt;</template> - </code> - - <!-- Simple table --> - <code name="table"> - <pattern>[table]<var scope="tableScope"/>[/table]</pattern> - <template>&lt;table&gt;<var/>&lt;/table&gt;</template> - </code> - <scope name="tableScope" ignoreText="true"> - <code name="tr"> - <pattern>[tr]<var scope="trScope"/>[/tr]</pattern> - <template>&lt;tr&gt;<var/>&lt;/tr&gt;</template> - </code> - <coderef name="comment"/> - </scope> - <scope name="trScope" ignoreText="true"> - <code name="th"> - <pattern>[th]<var/>[/th]</pattern> - <template>&lt;th&gt;<var/>&lt;/th&gt;</template> - </code> - <code name="td"> - <pattern>[td]<var/>[/td]</pattern> - <template>&lt;td&gt;<var/>&lt;/td&gt;</template> - </code> - <coderef name="comment"/> - </scope> -</configuration>- \ No newline at end of file