001package ca.uhn.hl7v2.model.primitive;
002
003import java.util.regex.Matcher;
004import 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 */
019public class FormattedTextEncoder {
020
021        private StringBuilder myBuffer;
022        private int myInBold;
023        private boolean myInCenter;
024
025        /**
026         * Use factory methods to instantiate this class
027         */
028        private FormattedTextEncoder() {
029                super();
030        }
031
032        private void addLt() {
033                myBuffer.append("&lt;");
034        }
035
036        private void addGt() {
037                myBuffer.append("&gt;");
038        }
039
040        private void addAmpersand() {
041                myBuffer.append("&amp;");
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("&nbsp;");
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                boolean myAtStartOfLine = true;
091                myInCenter = false;
092                boolean myWordWrap = true;
093                int myCurrentLineOffset = 0;
094                int myTemporaryIndent = 0;
095                int myIndent = 0;
096                boolean myNeedBreakBeforeNextText = false;
097                boolean 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                        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}