View Javadoc

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   * <b>Note that this class is not threadsafe!</b> Always use a new instance
13   * (from a factory method) for each invocation.
14   * </p>
15   * 
16   * @author James Agnew
17   * @see AbstractTextPrimitive
18   */
19  public class FormattedTextEncoder {
20  
21  	private StringBuilder myBuffer;
22  	private int myInBold;
23  	private boolean myInCenter;
24  
25  	/**
26  	 * Use factory methods to instantiate this class
27  	 */
28  	private FormattedTextEncoder() {
29  		super();
30  	}
31  
32  	private void addLt() {
33  		myBuffer.append("&lt;");
34  	}
35  
36  	private void addGt() {
37  		myBuffer.append("&gt;");
38  	}
39  
40  	private void addAmpersand() {
41  		myBuffer.append("&amp;");
42  	}
43  
44  	private void addBreak() {
45  		myBuffer.append("<br>");
46  	}
47  
48  	private void addEndNoBreak() {
49  		myBuffer.append("</nobr>");
50  	}
51  
52  	private void addHighAscii(char nextChar) {
53  		myBuffer.append("&#");
54  		myBuffer.append((int) nextChar);
55  		myBuffer.append(";");
56  	}
57  
58  	private void addSpace() {
59  		myBuffer.append("&nbsp;");
60  	}
61  
62  	private void addStartCenter() {
63  		myBuffer.append("<center>");
64  	}
65  
66  	private void addStartNoBreak() {
67  		myBuffer.append("<nobr>");
68  	}
69  
70  	private void closeCenterIfNeeded() {
71  		if (myInCenter) {
72  			myBuffer.append("</center>");
73  		}
74  	}
75  
76  	/**
77  	 * Convert the input string containing FT encoding strings (\.br\, \.sp XX\,
78  	 * etc.) into the appropriate output type for this encoder (currently HTML)
79  	 * 
80  	 * @param theInput
81  	 *            The input string
82  	 * @return An encoded version of the input string
83  	 */
84  	public String encode(String theInput) {
85  		if (theInput == null) {
86  			return null;
87  		}
88  
89  		myBuffer = new StringBuilder(theInput.length() + 20);
90  		boolean myAtStartOfLine = true;
91  		myInCenter = false;
92  		boolean myWordWrap = true;
93  		int myCurrentLineOffset = 0;
94  		int myTemporaryIndent = 0;
95  		int myIndent = 0;
96  		boolean myNeedBreakBeforeNextText = false;
97  		boolean myInDiv = false;
98  		myInBold = 0;
99  
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 }