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