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