001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.parsers.bbcode;
016    
017    import com.liferay.portal.kernel.util.GetterUtil;
018    import com.liferay.portal.kernel.util.IntegerWrapper;
019    import com.liferay.portal.kernel.util.SetUtil;
020    
021    import java.util.ArrayList;
022    import java.util.List;
023    import java.util.Set;
024    import java.util.Stack;
025    
026    /**
027     * @author Iliyan Peychev
028     */
029    public class BBCodeParser {
030    
031            public static final int TYPE_DATA = 0x04;
032    
033            public static final int TYPE_TAG_END = 0x02;
034    
035            public static final int TYPE_TAG_START = 0x01;
036    
037            public static final int TYPE_TAG_START_END = TYPE_TAG_START | TYPE_TAG_END;
038    
039            public BBCodeParser() {
040                    _blockElements = SetUtil.fromArray(
041                            new String[] {
042                                    "*", "center", "code", "justify", "left", "li", "list", "q",
043                                    "quote", "right", "table", "td", "th", "tr"
044                            });
045    
046                    _inlineElements = SetUtil.fromArray(
047                            new String[] {
048                                    "b", "color", "font", "i", "img", "s", "size", "u", "url"
049                            });
050    
051                    _selfCloseElements = SetUtil.fromArray(new String[] {"*"});
052            }
053    
054            public List<BBCodeItem> parse(String text) {
055                    List<BBCodeItem> bbCodeItems = new ArrayList<BBCodeItem>();
056    
057                    BBCodeLexer bbCodeLexer = new BBCodeLexer(text);
058                    Stack<String> tags = new Stack<String>();
059                    IntegerWrapper marker = new IntegerWrapper();
060    
061                    BBCodeToken bbCodeToken = null;
062    
063                    while ((bbCodeToken = bbCodeLexer.getNextBBCodeToken()) != null) {
064                            handleData(bbCodeItems, bbCodeLexer, marker, bbCodeToken, text);
065    
066                            if (bbCodeToken.getStartTag() == null) {
067                                    handleTagEnd(bbCodeItems, tags, bbCodeToken);
068    
069                                    continue;
070                            }
071    
072                            handleTagStart(bbCodeItems, tags, bbCodeToken);
073    
074                            String startTag = bbCodeToken.getStartTag();
075    
076                            if (!startTag.equals("code")) {
077                                    continue;
078                            }
079    
080                            while (true) {
081                                    bbCodeToken = bbCodeLexer.getNextBBCodeToken();
082    
083                                    if (bbCodeToken == null) {
084                                            break;
085                                    }
086    
087                                    String endTag = GetterUtil.getString(bbCodeToken.getEndTag());
088    
089                                    if (endTag.equals("code")) {
090                                            break;
091                                    }
092                            }
093    
094                            handleData(bbCodeItems, bbCodeLexer, marker, bbCodeToken, text);
095    
096                            if (bbCodeToken != null) {
097                                    handleTagEnd(bbCodeItems, tags, bbCodeToken);
098                            }
099                            else {
100                                    break;
101                            }
102                    }
103    
104                    handleData(bbCodeItems, bbCodeLexer, marker, null, text);
105                    handleTagEnd(bbCodeItems, tags, null);
106    
107                    return bbCodeItems;
108            }
109    
110            protected void handleData(
111                    List<BBCodeItem> bbCodeItems, BBCodeLexer bbCodeLexer,
112                    IntegerWrapper marker, BBCodeToken bbCodeToken, String data) {
113    
114                    int length = data.length();
115    
116                    int lastIndex = length;
117    
118                    if (bbCodeToken != null) {
119                            length = bbCodeToken.getStart();
120    
121                            lastIndex = bbCodeLexer.getLastIndex();
122                    }
123    
124                    if (length > marker.getValue()) {
125                            BBCodeItem bbCodeItem = new BBCodeItem(
126                                    TYPE_DATA, null, data.substring(marker.getValue(), length));
127    
128                            bbCodeItems.add(bbCodeItem);
129                    }
130    
131                    marker.setValue(lastIndex);
132            }
133    
134            protected void handleTagEnd(
135                    List<BBCodeItem> bbCodeItems, Stack<String> tags,
136                    BBCodeToken bbCodeToken) {
137    
138                    int size = 0;
139    
140                    if (bbCodeToken != null) {
141                            String endTag = bbCodeToken.getEndTag();
142    
143                            for (size = tags.size() - 1; size >= 0; size--) {
144                                    if (endTag.equals(tags.elementAt(size))) {
145                                            break;
146                                    }
147                            }
148                    }
149    
150                    if (size >= 0) {
151                            for (int i = tags.size() - 1; i >= size; i--) {
152                                    BBCodeItem bbCodeItem = new BBCodeItem(
153                                            TYPE_TAG_END, null, tags.elementAt(i));
154    
155                                    bbCodeItems.add(bbCodeItem);
156                            }
157    
158                            tags.setSize(size);
159                    }
160            }
161    
162            protected void handleTagStart(
163                    List<BBCodeItem> bbCodeItems, Stack<String> tags,
164                    BBCodeToken bbCodeToken) {
165    
166                    String startTag = bbCodeToken.getStartTag();
167    
168                    if (_blockElements.contains(startTag)) {
169                            String currentTag = null;
170    
171                            while (!tags.isEmpty() &&
172                                       ((currentTag = tags.lastElement()) != null) &&
173                                       _inlineElements.contains(currentTag)) {
174    
175                                    BBCodeToken currentTagBBCodeToken = new BBCodeToken(currentTag);
176    
177                                    handleTagEnd(bbCodeItems, tags, currentTagBBCodeToken);
178                            }
179                    }
180    
181                    if (_selfCloseElements.contains(startTag) &&
182                            startTag.equals(tags.lastElement())) {
183    
184                            BBCodeToken tagBBCodeToken = new BBCodeToken(startTag);
185    
186                            handleTagEnd(bbCodeItems, tags, tagBBCodeToken);
187                    }
188    
189                    tags.push(startTag);
190    
191                    BBCodeItem bbCodeItem = new BBCodeItem(
192                            TYPE_TAG_START, bbCodeToken.getAttribute(), startTag);
193    
194                    bbCodeItems.add(bbCodeItem);
195            }
196    
197            private Set<String> _blockElements;
198            private Set<String> _inlineElements;
199            private Set<String> _selfCloseElements;
200    
201    }