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 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("&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                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}