Mercurial Hosting > nabble
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/nabble/view/web/template/HtmlListNamespace.java Thu Mar 21 19:15:52 2019 -0600 @@ -0,0 +1,920 @@ +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); + } + } + } + } + } + } + + +}