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 | } |