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 * For now, only {@link #getInstanceHtml() HTML encoding} is supported, but others may be added.
013 * </p>
014 * <p>
015 * <b>Note that this class is not threadsafe!</b> Always use a new instance
016 * (from a factory method) for each invocation.
017 * </p>
018 * 
019 * @author James Agnew
020 * @see AbstractTextPrimitive
021 */
022public class FormattedTextEncoder {
023
024        private StringBuilder myBuffer;
025        private int myInBold;
026        private boolean myInCenter;
027
028        /**
029         * Use factory methods to instantiate this class
030         */
031        private FormattedTextEncoder() {
032                super();
033        }
034
035        private void addLt() {
036                myBuffer.append("&lt;");
037        }
038
039        private void addGt() {
040                myBuffer.append("&gt;");
041        }
042
043        private void addAmpersand() {
044                myBuffer.append("&amp;");
045        }
046
047        private void addBreak() {
048                myBuffer.append("<br>");
049        }
050
051        private void addEndNoBreak() {
052                myBuffer.append("</nobr>");
053        }
054
055        private void addHighAscii(char nextChar) {
056                myBuffer.append("&#");
057                myBuffer.append((int) nextChar);
058                myBuffer.append(";");
059        }
060
061        private void addSpace() {
062                myBuffer.append("&nbsp;");
063        }
064
065        private void addStartCenter() {
066                myBuffer.append("<center>");
067        }
068
069        private void addStartNoBreak() {
070                myBuffer.append("<nobr>");
071        }
072
073        private void closeCenterIfNeeded() {
074                if (myInCenter) {
075                        myBuffer.append("</center>");
076                }
077        }
078
079        /**
080         * Convert the input string containing FT encoding strings (\.br\, \.sp XX\,
081         * etc.) into the appropriate output type for this encoder (currently HTML)
082         * 
083         * @param theInput
084         *            The input string
085         * @return An encoded version of the input string
086         */
087        public String encode(String theInput) {
088                if (theInput == null) {
089                        return null;
090                }
091
092                myBuffer = new StringBuilder(theInput.length() + 20);
093                boolean myAtStartOfLine = true;
094                myInCenter = false;
095                boolean myWordWrap = true;
096                int myCurrentLineOffset = 0;
097                int myTemporaryIndent = 0;
098                int myIndent = 0;
099                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}