some cosmetic change, organzied imports, and made some accesses in a static way
[mir.git] / source / mir / misc / StringUtil.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package  mir.misc;
31
32 import gnu.regexp.RE;
33 import gnu.regexp.REException;
34
35 import java.text.NumberFormat;
36 import java.util.Calendar;
37 import java.util.Date;
38 import java.util.GregorianCalendar;
39 import java.util.TimeZone;
40
41 /**
42  * Statische Hilfsmethoden zur Stringbehandlung
43  *
44  * @version $Id: StringUtil.java,v 1.33.2.5 2003/10/23 14:55:28 rk Exp $
45  * @author mir-coders group
46  *
47  */
48 public final class StringUtil {
49
50   private static RE   re_newline2br, re_brbr2p, re_mail, re_url, re_tags,
51                       re_tables, re_forbiddenTags;
52
53   private StringUtil() { }  // this avoids contruction
54
55   static {
56     try {
57       //precompile regex
58       re_newline2br = new RE("(\r?\n){1}");
59       re_brbr2p     = new RE("(<br>\r?\n<br>){1,}");
60       re_mail       = new RE("\\b([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_-]+)\\.([a-zA-Z0-9_.-]+)\\b");
61       re_url        = new RE("((https://)|(http://)|(ftp://)){1}([a-zA-Z0-9_-]+).([a-zA-Z0-9_.:-]+)/?([^ \t\r\n<>\\)\\]]+[^ \t\r\n.,<>\\)\\]])");
62       re_tags       = new RE("<[^>]*>",RE.REG_ICASE);
63       re_tables = new RE("<[ \t\r\n/]*(table|td|tr)[ \t\r\n]*>",RE.REG_ICASE);
64       re_forbiddenTags = new RE("<[ \t\r\n/]*(html|meta|body|head|script)[ \t\r\n]*>",RE.REG_ICASE);
65     }
66     catch (REException e){
67       System.err.println("FATAL: StringUtil: could not precompile REGEX: "+e.toString());
68     }
69   }
70
71   /**
72    * Formats a number with the specified minimum and maximum number of digits.
73    **/
74   public static synchronized String zeroPaddingNumber(long value, int minDigits,
75       int maxDigits)
76   {
77     NumberFormat numberFormat = NumberFormat.getInstance();
78     numberFormat.setMinimumIntegerDigits(minDigits);
79     numberFormat.setMaximumIntegerDigits(maxDigits);
80     return numberFormat.format(value);
81   }
82
83   /**
84    * Wandelt Datum in einen 8-ziffrigen String um (yyyymmdd)
85    * @param theDate
86    * @return 8-ziffriger String (yyyymmdd)
87    */
88
89   public static final String date2webdbDate (GregorianCalendar theDate) {
90     StringBuffer webdbDate = new StringBuffer();
91     webdbDate.append(String.valueOf(theDate.get(Calendar.YEAR)));
92     webdbDate.append(pad2(theDate.get(Calendar.MONTH) + 1));
93     webdbDate.append(pad2(theDate.get(Calendar.DATE)));
94     return  webdbDate.toString();
95   }
96
97   /**
98    * Wandelt Calendar in einen 12-ziffrigen String um (yyyymmddhhmm)
99    * @param theDate
100    * @return 12-ziffriger String (yyyymmdd)
101    */
102
103   public static final String date2webdbDateTime (GregorianCalendar theDate) {
104     StringBuffer webdbDate = new StringBuffer();
105     webdbDate.append(String.valueOf(theDate.get(Calendar.YEAR)));
106     webdbDate.append(pad2(theDate.get(Calendar.MONTH) + 1));
107     webdbDate.append(pad2(theDate.get(Calendar.DATE)));
108     webdbDate.append(pad2(theDate.get(Calendar.HOUR)));
109     webdbDate.append(pad2(theDate.get(Calendar.MINUTE)));
110     return  webdbDate.toString();
111   }
112
113   /**
114    * Return a http://www.w3.org/TR/NOTE-datetime formatted date (yyyy-mm-ddThh:mm:ssTZ)
115    * @param theDate
116    * @return w3approved datetime
117    */
118
119   public static final String date2w3DateTime (GregorianCalendar theDate) {
120     StringBuffer webdbDate = new StringBuffer();
121     webdbDate.append(String.valueOf(theDate.get(Calendar.YEAR)));
122     webdbDate.append("-");
123     webdbDate.append(pad2(theDate.get(Calendar.MONTH) + 1));
124     webdbDate.append("-");
125     webdbDate.append(pad2(theDate.get(Calendar.DATE)));
126     webdbDate.append("T");
127     webdbDate.append(pad2(theDate.get(Calendar.HOUR_OF_DAY)));
128     webdbDate.append(":");
129     webdbDate.append(pad2(theDate.get(Calendar.MINUTE)));
130     webdbDate.append(":");
131     webdbDate.append(pad2(theDate.get(Calendar.SECOND)));
132     //assumes you are an hour-multiple away from UTC....
133     int offset=(theDate.get(Calendar.ZONE_OFFSET)/(60*60*1000));
134     if (offset < 0){
135       webdbDate.append("-");
136     }
137     else{
138       webdbDate.append("+");
139     }
140     webdbDate.append(pad2(Math.abs(offset)));
141     webdbDate.append(":00");
142     return  webdbDate.toString();
143   }
144
145   /**
146    * wandelt Calendar in dd.mm.yyyy / hh.mm um
147    * @param theDate
148    * @return String mit (dd.mm.yyyy / hh.mm um)
149    */
150   public static String date2readableDateTime (GregorianCalendar theDate) {
151     String readable = "";
152     int hour;
153     readable += pad2(theDate.get(Calendar.DATE));
154     readable += "." + pad2(theDate.get(Calendar.MONTH) + 1);
155     readable += "." + String.valueOf(theDate.get(Calendar.YEAR));
156     hour = theDate.get(Calendar.HOUR);
157     if (theDate.get(Calendar.AM_PM) == Calendar.PM)
158       hour += 12;
159     readable += " / " + pad2(hour);
160     readable += ":" + pad2(theDate.get(Calendar.MINUTE));
161     return  readable;
162   }
163
164   /**
165   *  deleteForbiddenTags
166   *  this method deletes all <script>, <body> and <head>-tags
167   */
168   public static final String deleteForbiddenTags(String haystack) {
169     return re_forbiddenTags.substituteAll(haystack,"");
170   }
171
172   /**
173    *  deleteHTMLTableTags
174    *  this method deletes all <table>, <tr> and <td>-tags
175    */
176   public static final String deleteHTMLTableTags(String haystack) {
177     return re_tables.substituteAll(haystack,"");
178   }
179
180   /**
181    * wandelt eine Datum in einen 8-buchstabigen String, der durch <code>/</code>
182    * getrennt ist.
183    *
184    * @param webdbDate
185    * @return String mit <code>/yyyy/mm/dd</code>
186    */
187   public static final String webdbDate2path (String webdbDate) {
188     StringBuffer path = new StringBuffer();
189     path.append("/").append(webdbDate.substring(0, 4));
190     path.append("/").append(webdbDate.substring(4, 6));
191     path.append("/");
192     //who did this?
193     //path.append("/").append(webdbDate.substring(6, 8));
194     return  path.toString();
195   }
196
197   /**
198    * Ersetzt in String <code>s</code> das Regexp <code>pattern</code> durch <code>substitute</code>
199    * @param s
200    * @param pattern
201    * @param substitute
202    * @return String mit den Ersetzungen
203    */
204   public static String regexpReplace(String haystack, String pattern, String substitute) {
205     try {
206       RE regex = new RE(pattern);
207       return regex.substituteAll(haystack,substitute);
208     } catch(REException ex){
209       return null;
210     }
211   }
212
213   /**
214    * L?scht <code>/</code> am Ende des Strings, falls vorhanden
215    * @param path
216    * @return String ohne <code>/</code> am Ende
217    */
218   public static final String removeSlash (String path) {
219     return  path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length()
220         - 1) : path;
221   }
222
223   /**
224    * formatiert eine Zahl (0-99) zweistellig (z.B. 5 -> 05)
225    * @return zwistellige Zahl
226    */
227   public static String pad2 (int number) {
228     return  number < 10 ? "0" + number : String.valueOf(number);
229   }
230
231   /**
232    * formatiert eine Zahl (0-999) dreistellig (z.B. 7 -> 007)
233    *
234    * @return 3-stellige Zahl
235    */
236   public static String pad3 (int number) {
237     return  number < 10 ? "00" + number : number < 100 ? "0" + number : String.valueOf(number);
238   }
239
240   /**
241    * Liefert Default-Wert def zur?ck, wenn String <code>s</code>
242    * kein Integer ist.
243    *
244    * @param s
245    * @param def
246    * @return geparster int aus s oder def
247    */
248   public static int parseInt(String s, int def) {
249     if (s == null) return def;
250     try {
251       return Integer.parseInt(s);
252     } catch (NumberFormatException e) {
253       return def;
254     }
255   }
256
257
258   /**
259    *  convertNewline2P ist eine regex-routine zum umwandeln von 2 oder mehr newlines (\n)
260    *  in den html-tag <p>
261    *  nur sinnvoll, wenn text nicht im html-format eingegeben
262    */
263   public static String convertNewline2P(String haystack) {
264     return re_brbr2p.substituteAll(haystack,"\n</p><p>");
265   }
266
267   /**
268    *  convertNewline2Break ist eine regex-routine zum umwandeln von 1 newline (\n)
269    *  in den html-tag <br>
270    *  nur sinnvoll, wenn text nicht im html-format eingegeben
271    */
272   public static String convertNewline2Break(String haystack) {
273     return re_newline2br.substituteAll(haystack,"$0<br />");
274   }
275
276   /**
277    *  createMailLinks wandelt text im email-adressenformat
278    *  in einen klickbaren link um
279    *  nur sinnvoll, wenn text nicht im html-format eingegeben
280    */
281   public static String createMailLinks(String haystack) {
282     return re_mail.substituteAll(haystack,"<a href=\"mailto:$0\">$0</a>");
283   }
284
285
286   /**
287    *  createMailLinks wandelt text im email-adressenformat
288    *  in einen klickbaren link um
289    *  nur sinnvoll, wenn text nicht im html-format eingegeben
290    */
291   public static String createMailLinks(String haystack, String imageRoot, String mailImage) {
292     return re_mail.substituteAll(haystack,"<img src=\""+imageRoot+"/"+mailImage+"\" border=\"0\"/>&#160;<a href=\"mailto:$0\">$0</a>");
293   }
294
295
296   /**
297    *  createURLLinks wandelt text im url-format
298    *  in einen klickbaren link um
299    *  nur sinnvoll, wenn text nicht im html-format eingegeben
300    */
301   public static String createURLLinks(String haystack) {
302     return re_url.substituteAll(haystack,"<a href=\"$0\">$0</a>");
303   }
304
305   /**
306    * this routine takes text in url format and makes
307    * a clickaeble "<href>" link removing any "illegal" html tags
308    * @param haystack, the url
309    * @param title, the href link text
310    * @param imagRoot, the place to find icons
311    * @param extImage, the url of the icon to show next to the link
312    * @return a String containing the url
313    */
314   public static String createURLLinks(String haystack, String title, String imageRoot,String extImage) {
315     if (title == null) {
316       return re_url.substituteAll(haystack,"<img src=\""+imageRoot+"/"+extImage+"\" border=\"0\"/>&#160;<a href=\"$0\">$0</a>");
317     } else {
318       title = removeHTMLTags(title);
319       return re_url.substituteAll(haystack,"<img src=\""+imageRoot+"/"+extImage+"\" border=\"0\"/>&#160;<a href=\"$0\">"+title+"</a>");
320     }
321   }
322
323   /**
324    * this routine takes text in url format and makes
325    * a clickaeble "<href>" link removing any "illegal" html tags
326    * @param haystack, the url
327    * @param imageRoot, the place to find icons
328    * @param extImage, the url of the icon to show next to the link
329    * @param intImage, unused
330    * @return a String containing the url
331    */
332   public static String createURLLinks(String haystack, String title, String imageRoot,String extImage,String intImage) {
333     return createURLLinks(haystack, title, imageRoot, extImage);
334   }
335
336   /**
337    * this method deletes all html tags
338    */
339   public static final String removeHTMLTags(String haystack){
340     return re_tags.substituteAll(haystack,"");
341   }
342
343   /**
344    * this method deletes all but the approved tags html tags
345    * it also deletes approved tags which contain malicious-looking attributes and doesn't work at all
346    */
347   public static String approveHTMLTags(String haystack){
348     try {
349       String approvedTags="a|img|h1|h2|h3|h4|h5|h6|br|b|i|strong|p";
350       String badAttributes="onAbort|onBlur|onChange|onClick|onDblClick|onDragDrop|onError|onFocus|onKeyDown|onKeyPress|onKeyUp|onLoad|onMouseDown|onMouseMove|onMouseOut|onMouseOver|onMouseUp|onMove|onReset|onResize|onSelect|onSubmit|onUnload";
351       String approvedProtocols="rtsp|http|ftp|https|freenet|mailto";
352
353       // kill all the bad tags that have attributes
354       String s = "<\\s*/?\\s*(?!(("+approvedTags+")\\s))\\w+\\s[^>]*>";
355       RE regex = new RE(s,RE.REG_ICASE);
356       haystack = regex.substituteAll(haystack,"");
357
358       // kill all the bad tags that are attributeless
359       regex = new RE("<\\s*/?\\s*(?!(("+approvedTags+")\\s*>))\\w+\\s*>",RE.REG_ICASE);
360       haystack = regex.substituteAll(haystack,"");
361
362       // kill all the tags which have a javascript attribute like onLoad
363       regex = new RE("<[^>]*("+badAttributes+")[^>]*>",RE.REG_ICASE);
364       haystack = regex.substituteAll(haystack,"");
365
366       // kill all the tags which include a url to an unacceptable protocol
367       regex = new RE("<\\s*a\\s+[^>]*href=(?!(\'|\")?("+approvedProtocols+"))[^>]*>",RE.REG_ICASE);
368       haystack = regex.substituteAll(haystack,"");
369
370       return haystack;
371     } catch(REException ex){
372       ex.printStackTrace();
373       return null;
374     }
375   }
376
377
378   /**
379    *  createHTML ruft alle regex-methoden zum unwandeln eines nicht
380    *  htmlcodierten string auf und returnt einen htmlcodierten String
381    */
382   public static String createHTML(String content){
383     content=convertNewline2Break(content);
384     content=convertNewline2P(content);
385     content=createMailLinks(content);
386     content=createURLLinks(content);
387     return content;
388   }
389
390
391   /**
392    *  createHTML ruft alle regex-methoden zum unwandeln eines nicht
393    *  htmlcodierten string auf und returnt einen htmlcodierten String
394    */
395   public static String createHTML(String content,String producerDocRoot,String mailImage,String extImage,String intImage){
396     content=convertNewline2Break(content);
397     content=convertNewline2P(content);
398     content=createMailLinks(content,producerDocRoot,mailImage);
399     content=createURLLinks(content,null,producerDocRoot,extImage,intImage);
400     return content;
401   }
402
403   /**
404    * Converts mir's horrible internal date format (yyyy-MM-dd HH:mm:ss+zz) into a java Date
405    *
406    * @param anInternalDate
407    * @return
408    */
409   public static Date convertMirInternalDateToDate(String anInternalDate) {
410     Calendar calendar = new GregorianCalendar();
411
412     int year;
413     int month;
414     int day;
415     int hours;
416     int minutes;
417     int seconds;
418     int timezoneOffset;
419
420     year = Integer.parseInt(anInternalDate.substring(0,4));
421     month = Integer.parseInt(anInternalDate.substring(5,7));
422     day = Integer.parseInt(anInternalDate.substring(8,10));
423     hours = Integer.parseInt(anInternalDate.substring(11,13));
424     minutes = Integer.parseInt(anInternalDate.substring(14,16));
425     seconds = Integer.parseInt(anInternalDate.substring(17,19));
426
427     timezoneOffset = Integer.parseInt(anInternalDate.substring(20,22));
428     if (anInternalDate.charAt(19) == '-')
429       timezoneOffset = -timezoneOffset;
430
431     calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
432     calendar.set(year, month-1, day, hours, minutes, seconds);
433     calendar.add(Calendar.HOUR, -timezoneOffset);
434
435     return calendar.getTime();
436   }
437
438 }
439