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                        switch (nextChar) {
109                        case '\\':
110
111                                int theStart = i + 1;
112                                int numericArgument = Integer.MIN_VALUE;
113                                int offsetIncludingNumericArgument = 0;
114                                String nextFourChars = theInput.substring(theStart, Math.min(theInput.length(), theStart + 4)).toLowerCase();
115                                if (theInput.length() >= theStart + 5) {
116                                        char sep = theInput.charAt(i + 4);
117                                        if (theInput.charAt(i + 1) == '.' && (sep == ' ' || sep == '-' || sep == '+')) {
118                                                String nextThirtyChars = theInput.substring(theStart + 3, Math.min(theInput.length(), theStart + 30));
119                                                Matcher m = Pattern.compile("^([ +-]?[0-9]+)\\\\").matcher(nextThirtyChars);
120                                                if (m.find()) {
121                                                        String group = m.group(1);
122                                                        offsetIncludingNumericArgument = group.length() + 4;
123                                                        group = group.replace('+', ' ').trim();
124                                                        numericArgument = Integer.parseInt(group);
125                                                }
126                                        }
127                                }
128
129                                if (nextFourChars.equals(".br\\")) {
130
131                                        closeCenterIfNeeded();
132                                        if (myNeedBreakBeforeNextText) {
133                                                addBreak();
134                                        }
135                                        myNeedBreakBeforeNextText = true;
136                                        i += 4;
137                                        myAtStartOfLine = true;
138                                        myInCenter = false;
139                                        myCurrentLineOffset = 0;
140
141                                } else if (nextFourChars.startsWith("h\\")) {
142
143                                        startBold();
144                                        i += 2;
145
146                                } else if (nextFourChars.startsWith("n\\")) {
147
148                                        endBold();
149                                        i += 2;
150
151                                } else if (nextFourChars.startsWith(".in") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) {
152
153                                        myIndent = numericArgument;
154                                        myTemporaryIndent = 0;
155                                        i += offsetIncludingNumericArgument;
156
157                                } else if (nextFourChars.startsWith(".ti") && myAtStartOfLine && numericArgument != Integer.MIN_VALUE) {
158
159                                        myTemporaryIndent = numericArgument;
160                                        i += offsetIncludingNumericArgument;
161
162                                } else if (nextFourChars.equals(".ce\\")) {
163
164                                        closeCenterIfNeeded();
165                                        if (!myAtStartOfLine) {
166                                                addBreak();
167                                        }
168                                        addStartCenter();
169                                        i += 4;
170                                        myAtStartOfLine = false;
171                                        myInCenter = true;
172
173                                } else if (nextFourChars.equals(".fi\\")) {
174
175                                        if (!myWordWrap) {
176                                                addEndNoBreak();
177                                                myWordWrap = true;
178                                        }
179                                        i += 4;
180
181                                } else if (nextFourChars.equals(".nf\\")) {
182
183                                        if (myWordWrap) {
184                                                addStartNoBreak();
185                                                myWordWrap = false;
186                                        }
187                                        i += 4;
188
189                                } else if (nextFourChars.startsWith(".sp")) {
190
191                                        if (nextFourChars.equals(".sp\\")) {
192                                                numericArgument = 1;
193                                                i += 4;
194                                        } else if (numericArgument != -1) {
195                                                i += offsetIncludingNumericArgument;
196                                        }
197
198                                        if (numericArgument > 0) {
199
200                                                for (int j = 0; j < numericArgument; j++) {
201                                                        addBreak();
202                                                }
203                                                for (int j = 0; j < myCurrentLineOffset; j++) {
204                                                        addSpace();
205                                                }
206
207                                        } else if (numericArgument == Integer.MIN_VALUE) {
208
209                                                handled = false;
210
211                                        }
212
213                                } else if (nextFourChars.equals(".sk ") && numericArgument >= 0) {
214
215                                        for (int j = 0; j < numericArgument; j++) {
216                                                addSpace();
217                                        }
218                                        i += offsetIncludingNumericArgument;
219
220                                } else {
221
222                                        handled = false;
223
224                                }
225
226                                break;
227                        default:
228
229                                handled = false;
230
231                        }
232
233                        if (!handled) {
234
235                                if (myAtStartOfLine) {
236
237                                        int thisLineIndent = Math.max(0, myIndent + myTemporaryIndent);
238                                        if (myNeedBreakBeforeNextText) {
239
240                                                if (myInDiv) {
241                                                        myBuffer.append("</div>");
242                                                } else if (thisLineIndent == 0) {
243                                                        addBreak();
244                                                }
245                                        }
246
247                                        if (thisLineIndent > 0) {
248                                                myBuffer.append("<div style=\"margin-left: ");
249                                                myBuffer.append(thisLineIndent);
250                                                myBuffer.append("em;\">");
251                                                myInDiv = true;
252                                        }
253                                }
254
255                                switch (nextChar) {
256                                case '&':
257                                        addAmpersand();
258                                        break;
259                                case '<':
260                                        addLt();
261                                        break;
262                                case '>':
263                                        addGt();
264                                        break;
265                                default:
266                                        if (nextChar >= 160) {
267                                                addHighAscii(nextChar);
268                                        } else {
269                                                myBuffer.append(nextChar);
270                                        }
271                                }
272
273                                myAtStartOfLine = false;
274                                myNeedBreakBeforeNextText = false;
275                                myCurrentLineOffset++;
276
277                        }
278
279                }
280
281                endBold();
282
283                if (!myWordWrap) {
284                        addEndNoBreak();
285                }
286                closeCenterIfNeeded();
287
288                if (myInDiv) {
289                        myBuffer.append("</div>");
290                }
291
292                return myBuffer.toString();
293        }
294
295        private void endBold() {
296                for (int i = 0; i < myInBold; i++) {
297                        myBuffer.append("</b>");
298                }
299                myInBold = 0;
300        }
301
302        private void startBold() {
303                myBuffer.append("<b>");
304                myInBold++;
305        }
306
307        /**
308         * Returns a newly created instance which uses standard HTML encoding. The
309         * returned instance is not threadsafe, so this method should be called to
310         * obtain a new instance in any thread that requires a FormattedTextEncoder.
311         * 
312         * @see AbstractTextPrimitive#getValueAsHtml() for a description of the
313         *      encoding performed by this type of encoder
314         */
315        public static FormattedTextEncoder getInstanceHtml() {
316                return new FormattedTextEncoder();
317        }
318
319}