001: /******************************************************************************
002: * JBoss, a division of Red Hat *
003: * Copyright 2006, Red Hat Middleware, LLC, and individual *
004: * contributors as indicated by the @authors tag. See the *
005: * copyright.txt in the distribution for a full listing of *
006: * individual contributors. *
007: * *
008: * This is free software; you can redistribute it and/or modify it *
009: * under the terms of the GNU Lesser General Public License as *
010: * published by the Free Software Foundation; either version 2.1 of *
011: * the License, or (at your option) any later version. *
012: * *
013: * This software is distributed in the hope that it will be useful, *
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of *
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
016: * Lesser General Public License for more details. *
017: * *
018: * You should have received a copy of the GNU Lesser General Public *
019: * License along with this software; if not, write to the Free *
020: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
021: * 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
022: ******************************************************************************/package org.jboss.portal.format.parser.bbcode;
023:
024: import org.jboss.portal.format.parser.AbstractParser;
025: import org.jboss.portal.format.parser.ParseEvent;
026: import org.jboss.portal.format.parser.TextEvent;
027: import org.jboss.portal.format.parser.Token;
028: import org.jboss.portal.format.parser.chars.MutableChars;
029: import org.jboss.portal.format.util.Stack;
030:
031: import java.io.IOException;
032: import java.io.Reader;
033: import java.util.Iterator;
034:
035: /**
036: * This nasty class parse BB code and create events.
037: *
038: * @author <a href="mailto:julien@jboss.org">Julien Viet</a>
039: * @version $Revision: 8784 $
040: */
041: public class BBCodeParser extends AbstractParser {
042:
043: // Public constants
044:
045: public static final int EVENT_NORMAL = 0;
046: public static final int EVENT_BOLD = 1;
047: public static final int EVENT_ITALIC = 2;
048: public static final int EVENT_UNDERLINE = 3;
049: public static final int EVENT_COLOR = 4;
050: public static final int EVENT_SIZE = 5;
051: public static final int EVENT_QUOTE = 6;
052: public static final int EVENT_CODE = 7;
053: public static final int EVENT_ITEM = 8;
054: public static final int EVENT_LINK = 9;
055: public static final int EVENT_UNORDERED_LIST = 10;
056: public static final int EVENT_ALPHABETICALLY_ORDERED_LIST = 11;
057: public static final int EVENT_NUMERICALLY_ORDERED_LIST = 12;
058:
059: public static final String BUNDLE_KEY_CODE = "Message_code";
060: public static final String BUNDLE_KEY_QUOTE = "Message_quote";
061: public static final String BUNDLE_KEY_WROTE = "Message_wrote";
062:
063: // The parser state
064:
065: public static final Reader NULL_READER = new Reader() {
066: public int read(char cbuf[], int off, int len)
067: throws IOException {
068: return 0;
069: }
070:
071: public void close() throws IOException {
072: }
073: };
074:
075: private Analyzer analyzer = new Analyzer(BBCodeParser.NULL_READER);
076: private CodeKey myKey = new CodeKey();
077: private MutableChars buffer = new MutableChars();
078: private TextEvent textEvent = new TextEvent();
079: private OpenEvent openEvent = new OpenEvent();
080:
081: private Stack stack = new Stack(10) {
082: protected Stack.Key createKey() {
083: return new CloseEvent();
084: }
085:
086: protected boolean equals(Stack.Key key1, Stack.Key key2) {
087: return ((CodeKey) key1).getType() == ((CodeKey) key2)
088: .getType();
089: }
090: };
091:
092: public BBCodeParser() {
093: }
094:
095: public void parse(char[] chars, int offset, int length) {
096: // First we initialize the parser state
097: stack.reset();
098: analyzer.reset(chars, offset, length);
099: buffer.reset();
100:
101: // First
102: _start(EVENT_NORMAL, null);
103:
104: // Enter the switch loop
105: while (true) {
106: // Get the next token
107: Token t = analyzer.next();
108:
109: if (t == null) {
110: // No more tokens to read
111: break;
112: }
113:
114: // According to the token we fire the approriate events
115: switch (t.type) {
116: case Analyzer.OPEN_B:
117: _start(EVENT_BOLD, null);
118: break;
119: case Analyzer.CLOSE_B:
120: _end(EVENT_BOLD);
121: break;
122:
123: case Analyzer.OPEN_I:
124: _start(EVENT_ITALIC, null);
125: break;
126: case Analyzer.CLOSE_I:
127: _end(EVENT_ITALIC);
128: break;
129:
130: case Analyzer.OPEN_U:
131: _start(EVENT_UNDERLINE, null);
132: break;
133: case Analyzer.CLOSE_U:
134: _end(EVENT_UNDERLINE);
135: break;
136:
137: case Analyzer.OPEN_COLOR:
138: _start(EVENT_COLOR, t.value);
139: break;
140: case Analyzer.CLOSE_COLOR:
141: _end(EVENT_COLOR);
142: break;
143:
144: case Analyzer.OPEN_SIZE:
145: _start(EVENT_SIZE, t.value);
146: break;
147: case Analyzer.CLOSE_SIZE:
148: _end(EVENT_SIZE);
149: break;
150:
151: case Analyzer.OPEN_QUOTE_ANONYMOUS:
152: _start(EVENT_QUOTE, null);
153: break;
154: case Analyzer.OPEN_QUOTE:
155: _start(EVENT_QUOTE, t.value);
156: break;
157: case Analyzer.CLOSE_QUOTE:
158: _end(EVENT_QUOTE);
159: break;
160:
161: case Analyzer.OPEN_CODE:
162: _start(EVENT_CODE, null);
163: break;
164: case Analyzer.CLOSE_CODE:
165: _end(EVENT_CODE);
166: break;
167:
168: case Analyzer.OPEN_LIST_UNORDERED:
169: _start(EVENT_UNORDERED_LIST, null);
170: break;
171: case Analyzer.OPEN_LIST_ORDERED_NUMERICAL:
172: _start(EVENT_NUMERICALLY_ORDERED_LIST, null);
173: break;
174: case Analyzer.OPEN_LIST_ORDERED_ALPHABETICAL:
175: _start(EVENT_ALPHABETICALLY_ORDERED_LIST, null);
176: break;
177:
178: case Analyzer.CLOSE_LIST:
179: // If we match a list token on the stack at
180: // level -1 (because of the list item that should
181: // be pushed on the stack) we close
182: CodeKey tmp = (CodeKey) stack.peek(0);
183: if (tmp != null) {
184: int type = tmp.type;
185: if (type == EVENT_UNORDERED_LIST
186: || type == EVENT_NUMERICALLY_ORDERED_LIST
187: || type == EVENT_ALPHABETICALLY_ORDERED_LIST) {
188: _end(type);
189: }
190: }
191: break;
192:
193: case Analyzer.LIST_ITEM:
194: // If we have a sibling item close it
195: CodeKey tmp2 = (CodeKey) stack.peek(0);
196: if (tmp2 != null && tmp2.type == EVENT_ITEM) {
197: _end(EVENT_ITEM);
198: }
199: _start(EVENT_ITEM, null);
200: break;
201:
202: case Analyzer.LINK:
203: int bracket = t.value.indexOf(']');
204: if (bracket > 0) {
205: boolean hasEquals = t.value.charAt(0) == '=';
206: if (hasEquals) {
207: // todo add support for the link value which is not used yet
208: String url = t.value.substring(1, bracket);
209: String link = t.value.substring(bracket + 1);
210: _start(EVENT_LINK, url);
211: _end(EVENT_LINK);
212: }
213: } else {
214: _start(EVENT_LINK, t.value.substring(1));
215: _end(EVENT_LINK);
216: }
217:
218: break;
219:
220: case Analyzer.TEXT:
221: buffer.append(t.value.charAt(0));
222: break;
223:
224: default:
225: throw new IllegalStateException(
226: "This should not be possible");
227: }
228: }
229:
230: // Last
231: _end(EVENT_NORMAL);
232: }
233:
234: private void _text() {
235: if (buffer.length() > 0) {
236: textEvent.setText(buffer.chars(), 0, buffer.length());
237: buffer.reset();
238: handler.handle(textEvent);
239: }
240: }
241:
242: private void _start(int type, String string) {
243: _text();
244: CloseEvent closeEvent = (CloseEvent) stack.push();
245: openEvent.type = closeEvent.type = type;
246: openEvent.string = string;
247: handler.handle(openEvent);
248: }
249:
250: private void _end(int type) {
251: myKey.type = type;
252: Iterator i = stack.pop(myKey);
253: if (i.hasNext()) {
254: _text();
255: do {
256: handler.handle((CloseEvent) i.next());
257: } while (i.hasNext());
258: }
259: }
260:
261: private static class CodeKey implements Stack.Key {
262: protected int type;
263: protected String string;
264:
265: public CodeKey() {
266: type = -1;
267: string = null;
268: }
269:
270: public int getType() {
271: return type;
272: }
273:
274: public String getString() {
275: return string;
276: }
277: }
278:
279: public static class OpenEvent extends CodeKey implements ParseEvent {
280: public String toString() {
281: return "open: " + type + " " + string;
282: }
283: }
284:
285: public static class CloseEvent extends CodeKey implements
286: ParseEvent {
287: public String toString() {
288: return "close: " + type + " " + string;
289: }
290: }
291: }
|