001package ca.uhn.hl7v2.model.primitive; 002 003import java.util.regex.Matcher; 004import java.util.regex.Pattern; 005 006/** 007 * <p> 008 * Provides methods to convert between HL7 Formatted Text encoding (See Chapter 009 * 2.7) and other encoding schemes. 010 * </p> 011 * <p> 012 * For now, only {@link #getInstanceHtml() HTML encoding} is supported, but others may be added. 013 * </p> 014 * <p> 015 * <b>Note that this class is not threadsafe!</b> Always use a new instance 016 * (from a factory method) for each invocation. 017 * </p> 018 * 019 * @author James Agnew 020 * @see AbstractTextPrimitive 021 */ 022public class FormattedTextEncoder { 023 024 private StringBuilder myBuffer; 025 private int myInBold; 026 private boolean myInCenter; 027 028 /** 029 * Use factory methods to instantiate this class 030 */ 031 private FormattedTextEncoder() { 032 super(); 033 } 034 035 private void addLt() { 036 myBuffer.append("<"); 037 } 038 039 private void addGt() { 040 myBuffer.append(">"); 041 } 042 043 private void addAmpersand() { 044 myBuffer.append("&"); 045 } 046 047 private void addBreak() { 048 myBuffer.append("<br>"); 049 } 050 051 private void addEndNoBreak() { 052 myBuffer.append("</nobr>"); 053 } 054 055 private void addHighAscii(char nextChar) { 056 myBuffer.append("&#"); 057 myBuffer.append((int) nextChar); 058 myBuffer.append(";"); 059 } 060 061 private void addSpace() { 062 myBuffer.append(" "); 063 } 064 065 private void addStartCenter() { 066 myBuffer.append("<center>"); 067 } 068 069 private void addStartNoBreak() { 070 myBuffer.append("<nobr>"); 071 } 072 073 private void closeCenterIfNeeded() { 074 if (myInCenter) { 075 myBuffer.append("</center>"); 076 } 077 } 078 079 /** 080 * Convert the input string containing FT encoding strings (\.br\, \.sp XX\, 081 * etc.) into the appropriate output type for this encoder (currently HTML) 082 * 083 * @param theInput 084 * The input string 085 * @return An encoded version of the input string 086 */ 087 public String encode(String theInput) { 088 if (theInput == null) { 089 return null; 090 } 091 092 myBuffer = new StringBuilder(theInput.length() + 20); 093 boolean myAtStartOfLine = true; 094 myInCenter = false; 095 boolean myWordWrap = true; 096 int myCurrentLineOffset = 0; 097 int myTemporaryIndent = 0; 098 int myIndent = 0; 099 boolean myNeedBreakBeforeNextText = false; 100 boolean myInDiv = false; 101 myInBold = 0; 102 103 for (int i = 0; i < theInput.length(); i++) { 104 105 char nextChar = theInput.charAt(i); 106 boolean handled = true; 107 108 switch (nextChar) { 109 case '\\': 110 111 int theStart = i + 1; 112 int numericArgument = Integer.MIN_VALUE; 113 int offsetIncludingNumericArgument = 0; 114 String nextFourChars = theInput.substring(theStart, Math.min(theInput.length(), theStart + 4)).toLowerCase(); 115 if (theInput.length() >= theStart + 5) { 116 char sep = theInput.charAt(i + 4); 117 if (theInput.charAt(i + 1) == '.' && (sep == ' ' || sep == '-' || sep == '+')) { 118 String nextThirtyChars = theInput.substring(theStart + 3, Math.min(theInput.length(), theStart + 30)); 119 Matcher m = Pattern.compile("^([ +-]?[0-9]+)\\\\").matcher(nextThirtyChars); 120 if (m.find()) { 121 String group = m.group(1); 122 offsetIncludingNumericArgument = group.length() + 4; 123 group = group.replace('+', ' ').trim(); 124 numericArgument = Integer.parseInt(group); 125 } 126 } 127 } 128 129 if (nextFourChars.equals(".br\\")) { 130 131 closeCenterIfNeeded(); 132 if (myNeedBreakBeforeNextText) { 133 addBreak(); 134 } 135 myNeedBreakBeforeNextText = true; 136 i += 4; 137 myAtStartOfLine = true; 138 myInCenter = false; 139 myCurrentLineOffset = 0; 140 141 } else if (nextFourChars.startsWith("h\\")) { 142 143 startBold(); 144 i += 2; 145 146 } else if (nextFourChars.startsWith("n\\")) { 147 148 endBold(); 149 i += 2; 150 151 } else if (nextFourChars.startsWith(".in") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) { 152 153 myIndent = numericArgument; 154 myTemporaryIndent = 0; 155 i += offsetIncludingNumericArgument; 156 157 } else if (nextFourChars.startsWith(".ti") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) { 158 159 myTemporaryIndent = numericArgument; 160 i += offsetIncludingNumericArgument; 161 162 } else if (nextFourChars.equals(".ce\\")) { 163 164 closeCenterIfNeeded(); 165 if (!myAtStartOfLine) { 166 addBreak(); 167 } 168 addStartCenter(); 169 i += 4; 170 myAtStartOfLine = false; 171 myInCenter = true; 172 173 } else if (nextFourChars.equals(".fi\\")) { 174 175 if (!myWordWrap) { 176 addEndNoBreak(); 177 myWordWrap = true; 178 } 179 i += 4; 180 181 } else if (nextFourChars.equals(".nf\\")) { 182 183 if (myWordWrap) { 184 addStartNoBreak(); 185 myWordWrap = false; 186 } 187 i += 4; 188 189 } else if (nextFourChars.startsWith(".sp")) { 190 191 if (nextFourChars.equals(".sp\\")) { 192 numericArgument = 1; 193 i += 4; 194 } else if (numericArgument != -1) { 195 i += offsetIncludingNumericArgument; 196 } 197 198 if (numericArgument > 0) { 199 200 for (int j = 0; j < numericArgument; j++) { 201 addBreak(); 202 } 203 for (int j = 0; j < myCurrentLineOffset; j++) { 204 addSpace(); 205 } 206 207 } else if (numericArgument == Integer.MIN_VALUE) { 208 209 handled = false; 210 211 } 212 213 } else if (nextFourChars.equals(".sk ") && numericArgument >= 0) { 214 215 for (int j = 0; j < numericArgument; j++) { 216 addSpace(); 217 } 218 i += offsetIncludingNumericArgument; 219 220 } else { 221 222 handled = false; 223 224 } 225 226 break; 227 default: 228 229 handled = false; 230 231 } 232 233 if (!handled) { 234 235 if (myAtStartOfLine) { 236 237 int thisLineIndent = Math.max(0, myIndent + myTemporaryIndent); 238 if (myNeedBreakBeforeNextText) { 239 240 if (myInDiv) { 241 myBuffer.append("</div>"); 242 } else if (thisLineIndent == 0) { 243 addBreak(); 244 } 245 } 246 247 if (thisLineIndent > 0) { 248 myBuffer.append("<div style=\"margin-left: "); 249 myBuffer.append(thisLineIndent); 250 myBuffer.append("em;\">"); 251 myInDiv = true; 252 } 253 } 254 255 switch (nextChar) { 256 case '&': 257 addAmpersand(); 258 break; 259 case '<': 260 addLt(); 261 break; 262 case '>': 263 addGt(); 264 break; 265 default: 266 if (nextChar >= 160) { 267 addHighAscii(nextChar); 268 } else { 269 myBuffer.append(nextChar); 270 } 271 } 272 273 myAtStartOfLine = false; 274 myNeedBreakBeforeNextText = false; 275 myCurrentLineOffset++; 276 277 } 278 279 } 280 281 endBold(); 282 283 if (!myWordWrap) { 284 addEndNoBreak(); 285 } 286 closeCenterIfNeeded(); 287 288 if (myInDiv) { 289 myBuffer.append("</div>"); 290 } 291 292 return myBuffer.toString(); 293 } 294 295 private void endBold() { 296 for (int i = 0; i < myInBold; i++) { 297 myBuffer.append("</b>"); 298 } 299 myInBold = 0; 300 } 301 302 private void startBold() { 303 myBuffer.append("<b>"); 304 myInBold++; 305 } 306 307 /** 308 * Returns a newly created instance which uses standard HTML encoding. The 309 * returned instance is not threadsafe, so this method should be called to 310 * obtain a new instance in any thread that requires a FormattedTextEncoder. 311 * 312 * @see AbstractTextPrimitive#getValueAsHtml() for a description of the 313 * encoding performed by this type of encoder 314 */ 315 public static FormattedTextEncoder getInstanceHtml() { 316 return new FormattedTextEncoder(); 317 } 318 319}