001 package ca.uhn.hl7v2.model.primitive;
002
003 import java.util.regex.Matcher;
004 import 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 */
019 public 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 }