001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.util.common.string; 018 019import com.google.common.base.Splitter; 020import com.google.common.html.HtmlEscapers; 021import java.util.ArrayDeque; 022import java.util.Deque; 023import java.util.HashMap; 024import java.util.Map; 025 026public class MarkupUtils { 027 028 private MarkupUtils() { 029 super(); 030 } 031 032 private static class MarkupUtilsHolder { 033 static MarkupUtils instance = new MarkupUtils(); 034 } 035 036 public static MarkupUtils getInstance() { 037 return MarkupUtilsHolder.instance; 038 } 039 040 private static class MarkupTransformation { 041 String markup; 042 String startHtml; 043 String endHtml; 044 String requiredIn; 045 046 MarkupTransformation(Map<String, MarkupTransformation> transformations, String markup, String startHtml, String endHtml, String requiredIn) { 047 this.markup = markup; 048 this.startHtml = startHtml; 049 this.endHtml = endHtml; 050 this.requiredIn = requiredIn; 051 052 transformations.put(markup, this); 053 } 054 }; 055 056 private final static Map<String, MarkupTransformation> transformations; 057 058 static { 059 transformations = new HashMap<>(); 060 061 // TODO: Fill in additional tags from http://learningforlife.fsu.edu/webmaster/references/xhtml/tags/index.cfm 062 new MarkupTransformation(transformations, "b", "<b>", "</b>", null); 063 new MarkupTransformation(transformations, "i", "<i>", "</i>", null); 064 new MarkupTransformation(transformations, "u", "<u>", "</u>", null); 065 new MarkupTransformation(transformations, "s", "<s>", "</s>", null); 066 new MarkupTransformation(transformations, "ul", "<ul>", "</ul>", null); 067 new MarkupTransformation(transformations, "ol", "<ol>", "</ol>", null); 068 new MarkupTransformation(transformations, "li", "<li>", "</li>", "ul:ol"); 069 new MarkupTransformation(transformations, "blockquote", "<blockquote>", "</blockquote>", null); 070 new MarkupTransformation(transformations, "pre", "<pre>", "</pre>", null); 071 new MarkupTransformation(transformations, "quote", "<hr /><blockquote>", "</blockquote><hr />", null); 072 new MarkupTransformation(transformations, "br", "<br />", null, null); 073 new MarkupTransformation(transformations, "red", "<span style=\"color: red\">", "</span>", null); 074 new MarkupTransformation(transformations, "green", "<span style=\"color: green\">", "</span>", null); 075 new MarkupTransformation(transformations, "blue", "<span style=\"color: blue\">", "</span>", null); 076 new MarkupTransformation(transformations, "orange", "<span style=\"color: orange\">", "</span>", null); 077 new MarkupTransformation(transformations, "black", "<span style=\"color: black\">", "</span>", null); 078 new MarkupTransformation(transformations, "white", "<span style=\"color: white\">", "</span>", null); 079 new MarkupTransformation(transformations, "yellow", "<span style=\"color: yellow\">", "</span>", null); 080 new MarkupTransformation(transformations, "purple", "<span style=\"color: purple\">", "</span>", null); 081 new MarkupTransformation(transformations, "table", "<table>", "</table>", null); 082 new MarkupTransformation(transformations, "tr", "<tr>", "</tr>", "table"); 083 new MarkupTransformation(transformations, "td", "<td>", "</td>", "tr"); 084 } 085 086 public String filter(final String originalContent) { 087 String intermediateContent = HtmlEscapers.htmlEscaper().escape(originalContent); 088 StringBuilder finalContent = new StringBuilder(intermediateContent.length()); 089 StringBuilder markupBuilder = null; 090 boolean endMarkup = false; 091 Deque<MarkupTransformation> activeMarkup = new ArrayDeque<>(); 092 093 for(int ch : StringUtils.getInstance().codePoints(intermediateContent)) { 094 switch(ch) { 095 case '[': 096 markupBuilder = new StringBuilder(); 097 endMarkup = false; 098 break; 099 case ']': 100 MarkupTransformation mt = transformations.get(markupBuilder.toString()); 101 102 if(mt != null) { 103 if(endMarkup) { 104 if(mt == activeMarkup.peek()) { 105 activeMarkup.pop(); 106 finalContent.append(mt.endHtml); 107 } 108 } else { 109 boolean pushIt = true; 110 111 if(mt.requiredIn != null) { 112 MarkupTransformation lastMt = activeMarkup.peek(); 113 114 pushIt = false; 115 116 if(lastMt != null) { 117 String []endingMarkup = Splitter.on(':').trimResults().omitEmptyStrings().splitToList(mt.requiredIn).toArray(new String[0]); 118 119 for(int i = 0; i < endingMarkup.length; i++) { 120 if(endingMarkup[i].equals(lastMt.markup)) { 121 pushIt = true; 122 break; 123 } 124 } 125 } 126 } 127 128 if(pushIt) { 129 if(mt.endHtml != null) { 130 activeMarkup.push(mt); 131 } 132 133 finalContent.append(mt.startHtml); 134 } 135 } 136 } 137 138 markupBuilder = null; 139 break; 140 default: 141 if(markupBuilder == null) { 142 switch(ch) { 143 case '\t': 144 finalContent.append(" "); 145 break; 146 case '\r': 147 // Drop it. 148 break; 149 case '\n': 150 finalContent.append("<br />"); 151 break; 152 default: 153 finalContent.appendCodePoint(ch); 154 break; 155 } 156 } else { 157 if(ch == '/' && markupBuilder.length() == 0) { 158 endMarkup = true; 159 } else if(Character.isLetter(ch)) { 160 markupBuilder.appendCodePoint(Character.toLowerCase(ch)); 161 break; 162 } 163 } 164 break; 165 } 166 } 167 168 while(activeMarkup.peek() != null) { 169 finalContent.append(activeMarkup.pop().endHtml); 170 } 171 172 return finalContent.toString(); 173 } 174 175}