Mercurial Hosting > nabble
view src/nabble/view/web/template/HtmlListNamespace.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children |
line wrap: on
line source
package nabble.view.web.template; import fschmidt.html.Html; import fschmidt.html.HtmlCdata; import fschmidt.html.HtmlScript; import fschmidt.html.HtmlStyle; import fschmidt.html.HtmlTag; import fschmidt.html.HtmlTextContainer; import fschmidt.util.java.HtmlUtils; import fschmidt.util.java.ObjectUtils; import nabble.model.FileUpload; import nabble.model.Message; import nabble.model.ModelHome; import nabble.model.Node; import nabble.model.User; import nabble.naml.compiler.Command; import nabble.naml.compiler.CommandSpec; import nabble.naml.compiler.IPrintWriter; import nabble.naml.compiler.Interpreter; import nabble.naml.compiler.Namespace; import nabble.naml.compiler.ScopedInterpreter; import nabble.naml.namespaces.ListSequence; import nabble.view.lib.Jtp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.regex.Pattern; @Namespace ( name = "html_list", global = true ) public final class HtmlListNamespace extends ListSequence<Object> { private static final Logger logger = LoggerFactory.getLogger(HtmlListNamespace.class); private final Message.Source source; private final Message.Format format; public HtmlListNamespace(Html list,Message.Source source, Message.Format format) { super(list); this.source = source; this.format = format; } public String toString() { return ObjectUtils.join(elements); } public String toMailText() { return htmlToTextMail2(elements); } public static final CommandSpec process_raw_tags = CommandSpec.NO_OUTPUT; @Command public void process_raw_tags(IPrintWriter out,Interpreter interp) { processRaw(elements); } public static final CommandSpec process_cdata_tags = CommandSpec.NO_OUTPUT; @Command public void process_cdata_tags(IPrintWriter out,Interpreter interp) { processCDATA(elements); } public static final CommandSpec process_quotes = CommandSpec.NO_OUTPUT() .scopedParameters("wrote") .optionalParameters("max_quoted_lines") .build() ; @Command public void process_quotes(IPrintWriter out,ScopedInterpreter<AuthorWroteNamespace> interp) { int maxQuotedLines = interp.getArgAsInt("max_quoted_lines",10); processQuotes(elements,maxQuotedLines, interp); } public static final CommandSpec process_quotes_as_text = CommandSpec.NO_OUTPUT() .scopedParameters("wrote") .build() ; @Command public void process_quotes_as_text(IPrintWriter out,ScopedInterpreter<AuthorWroteNamespace> interp) { // In order to avoid complexity, the algorithm should process // just one type of line breaks. Since the desired output is TEXT, // the chosen line break is CRLF. Thus we have to convert <br> // into CRLF below. if (format == Message.Format.HTML) { convertBRIntoCRLF(elements); } // All lines breaks are CRLF now... processQuotesText(elements, 0, interp); } private void convertBRIntoCRLF(List<Object> elements) { for (int i = 0; i < elements.size(); i++) { Object o = elements.get(i); if (o instanceof HtmlTag && ((HtmlTag) o).getName().equals("br")) elements.set(i, "\n"); else if (o instanceof String) { elements.set(i, ((String)o).replaceAll("\r?\n", "")); } } } public static final CommandSpec process_email = CommandSpec.NO_OUTPUT; @Command public void process_email(IPrintWriter out,Interpreter interp) { processEmail(elements, source); } public static final CommandSpec set_target_to_top = CommandSpec.NO_OUTPUT; @Command public void set_target_to_top(IPrintWriter out,Interpreter interp) { setTargetToTop(elements); } public static final CommandSpec add_nofollow = CommandSpec.NO_OUTPUT() .optionalParameters("accept_rel_follow") .build() ; @Command public void add_nofollow(IPrintWriter out,Interpreter interp) { boolean acceptRelFollow = interp.getArgAsBoolean("accept_rel_follow", false); addNofollow(elements, acceptRelFollow); } public static final CommandSpec process_smilies = CommandSpec.NO_OUTPUT; @Command public void process_smilies(IPrintWriter out,Interpreter interp) { processSmilies(elements,true); } public static final CommandSpec process_smilies_as_text = CommandSpec.NO_OUTPUT; @Command public void process_smilies_as_text(IPrintWriter out,Interpreter interp) { processSmilies(elements,false); } public static final CommandSpec process_file_tags = CommandSpec.NO_OUTPUT; @Command public void process_file_tags(IPrintWriter out,Interpreter interp) { FileUpload.processFileTags(elements,source); } // security public static final CommandSpec disable_banned_tags = CommandSpec.NO_OUTPUT() .dotParameter("banned_tags") .optionalParameters("remove") .build() ; @Command public void disable_banned_tags(IPrintWriter out,Interpreter interp) { String[] tags = splitAndTrim(interp.getArgString("banned_tags")); boolean remove = interp.getArgAsBoolean("remove", format.isMail()); disableBannedTags(elements,remove,tags); } public static final CommandSpec disable_invalid_urls = CommandSpec.NO_OUTPUT() .dotParameter("url_attributes") .build() ; @Command public void disable_invalid_urls(IPrintWriter out,Interpreter interp) { String[] attrs = splitAndTrim(interp.getArgString("url_attributes")); disableInvalidUrls(elements,false,attrs); } public static final CommandSpec disable_javascript_urls = CommandSpec.NO_OUTPUT() .dotParameter("url_attributes") .build() ; @Command public void disable_javascript_urls(IPrintWriter out,Interpreter interp) { String[] attrs = splitAndTrim(interp.getArgString("url_attributes")); disableJavascriptUrls(elements,false,attrs); } public static final CommandSpec disable_on_event = CommandSpec.NO_OUTPUT; @Command public void disable_on_event(IPrintWriter out,Interpreter interp) { disableOnEvent(elements,false); } public static final CommandSpec disable_scripts = CommandSpec.NO_OUTPUT; @Command public void disable_scripts(IPrintWriter out,Interpreter interp) { disableScripts(elements,false); } public static final CommandSpec disable_style_blocks = CommandSpec.NO_OUTPUT() .optionalParameters("remove") .build() ; @Command public void disable_style_blocks(IPrintWriter out,Interpreter interp) { boolean remove = interp.getArgAsBoolean("remove", format.isMail()); disableStyleBlocks(elements,remove); } static String[] splitAndTrim(String s) { final String[] a = s.split(","); for( int i=0; i<a.length; i++ ) { a[i] = a[i].trim(); } return a; } public static final CommandSpec process_tag = CommandSpec.NO_OUTPUT() .parameters("tag") .scopedParameters("do") .dotParameter("do") .build() ; @Command public void process_tag(IPrintWriter out,ScopedInterpreter<TagNamespace> interp) { String tagName = interp.getArgString("tag"); for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)curr; if( tag.getName().equals(tagName) ) { TagNamespace ns = new TagNamespace(tag); String replacement = interp.getArgString(ns,"do"); i.set(replacement); } } } } public static final CommandSpec process_text = CommandSpec.NO_OUTPUT() .parameters("text") .dotParameter("replacement") .build() ; @Command public void process_text(IPrintWriter out,Interpreter interp) { String text = interp.getArgString("text"); String replacement = interp.getArgString("replacement"); for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof String ) { String s = (String) curr; if (text.equals(s)) { i.set(replacement); } } } } @Namespace ( name = "html_tag", global = false, transparent = true ) public static final class TagNamespace { private final HtmlTag tag; private TagNamespace(HtmlTag tag) { this.tag = tag; } @Command public void tag_as_string(IPrintWriter out,Interpreter interp) { out.print(tag); } public static final CommandSpec tag_attribute = new CommandSpec.Builder() .dotParameter("name") .build() ; @Command public void tag_attribute(IPrintWriter out,Interpreter interp) { out.print( HtmlTag.unquote(tag.getAttributeValue(interp.getArgString("name"))) ); } public static final CommandSpec tag_has_attribute = new CommandSpec.Builder() .dotParameter("name") .build() ; @Command public void tag_has_attribute(IPrintWriter out,Interpreter interp) { out.print( tag.getAttributeValue(interp.getArgString("name")) != null ); } } public static final CommandSpec process_embed = new CommandSpec.Builder() .dotParameter("regex") .build() ; @Command public void process_embed(IPrintWriter out,Interpreter interp) { Pattern ptn = Pattern.compile( interp.getArgString("regex") ); for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)curr; if( tag.getName().equals("nabble_embed") ) { if( tag.isEmpty() ) { // no good i.set( new Embedded(HtmlUtils.htmlEncode(tag.toString())) ); } else { List<Object> list = new ArrayList<Object>(); while(true) { i.remove(); if( !i.hasNext() ) { // no closing tag list.add(0,tag); elements.add( new Embedded(HtmlUtils.htmlEncode(ObjectUtils.join(list))) ); return; } curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag2 = (HtmlTag)curr; if( tag2.getName().equals("/nabble_embed") ) // done break; } list.add(curr); } String text = ObjectUtils.join(list); if( ptn.matcher(text).matches() ) { // ok i.set( new Embedded(text) ); } else { // not ok list.add(0,tag); list.add(curr); i.set( new Embedded(HtmlUtils.htmlEncode(ObjectUtils.join(list))) ); } } } } } } private static class Embedded { private final String text; Embedded(String text) { this.text = text; } public String toString() { return text; } } // from MessageFormatImpls private static void processRaw(List<Object> list) { for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) { Object o = i.next(); if( o instanceof HtmlTextContainer ) { HtmlTextContainer container = (HtmlTextContainer)o; if( container.startTag.getName().equalsIgnoreCase("raw") ) { HtmlTag preTag = new HtmlTag(container.startTag); preTag.setName("pre"); i.set( new Embedded( preTag.toString() + HtmlUtils.htmlEncode(container.text) + "</pre>" ) ); } } } } private static void processCDATA(List<Object> list) { for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) { Object o = i.next(); if (o instanceof HtmlCdata) { HtmlCdata container = (HtmlCdata) o; container.toString(); i.set("<pre>" + HtmlUtils.htmlEncode("<![CDATA[" + container.text + "]]>") + "</pre>"); } } } private static final HtmlTag blockquote = new HtmlTag("blockquote"); private static final HtmlTag _blockquote = new HtmlTag("/blockquote"); private static final HtmlTag divQuote = new HtmlTag("div"); private static final HtmlTag divQuoteAuthor = new HtmlTag("div"); private static final HtmlTag divQuoteMessage = new HtmlTag("div"); private static final HtmlTag divQuoteMessageHidden = new HtmlTag("div"); private static final HtmlTag _div = new HtmlTag("/div"); static { blockquote.setAttribute("class",HtmlTag.quote("quote dark-border-color")); divQuote.setAttribute("class",HtmlTag.quote("quote light-border-color")); divQuoteAuthor.setAttribute("class",HtmlTag.quote("quote-author")); divQuoteAuthor.setAttribute("style",HtmlTag.quote("font-weight: bold;")); divQuoteMessage.setAttribute("class",HtmlTag.quote("quote-message")); divQuoteMessageHidden.setAttribute("class",HtmlTag.quote("quote-message shrinkable-quote")); } @Namespace ( name = "html_list_author_wrote", global = true ) public static class AuthorWroteNamespace { private final String authorStr; private AuthorWroteNamespace(String authorStr) { this.authorStr = authorStr; } @Command public void author(IPrintWriter out,Interpreter interp) { out.print(authorStr); } } private static class QuoteInfo { final int i; final int nBreaks; QuoteInfo(int i,int nBreaks) { this.i = i; this.nBreaks = nBreaks; } } private static final Set<String> breakingTags = new HashSet<String>(Arrays.asList( "br", "div", "p" )); private static void processQuotes(List<Object> list, int maxQuotedLines, ScopedInterpreter<AuthorWroteNamespace> interp) { processQuotes(list,maxQuotedLines,0, interp); } private static QuoteInfo processQuotes(List<Object> list,int maxQuotedLines,int start, ScopedInterpreter<AuthorWroteNamespace> interp) { int nBreaks = 0; int n = list.size(); for( int i=start; i<n; i++ ) { Object o = list.get(i); if( !(o instanceof HtmlTag) ) continue; HtmlTag tag = (HtmlTag)o; String tagName = tag.getName().toLowerCase(); if( tagName.equals("/quote") ) return new QuoteInfo(i,nBreaks); if( tagName.equals("quote") ) { QuoteInfo qi = processQuotes(list,maxQuotedLines,i+1, interp); if( qi==null ) { list.set(i,HtmlUtils.htmlEncode(tag.toString())); return null; } int closeQuote = qi.i; nBreaks += qi.nBreaks + 1; int fromEnd = list.size() - closeQuote; list.remove(i); nBreaks -= removeBr(list,i); list.add(i++,blockquote); list.add(i++,divQuote); list.add(i++,"\n"); String author = HtmlTag.unquote(tag.getAttributeValue("author")); if( author != null ) { list.add(i++,divQuoteAuthor); list.add(i++,interp.getArgString(new AuthorWroteNamespace(author),"wrote")); list.add(i++,_div); list.add(i++,"\n"); } list.add(i++, qi.nBreaks < maxQuotedLines ? divQuoteMessage : divQuoteMessageHidden ); // list.add(i++, divQuoteMessage ); i = list.size() - fromEnd; int brs = removeBrUp(list,i-1); nBreaks -= brs; i -= brs; list.remove(i); nBreaks -= removeBr(list,i); list.add(i++,_div); list.add(i++,"\n"); list.add(i++,_div); list.add(i++,_blockquote); list.add(i++,"\n"); i--; n = list.size(); } else if( breakingTags.contains(tagName) ) { nBreaks++; } } return null; } private static final HtmlTag _a = new HtmlTag("/a"); private static void processEmail(List<Object> list,Message.Source src) { int count = 0; int n = list.size() - 3 + 1; for( int i=0; i<n; i++ ) { Object o = list.get(i); if( !(o instanceof HtmlTag) ) continue; HtmlTag tag = (HtmlTag)o; if( !tag.getName().toLowerCase().equals("email") ) continue; o = list.get(i+1); if( !(o instanceof String) ) continue; String email = (String)o; o = list.get(i+2); if( !(o instanceof HtmlTag) ) continue; HtmlTag endTag = (HtmlTag)o; if( !endTag.getName().toLowerCase().equals("/email") ) continue; list.remove(i); list.remove(i); list.remove(i); HtmlTag a = new HtmlTag("a"); StringBuilder href = new StringBuilder(); href.append( "/user/SendEmail.jtp" ); if( src==null || src instanceof Message.TempSource) { href.append( "?type=email&email=" ).append( HtmlUtils.urlEncode(email) ); } else { if( src instanceof Node ) { Node node = (Node)src; href.append( "?type=node&node=" ).append( node.getId() ); } else if( src instanceof User ) { User user = (User)src; href.append( "?type=sig&user=" ).append( user.getId() ); } else { throw new RuntimeException("src="+src); } href.append( "&i=" ).append( count++ ); } a.setAttribute("href",HtmlTag.quote(href.toString())); a.setAttribute("target","\"_top\""); a.setAttribute("rel","\"nofollow\""); list.add(i++,a); list.add(i++,ModelHome.hideEmail(email)); list.add(i,_a); } } private static void setTargetToTop(List<Object> list) { for( Object o : list ) { if( o instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)o; if( tag.getName().toLowerCase().equals("a") ) { if( tag.getAttributeValue("target") == null ) { tag.setAttribute("target","\"_top\""); } } } } } private static void addNofollow(List<Object> list, boolean acceptRelFollow) { for( Object o : list ) { if( o instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)o; String tagName = tag.getName().toLowerCase(); if (tagName.equals("a")) { if (acceptRelFollow && "\"follow\"".equals(tag.getAttributeValue("rel"))) continue; tag.setAttribute("rel","\"nofollow\""); tag.setAttribute("link","\"external\""); } } } } // from HtmlSecurity private static void disable(ListIterator<Object> i,Object curr,boolean removeViolation) { if( removeViolation ) { i.remove(); } else { i.set(HtmlUtils.htmlEncode(curr.toString())); } } public static void disableBannedTags(List<Object> html,boolean removeViolation,String... tagNames) { Set<String> tagNameSet = new HashSet<String>(); for( String tagName : tagNames ) { tagNameSet.add(tagName); tagNameSet.add("/"+tagName); } for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)curr; if( tagNameSet.contains(tag.getName().toLowerCase()) ) { disable(i,tag,removeViolation); } } } } private static final URL baseUrl; static { try { baseUrl = new URL("https://www.nabble.com/"); // any valid URL is fine here } catch(MalformedURLException e) { logger.error("",e); System.exit(-1); throw new RuntimeException(e); } } private static void disableInvalidUrls(List<Object> html,boolean removeViolation,String... parameters) { for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)curr; for (String attrName: parameters) { String val = HtmlTag.unquote(tag.getAttributeValue(attrName)); if (val != null) { try { new URL(baseUrl,val).toURI(); } catch(MalformedURLException e) { disable(i,tag,removeViolation); break; } catch(URISyntaxException e) { disable(i,tag,removeViolation); break; } } } } } } private static void disableJavascriptUrls(List<Object> html,boolean removeViolation,String... parameters) { for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)curr; for (String attrName: parameters) { String val = HtmlTag.unquote(tag.getAttributeValue(attrName)); if (val != null && val.toLowerCase().startsWith("javascript:")) { disable(i,tag,removeViolation); break; } } } } } private static void disableOnEvent(List<Object> html,boolean removeViolation) { for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)curr; for( String attrName : tag.getAttributeNames() ) { if (HtmlTag.unquote(attrName).toLowerCase().startsWith("on")) { disable(i,tag,removeViolation); break; } } } } } private static final Pattern urchin = Pattern.compile("\\s*_uacct\\s*=\\s*[\"'][^\"']+[\"']\\s*;\\s*urchinTracker\\(\\);\\s*",Pattern.MULTILINE); private static void disableScripts(List<Object> html,boolean removeViolation) { for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlScript ) { HtmlScript script = (HtmlScript)curr; String src = HtmlTag.unquote(script.startTag.getAttributeValue("src")); if( src != null ) { if( script.text.trim().length()==0 ) { if( src.equals("http://www.google-analytics.com/urchin.js") ) continue; } } else { if( urchin.matcher(script.text).matches() ) continue; } disable(i,script,removeViolation); } } } private static void disableStyleBlocks(List<Object> html,boolean removeViolation) { for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) { Object curr = i.next(); if( curr instanceof HtmlStyle ) { disable(i,curr,removeViolation); } } } private static String htmlToTextMail2(List<Object> html) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < html.size(); i++) { Object next = html.get(i); if( next instanceof String ) { buf.append(next); } else if( next instanceof HtmlTag) { HtmlTag tag = (HtmlTag)next; String tagName = tag.getName().toLowerCase(); String separator = Message.htmlSeparators.get(tagName); if (separator!=null && buf.length()>0 && buf.charAt(buf.length()-1)!='\n') buf.append(separator); if (tagName.equals("img")) { String src = tag.getAttributeValue("src"); if (src!=null) { buf.append('<'); buf.append(HtmlTag.unquote(src)); buf.append("> "); } } else if (tagName.equals("a")) { String src = tag.getAttributeValue("href"); if (src!=null) { String anchorText = html.get(i+1).toString(); buf.append(anchorText.trim()); buf.append(" <"); buf.append(HtmlTag.unquote(src)); buf.append("> "); i++; } } } } Message.wrapQuoteText(buf); return buf.toString(); } private static int processQuotesText(List<Object> list, int start, ScopedInterpreter<AuthorWroteNamespace> interp) { int n = list.size(); for( int i=start; i<n; i++ ) { Object o = list.get(i); if( !(o instanceof HtmlTag) ) continue; HtmlTag tag = (HtmlTag)o; String tagName = tag.getName().toLowerCase(); if( tagName.equals("/quote") ) return i; if( tagName.equals("quote") ) { int closeQuote = processQuotesText(list, i+1, interp); if( closeQuote == -1 ) return -1; int fromEnd = list.size() - closeQuote; list.remove(i); removeCRLF(list, i, 0); if (i > 0) list.add(i++,"\n"); String author = HtmlTag.unquote(tag.getAttributeValue("author")); if( author != null ) { list.add(i++,interp.getArgString(new AuthorWroteNamespace(author),"wrote")); } int begin = i; i = list.size() - fromEnd; removeCRLFUp(list, i-1, -1); list.remove(i); removeCRLF(list, i, 0); for (int j=begin;j<i;j++) quoteText(list,j); list.add(i, "\n"); n = list.size(); } } return -1; } private static void quoteText(List<Object> list,int i) { Object obj = list.get(i); if( !(obj instanceof String) ) return; String s = (String) obj; if (s.length() == 0) return; removeCRLFUp(list, i-1, -1); s = CRLF_UP_PTN.matcher(s).replaceAll(""); if (!s.startsWith("\n") && !s.startsWith("\r\n") ) s = "\n" + s; s = s.replaceAll("\n", "\n> "); s = s.replaceAll("\n> >", "\n>>"); list.set(i, s + '\n'); removeCRLF(list, i+1, +1); } private static final Pattern CRLF_PTN = Pattern.compile("^(\\r?\\n){1}"); private static final Pattern CRLF_UP_PTN = Pattern.compile("(\\r?\\n){1}$"); private static final Pattern BR_PTN = Pattern.compile("^(\\s*<br/?>){1,2}"); private static final Pattern BR_UP_PTN = Pattern.compile("(<br/?>\\s*){1,2}$"); private static void removeCRLF(List<Object> list, int i, int increment) { Object obj = i >= 0 && i < list.size()? list.get(i) : null; if (obj instanceof String) { String s = (String) obj; while (s.length() == 0 && increment != 0) { i += increment; obj = i >= 0 && i < list.size()? list.get(i) : null; if (obj == null || !(obj instanceof String)) return; s = (String) obj; } s = CRLF_PTN.matcher(s).replaceFirst(""); list.set(i, s); } } private static void removeCRLFUp(List<Object> list, int i, int increment) { Object obj = i >= 0 && i < list.size()? list.get(i) : null; if (obj instanceof String) { String s = (String) obj; while (s.length() == 0 && increment != 0) { i += increment; obj = i >= 0 && i < list.size()? list.get(i) : null; if (obj == null || !(obj instanceof String)) return; s = (String) obj; } s = CRLF_UP_PTN.matcher(s).replaceFirst(""); list.set(i, s); } } private static int removeBr(List<Object> list,int i) { return removeBr(list,i,0); } private static int removeBr(List<Object> list,int i,int count) { if( count==2 ) return count; if( i >= list.size() ) return count; Object obj = list.get(i); if( obj instanceof String ) { String s = (String)obj; s = BR_PTN.matcher(s).replaceAll(""); list.set(i,s); } else if( obj instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)obj; if( tag.getName().equalsIgnoreCase("br") ) { list.remove(i); return removeBr(list,i,count+1); } } return count; } private static int removeBrUp(List<Object> list,int i) { return removeBrUp(list,i,0); } private static int removeBrUp(List<Object> list,int i,int count) { if( count==2 ) return count; if( i < 0 ) return count; Object obj = list.get(i); if( obj instanceof String ) { String s = (String)obj; s = BR_UP_PTN.matcher(s).replaceAll(""); list.set(i,s); } else if( obj instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)obj; if( tag.getName().equalsIgnoreCase("br") ) { list.remove(i); return removeBrUp(list,i-1,count+1); } } return count; } // private static final String smiliesDir = "http://" + Jtp.getDefaultHost() + "/images/smiley/"; private static final String smiliesDir = "/images/smiley/"; private static void processSmilies(List<Object> list,boolean isHtml) { for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) { Object o = i.next(); if( o instanceof HtmlTag ) { HtmlTag tag = (HtmlTag)o; if( tag.getName().toLowerCase().equals("smiley") ) { if( isHtml ) { String s = HtmlTag.unquote(tag.getAttributeValue("image")); if( s != null ) { HtmlTag img = new HtmlTag("img class='smiley' src='"+smiliesDir+s+"' /"); i.set(img); } } } } } } }