| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| FormattedTextEncoder |
|
| 4.333333333333333;4.333 |
| 1 | package ca.uhn.hl7v2.model.primitive; | |
| 2 | ||
| 3 | import java.util.regex.Matcher; | |
| 4 | import java.util.regex.Pattern; | |
| 5 | ||
| 6 | /** | |
| 7 | * <p> | |
| 8 | * Provides methods to convert between HL7 Formatted Text encoding (See Chapter | |
| 9 | * 2.7) and other encoding schemes. | |
| 10 | * </p> | |
| 11 | * <p> | |
| 12 | * For now, only {@link #getInstanceHtml() HTML encoding} is supported, but others may be added. | |
| 13 | * </p> | |
| 14 | * <p> | |
| 15 | * <b>Note that this class is not threadsafe!</b> Always use a new instance | |
| 16 | * (from a factory method) for each invocation. | |
| 17 | * </p> | |
| 18 | * | |
| 19 | * @author James Agnew | |
| 20 | * @see AbstractTextPrimitive | |
| 21 | */ | |
| 22 | public class FormattedTextEncoder { | |
| 23 | ||
| 24 | private StringBuilder myBuffer; | |
| 25 | private int myInBold; | |
| 26 | private boolean myInCenter; | |
| 27 | ||
| 28 | /** | |
| 29 | * Use factory methods to instantiate this class | |
| 30 | */ | |
| 31 | private FormattedTextEncoder() { | |
| 32 | 155 | super(); |
| 33 | 155 | } |
| 34 | ||
| 35 | private void addLt() { | |
| 36 | 5 | myBuffer.append("<"); |
| 37 | 5 | } |
| 38 | ||
| 39 | private void addGt() { | |
| 40 | 5 | myBuffer.append(">"); |
| 41 | 5 | } |
| 42 | ||
| 43 | private void addAmpersand() { | |
| 44 | 10 | myBuffer.append("&"); |
| 45 | 10 | } |
| 46 | ||
| 47 | private void addBreak() { | |
| 48 | 120 | myBuffer.append("<br>"); |
| 49 | 120 | } |
| 50 | ||
| 51 | private void addEndNoBreak() { | |
| 52 | 15 | myBuffer.append("</nobr>"); |
| 53 | 15 | } |
| 54 | ||
| 55 | private void addHighAscii(char nextChar) { | |
| 56 | 5 | myBuffer.append("&#"); |
| 57 | 5 | myBuffer.append((int) nextChar); |
| 58 | 5 | myBuffer.append(";"); |
| 59 | 5 | } |
| 60 | ||
| 61 | private void addSpace() { | |
| 62 | 160 | myBuffer.append(" "); |
| 63 | 160 | } |
| 64 | ||
| 65 | private void addStartCenter() { | |
| 66 | 35 | myBuffer.append("<center>"); |
| 67 | 35 | } |
| 68 | ||
| 69 | private void addStartNoBreak() { | |
| 70 | 15 | myBuffer.append("<nobr>"); |
| 71 | 15 | } |
| 72 | ||
| 73 | private void closeCenterIfNeeded() { | |
| 74 | 265 | if (myInCenter) { |
| 75 | 35 | myBuffer.append("</center>"); |
| 76 | } | |
| 77 | 265 | } |
| 78 | ||
| 79 | /** | |
| 80 | * Convert the input string containing FT encoding strings (\.br\, \.sp XX\, | |
| 81 | * etc.) into the appropriate output type for this encoder (currently HTML) | |
| 82 | * | |
| 83 | * @param theInput | |
| 84 | * The input string | |
| 85 | * @return An encoded version of the input string | |
| 86 | */ | |
| 87 | public String encode(String theInput) { | |
| 88 | 155 | if (theInput == null) { |
| 89 | 0 | return null; |
| 90 | } | |
| 91 | ||
| 92 | 155 | myBuffer = new StringBuilder(theInput.length() + 20); |
| 93 | 155 | boolean myAtStartOfLine = true; |
| 94 | 155 | myInCenter = false; |
| 95 | 155 | boolean myWordWrap = true; |
| 96 | 155 | int myCurrentLineOffset = 0; |
| 97 | 155 | int myTemporaryIndent = 0; |
| 98 | 155 | int myIndent = 0; |
| 99 | 155 | boolean myNeedBreakBeforeNextText = false; |
| 100 | 155 | boolean myInDiv = false; |
| 101 | 155 | myInBold = 0; |
| 102 | ||
| 103 | 3440 | for (int i = 0; i < theInput.length(); i++) { |
| 104 | ||
| 105 | 3285 | char nextChar = theInput.charAt(i); |
| 106 | 3285 | boolean handled = true; |
| 107 | ||
| 108 | 3285 | switch (nextChar) { |
| 109 | case '\\': | |
| 110 | ||
| 111 | 265 | int theStart = i + 1; |
| 112 | 265 | int numericArgument = Integer.MIN_VALUE; |
| 113 | 265 | int offsetIncludingNumericArgument = 0; |
| 114 | 265 | String nextFourChars = theInput.substring(theStart, Math.min(theInput.length(), theStart + 4)).toLowerCase(); |
| 115 | 265 | if (theInput.length() >= theStart + 5) { |
| 116 | 245 | char sep = theInput.charAt(i + 4); |
| 117 | 245 | if (theInput.charAt(i + 1) == '.' && (sep == ' ' || sep == '-' || sep == '+')) { |
| 118 | 75 | String nextThirtyChars = theInput.substring(theStart + 3, Math.min(theInput.length(), theStart + 30)); |
| 119 | 75 | Matcher m = Pattern.compile("^([ +-]?[0-9]+)\\\\").matcher(nextThirtyChars); |
| 120 | 75 | if (m.find()) { |
| 121 | 65 | String group = m.group(1); |
| 122 | 65 | offsetIncludingNumericArgument = group.length() + 4; |
| 123 | 65 | group = group.replace('+', ' ').trim(); |
| 124 | 65 | numericArgument = Integer.parseInt(group); |
| 125 | } | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | 265 | if (nextFourChars.equals(".br\\")) { |
| 130 | ||
| 131 | 75 | closeCenterIfNeeded(); |
| 132 | 75 | if (myNeedBreakBeforeNextText) { |
| 133 | 0 | addBreak(); |
| 134 | } | |
| 135 | 75 | myNeedBreakBeforeNextText = true; |
| 136 | 75 | i += 4; |
| 137 | 75 | myAtStartOfLine = true; |
| 138 | 75 | myInCenter = false; |
| 139 | 75 | myCurrentLineOffset = 0; |
| 140 | ||
| 141 | 190 | } else if (nextFourChars.startsWith("h\\")) { |
| 142 | ||
| 143 | 15 | startBold(); |
| 144 | 15 | i += 2; |
| 145 | ||
| 146 | 175 | } else if (nextFourChars.startsWith("n\\")) { |
| 147 | ||
| 148 | 10 | endBold(); |
| 149 | 10 | i += 2; |
| 150 | ||
| 151 | 165 | } else if (nextFourChars.startsWith(".in") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) { |
| 152 | ||
| 153 | 25 | myIndent = numericArgument; |
| 154 | 25 | myTemporaryIndent = 0; |
| 155 | 25 | i += offsetIncludingNumericArgument; |
| 156 | ||
| 157 | 140 | } else if (nextFourChars.startsWith(".ti") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) { |
| 158 | ||
| 159 | 5 | myTemporaryIndent = numericArgument; |
| 160 | 5 | i += offsetIncludingNumericArgument; |
| 161 | ||
| 162 | 135 | } else if (nextFourChars.equals(".ce\\")) { |
| 163 | ||
| 164 | 35 | closeCenterIfNeeded(); |
| 165 | 35 | if (!myAtStartOfLine) { |
| 166 | 30 | addBreak(); |
| 167 | } | |
| 168 | 35 | addStartCenter(); |
| 169 | 35 | i += 4; |
| 170 | 35 | myAtStartOfLine = false; |
| 171 | 35 | myInCenter = true; |
| 172 | ||
| 173 | 100 | } else if (nextFourChars.equals(".fi\\")) { |
| 174 | ||
| 175 | 10 | if (!myWordWrap) { |
| 176 | 10 | addEndNoBreak(); |
| 177 | 10 | myWordWrap = true; |
| 178 | } | |
| 179 | 10 | i += 4; |
| 180 | ||
| 181 | 90 | } else if (nextFourChars.equals(".nf\\")) { |
| 182 | ||
| 183 | 15 | if (myWordWrap) { |
| 184 | 15 | addStartNoBreak(); |
| 185 | 15 | myWordWrap = false; |
| 186 | } | |
| 187 | 15 | i += 4; |
| 188 | ||
| 189 | 75 | } else if (nextFourChars.startsWith(".sp")) { |
| 190 | ||
| 191 | 50 | if (nextFourChars.equals(".sp\\")) { |
| 192 | 10 | numericArgument = 1; |
| 193 | 10 | i += 4; |
| 194 | 40 | } else if (numericArgument != -1) { |
| 195 | 40 | i += offsetIncludingNumericArgument; |
| 196 | } | |
| 197 | ||
| 198 | 50 | if (numericArgument > 0) { |
| 199 | ||
| 200 | 75 | for (int j = 0; j < numericArgument; j++) { |
| 201 | 45 | addBreak(); |
| 202 | } | |
| 203 | 165 | for (int j = 0; j < myCurrentLineOffset; j++) { |
| 204 | 135 | addSpace(); |
| 205 | } | |
| 206 | ||
| 207 | 20 | } else if (numericArgument == Integer.MIN_VALUE) { |
| 208 | ||
| 209 | 20 | handled = false; |
| 210 | ||
| 211 | } | |
| 212 | ||
| 213 | 25 | } else if (nextFourChars.equals(".sk ") && numericArgument >= 0) { |
| 214 | ||
| 215 | 35 | for (int j = 0; j < numericArgument; j++) { |
| 216 | 25 | addSpace(); |
| 217 | } | |
| 218 | 10 | i += offsetIncludingNumericArgument; |
| 219 | ||
| 220 | } else { | |
| 221 | ||
| 222 | 15 | handled = false; |
| 223 | ||
| 224 | } | |
| 225 | ||
| 226 | 15 | break; |
| 227 | default: | |
| 228 | ||
| 229 | 3020 | handled = false; |
| 230 | ||
| 231 | } | |
| 232 | ||
| 233 | 3285 | if (!handled) { |
| 234 | ||
| 235 | 3055 | if (myAtStartOfLine) { |
| 236 | ||
| 237 | 225 | int thisLineIndent = Math.max(0, myIndent + myTemporaryIndent); |
| 238 | 225 | if (myNeedBreakBeforeNextText) { |
| 239 | ||
| 240 | 75 | if (myInDiv) { |
| 241 | 10 | myBuffer.append("</div>"); |
| 242 | 65 | } else if (thisLineIndent == 0) { |
| 243 | 45 | addBreak(); |
| 244 | } | |
| 245 | } | |
| 246 | ||
| 247 | 225 | if (thisLineIndent > 0) { |
| 248 | 30 | myBuffer.append("<div style=\"margin-left: "); |
| 249 | 30 | myBuffer.append(thisLineIndent); |
| 250 | 30 | myBuffer.append("em;\">"); |
| 251 | 30 | myInDiv = true; |
| 252 | } | |
| 253 | } | |
| 254 | ||
| 255 | 3055 | switch (nextChar) { |
| 256 | case '&': | |
| 257 | 10 | addAmpersand(); |
| 258 | 10 | break; |
| 259 | case '<': | |
| 260 | 5 | addLt(); |
| 261 | 5 | break; |
| 262 | case '>': | |
| 263 | 5 | addGt(); |
| 264 | 5 | break; |
| 265 | default: | |
| 266 | 3035 | if (nextChar >= 160) { |
| 267 | 5 | addHighAscii(nextChar); |
| 268 | } else { | |
| 269 | 3030 | myBuffer.append(nextChar); |
| 270 | } | |
| 271 | } | |
| 272 | ||
| 273 | 3055 | myAtStartOfLine = false; |
| 274 | 3055 | myNeedBreakBeforeNextText = false; |
| 275 | 3055 | myCurrentLineOffset++; |
| 276 | ||
| 277 | } | |
| 278 | ||
| 279 | } | |
| 280 | ||
| 281 | 155 | endBold(); |
| 282 | ||
| 283 | 155 | if (!myWordWrap) { |
| 284 | 5 | addEndNoBreak(); |
| 285 | } | |
| 286 | 155 | closeCenterIfNeeded(); |
| 287 | ||
| 288 | 155 | if (myInDiv) { |
| 289 | 20 | myBuffer.append("</div>"); |
| 290 | } | |
| 291 | ||
| 292 | 155 | return myBuffer.toString(); |
| 293 | } | |
| 294 | ||
| 295 | private void endBold() { | |
| 296 | 180 | for (int i = 0; i < myInBold; i++) { |
| 297 | 15 | myBuffer.append("</b>"); |
| 298 | } | |
| 299 | 165 | myInBold = 0; |
| 300 | 165 | } |
| 301 | ||
| 302 | private void startBold() { | |
| 303 | 15 | myBuffer.append("<b>"); |
| 304 | 15 | myInBold++; |
| 305 | 15 | } |
| 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 | 155 | return new FormattedTextEncoder(); |
| 317 | } | |
| 318 | ||
| 319 | } |