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   * 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  		super();
33  	}
34  
35  	private void addLt() {
36  		myBuffer.append("&lt;");
37  	}
38  
39  	private void addGt() {
40  		myBuffer.append("&gt;");
41  	}
42  
43  	private void addAmpersand() {
44  		myBuffer.append("&amp;");
45  	}
46  
47  	private void addBreak() {
48  		myBuffer.append("<br>");
49  	}
50  
51  	private void addEndNoBreak() {
52  		myBuffer.append("</nobr>");
53  	}
54  
55  	private void addHighAscii(char nextChar) {
56  		myBuffer.append("&#");
57  		myBuffer.append((int) nextChar);
58  		myBuffer.append(";");
59  	}
60  
61  	private void addSpace() {
62  		myBuffer.append("&nbsp;");
63  	}
64  
65  	private void addStartCenter() {
66  		myBuffer.append("<center>");
67  	}
68  
69  	private void addStartNoBreak() {
70  		myBuffer.append("<nobr>");
71  	}
72  
73  	private void closeCenterIfNeeded() {
74  		if (myInCenter) {
75  			myBuffer.append("</center>");
76  		}
77  	}
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  		if (theInput == null) {
89  			return null;
90  		}
91  
92  		myBuffer = new StringBuilder(theInput.length() + 20);
93  		boolean myAtStartOfLine = true;
94  		myInCenter = false;
95  		boolean myWordWrap = true;
96  		int myCurrentLineOffset = 0;
97  		int myTemporaryIndent = 0;
98  		int myIndent = 0;
99  		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 }