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