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 if (nextChar == '\\') { 109 int theStart = i + 1; 110 int numericArgument = Integer.MIN_VALUE; 111 int offsetIncludingNumericArgument = 0; 112 String nextFourChars = theInput.substring(theStart, Math.min(theInput.length(), theStart + 4)).toLowerCase(); 113 if (theInput.length() >= theStart + 5) { 114 char sep = theInput.charAt(i + 4); 115 if (theInput.charAt(i + 1) == '.' && (sep == ' ' || sep == '-' || sep == '+')) { 116 String nextThirtyChars = theInput.substring(theStart + 3, Math.min(theInput.length(), theStart + 30)); 117 Matcher m = Pattern.compile("^([ +-]?[0-9]+)\\\\").matcher(nextThirtyChars); 118 if (m.find()) { 119 String group = m.group(1); 120 offsetIncludingNumericArgument = group.length() + 4; 121 group = group.replace('+', ' ').trim(); 122 numericArgument = Integer.parseInt(group); 123 } 124 } 125 } 126 127 if (nextFourChars.equals(".br\\")) { 128 129 closeCenterIfNeeded(); 130 if (myNeedBreakBeforeNextText) { 131 addBreak(); 132 } 133 myNeedBreakBeforeNextText = true; 134 i += 4; 135 myAtStartOfLine = true; 136 myInCenter = false; 137 myCurrentLineOffset = 0; 138 139 } else if (nextFourChars.startsWith("h\\")) { 140 141 startBold(); 142 i += 2; 143 144 } else if (nextFourChars.startsWith("n\\")) { 145 146 endBold(); 147 i += 2; 148 149 } else if (nextFourChars.startsWith(".in") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) { 150 151 myIndent = numericArgument; 152 myTemporaryIndent = 0; 153 i += offsetIncludingNumericArgument; 154 155 } else if (nextFourChars.startsWith(".ti") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) { 156 157 myTemporaryIndent = numericArgument; 158 i += offsetIncludingNumericArgument; 159 160 } else if (nextFourChars.equals(".ce\\")) { 161 162 closeCenterIfNeeded(); 163 if (!myAtStartOfLine) { 164 addBreak(); 165 } 166 addStartCenter(); 167 i += 4; 168 myAtStartOfLine = false; 169 myInCenter = true; 170 171 } else if (nextFourChars.equals(".fi\\")) { 172 173 if (!myWordWrap) { 174 addEndNoBreak(); 175 myWordWrap = true; 176 } 177 i += 4; 178 179 } else if (nextFourChars.equals(".nf\\")) { 180 181 if (myWordWrap) { 182 addStartNoBreak(); 183 myWordWrap = false; 184 } 185 i += 4; 186 187 } else if (nextFourChars.startsWith(".sp")) { 188 189 if (nextFourChars.equals(".sp\\")) { 190 numericArgument = 1; 191 i += 4; 192 } else if (numericArgument != -1) { 193 i += offsetIncludingNumericArgument; 194 } 195 196 if (numericArgument > 0) { 197 198 for (int j = 0; j < numericArgument; j++) { 199 addBreak(); 200 } 201 for (int j = 0; j < myCurrentLineOffset; j++) { 202 addSpace(); 203 } 204 205 } else if (numericArgument == Integer.MIN_VALUE) { 206 207 handled = false; 208 209 } 210 211 } else if (nextFourChars.equals(".sk ") && numericArgument >= 0) { 212 213 for (int j = 0; j < numericArgument; j++) { 214 addSpace(); 215 } 216 i += offsetIncludingNumericArgument; 217 218 } else { 219 220 handled = false; 221 222 } 223 } else { 224 handled = false; 225 } 226 227 if (!handled) { 228 229 if (myAtStartOfLine) { 230 231 int thisLineIndent = Math.max(0, myIndent + myTemporaryIndent); 232 if (myNeedBreakBeforeNextText) { 233 234 if (myInDiv) { 235 myBuffer.append("</div>"); 236 } else if (thisLineIndent == 0) { 237 addBreak(); 238 } 239 } 240 241 if (thisLineIndent > 0) { 242 myBuffer.append("<div style=\"margin-left: "); 243 myBuffer.append(thisLineIndent); 244 myBuffer.append("em;\">"); 245 myInDiv = true; 246 } 247 } 248 249 switch (nextChar) { 250 case '&': 251 addAmpersand(); 252 break; 253 case '<': 254 addLt(); 255 break; 256 case '>': 257 addGt(); 258 break; 259 default: 260 if (nextChar >= 160) { 261 addHighAscii(nextChar); 262 } else { 263 myBuffer.append(nextChar); 264 } 265 } 266 267 myAtStartOfLine = false; 268 myNeedBreakBeforeNextText = false; 269 myCurrentLineOffset++; 270 271 } 272 273 } 274 275 endBold(); 276 277 if (!myWordWrap) { 278 addEndNoBreak(); 279 } 280 closeCenterIfNeeded(); 281 282 if (myInDiv) { 283 myBuffer.append("</div>"); 284 } 285 286 return myBuffer.toString(); 287 } 288 289 private void endBold() { 290 for (int i = 0; i < myInBold; i++) { 291 myBuffer.append("</b>"); 292 } 293 myInBold = 0; 294 } 295 296 private void startBold() { 297 myBuffer.append("<b>"); 298 myInBold++; 299 } 300 301 /** 302 * Returns a newly created instance which uses standard HTML encoding. The 303 * returned instance is not threadsafe, so this method should be called to 304 * obtain a new instance in any thread that requires a FormattedTextEncoder. 305 * 306 * @see AbstractTextPrimitive#getValueAsHtml() for a description of the 307 * encoding performed by this type of encoder 308 */ 309 public static FormattedTextEncoder getInstanceHtml() { 310 return new FormattedTextEncoder(); 311 } 312 313}