001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: XMLUtil.java,v 1.5 2007/01/15 15:58:40 drmlipp Exp $
021: *
022: * $Log: XMLUtil.java,v $
023: * Revision 1.5 2007/01/15 15:58:40 drmlipp
024: * Use form title to set portlet title.
025: *
026: * Revision 1.4 2006/12/12 09:42:22 drmlipp
027: * Added URIs and W3C DOM to string helper.
028: *
029: * Revision 1.3.4.3 2006/12/11 12:10:43 drmlipp
030: * Added comment.
031: *
032: * Revision 1.3.4.2 2006/12/05 16:37:22 drmlipp
033: * Added namespaces.
034: *
035: * Revision 1.3.4.1 2006/11/27 16:57:33 drmlipp
036: * Outputting event.
037: *
038: * Revision 1.3 2006/09/29 12:32:08 drmlipp
039: * Consistently using WfMOpen as projct name now.
040: *
041: * Revision 1.2 2006/03/08 14:46:42 drmlipp
042: * Synchronized with 1.3.3p5.
043: *
044: * Revision 1.1.1.4.6.3 2005/12/16 10:55:11 drmlipp
045: * Fixed new methods' signature.
046: *
047: * Revision 1.1.1.4.6.2 2005/12/16 09:38:50 drmlipp
048: * Added support for parsing local datetime specifications.
049: *
050: * Revision 1.1.1.4.6.1 2005/12/14 09:56:26 drmlipp
051: * Added support for parsing xsd:double.
052: *
053: * Revision 1.1.1.4 2004/08/18 15:17:35 drmlipp
054: * Update to 1.2
055: *
056: * Revision 1.10 2004/03/16 17:04:27 lipp
057: * Added some useful constansts.
058: *
059: * Revision 1.9 2003/09/07 19:40:07 lipp
060: * Fixed handling of milliseconds.
061: *
062: * Revision 1.8 2003/09/05 15:08:39 lipp
063: * Fixed sign handling.
064: *
065: * Revision 1.7 2003/09/05 11:15:43 lipp
066: * Added parsing of XSD duration.
067: *
068: * Revision 1.6 2003/09/05 09:44:05 lipp
069: * Fixed wrong usage of SimpleDateFormat (isn't thread safe).
070: *
071: * Revision 1.5 2003/06/27 08:51:46 lipp
072: * Fixed copyright/license information.
073: *
074: * Revision 1.4 2003/05/01 17:52:54 lipp
075: * Added xmlns uri.
076: *
077: * Revision 1.3 2003/03/31 17:01:36 huaiyang
078: * error with indexOutOfBoundException fixed.
079: *
080: * Revision 1.2 2003/03/31 12:43:12 huaiyang
081: * String of DateTime with millisec supported.
082: *
083: * Revision 1.1 2003/03/27 16:32:20 lipp
084: * Started support for addditional data types.
085: *
086: */
087: package de.danet.an.util;
088:
089: import java.util.Date;
090: import java.util.TimeZone;
091: import java.util.regex.Matcher;
092: import java.util.regex.Pattern;
093:
094: import java.io.StringWriter;
095: import java.text.DateFormat;
096: import java.text.ParseException;
097: import java.text.SimpleDateFormat;
098:
099: import javax.xml.transform.Transformer;
100: import javax.xml.transform.TransformerConfigurationException;
101: import javax.xml.transform.TransformerException;
102: import javax.xml.transform.TransformerFactory;
103: import javax.xml.transform.TransformerFactoryConfigurationError;
104: import javax.xml.transform.dom.DOMSource;
105: import javax.xml.transform.stream.StreamResult;
106:
107: import org.w3c.dom.Element;
108: import org.w3c.dom.Node;
109:
110: /**
111: * This class provides some utility functions for handling XML.
112: *
113: * @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
114: * @version $Revision: 1.5 $
115: */
116: public class XMLUtil {
117:
118: private static DateFormat xsdGMTDateTimeFormat;
119: private static DateFormat xsdLocalDateTime;
120: static {
121: xsdGMTDateTimeFormat = new SimpleDateFormat(
122: "yyyy-MM-dd'T'HH:mm:ss'Z'");
123: xsdGMTDateTimeFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
124: xsdLocalDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
125: }
126:
127: /** The URI for xmlns attributes. */
128: public static final String XMLNS_NS = "http://www.w3.org/2000/xmlns/";
129:
130: /** The URI for XML schema. */
131: public static final String XMLNS_SCHEMA = "http://www.w3.org/2001/XMLSchema";
132:
133: /** The URI for XML schema instance. */
134: public static final String XMLNS_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";
135:
136: /** The URI for XForms. */
137: public static final String XMLNS_XFORMS = "http://www.w3.org/2002/xforms";
138:
139: /** The URI for XHTML */
140: public static final String XMLNS_XHTML = "http://www.w3.org/1999/xhtml";
141:
142: /**
143: * Parses dateTime string as specified in <a
144: * href="http://www.w3.org/TR/xmlschema-2/#dateTime"> XML Schema
145: * Part 2</a>, i.e. '-'? CCYY '-' MM '-' DD 'T' hh ':' mm ':' ss ['.' s+]
146: * ['Z' | ('+'|'-') hh ':' mm].
147: * @param dateTime the date and time string.
148: * @return parsed date.
149: * @throws ParseException if the string cannot be parsed.
150: */
151: public static Date parseXsdDateTime(String dateTime)
152: throws ParseException {
153: DateFormat dateTimeFormat = (DateFormat) xsdLocalDateTime
154: .clone();
155: String millis = null;
156: int posOfExt = 19;
157: if (dateTime.charAt(0) == '-') {
158: posOfExt = 20;
159: }
160: if (dateTime.length() > posOfExt) {
161: char c = dateTime.charAt(posOfExt);
162: if (c == '.') {
163: // if the given datetime has ms, separate them
164: int eoms = posOfExt + 1;
165: while (eoms < dateTime.length()
166: && Character.isDigit(dateTime.charAt(eoms))) {
167: eoms += 1;
168: }
169: millis = dateTime.substring(posOfExt, eoms);
170: dateTime = dateTime.substring(0, posOfExt)
171: + dateTime.substring(eoms);
172: }
173: if (dateTime.length() > posOfExt) {
174: c = dateTime.charAt(posOfExt);
175: if (c == '+' || c == '-') {
176: dateTimeFormat.setTimeZone(TimeZone
177: .getTimeZone("GMT"
178: + dateTime.substring(posOfExt)));
179: dateTime = dateTime.substring(0, posOfExt);
180: } else if (c == 'Z') {
181: dateTimeFormat.setTimeZone(TimeZone
182: .getTimeZone("GMT"));
183: } else {
184: throw new ParseException(
185: "Illegal timezone specification: "
186: + dateTime, posOfExt);
187: }
188: }
189: }
190: Date res = dateTimeFormat.parse(dateTime);
191: if (millis != null) {
192: res.setTime(res.getTime()
193: + Math.round(Float.parseFloat(millis) * 1000));
194: }
195: return res;
196: }
197:
198: /**
199: * Convert a given date to a XSD compliant datetime representation with
200: * unspecified timezone (i.e. local time).
201: * @param timestamp the date to convert.
202: * @return the result.
203: */
204: public static String toXsdLocalDateTime(Date timestamp) {
205: return xsdLocalDateTime.format(timestamp);
206: }
207:
208: /**
209: * Convert date given as milliseconds since January 1, 1970, 00:00:00 GMT
210: * to a XSD compliant datetime representation with unspecified
211: * timezone (i.e. local time).
212: * @param time the time to convert.
213: * @return the result.
214: */
215: public static String toXsdLocalDateTime(long time) {
216: return xsdLocalDateTime.format(new Date(time));
217: }
218:
219: /**
220: * Convert a given date to a XSD compliant GMT datetime representation.
221: * @param timestamp the date to convert.
222: * @return the result.
223: */
224: public static String toXsdGMTDateTime(Date timestamp) {
225: return xsdGMTDateTimeFormat.format(timestamp);
226: }
227:
228: /**
229: * Convert date given as milliseconds since January 1, 1970, 00:00:00 GMT
230: * to a XSD compliant GMT datetime representation.
231: * @param time the time to convert.
232: * @return the result.
233: */
234: public static String toXsdGMTDateTime(long time) {
235: return xsdLocalDateTime.format(new Date(time));
236: }
237:
238: private static Pattern parsePat = Pattern
239: .compile("^(-)?P(([0-9]+?)Y)?(([0-9]+?)M)?(([0-9]+?)D)?"
240: + "(T(([0-9]+?)H)?(([0-9]+?)M)?(([0-9\\.]+?)S)?)?$");
241:
242: /**
243: * Parses duration string as specified in <a
244: * href="http://www.w3.org/TR/xmlschema-2/#duration"> XML Schema
245: * Part 2</a>, i.e. [-]P[nY][nM][nD][T[nH][nM][n[.m]S]], with at
246: * least on value in the time part if it exists or one value in
247: * the date part if the time part does not exist.
248: * @param duration the duration to be parsed
249: * @return parsed duration
250: * @throws ParseException if the string cannot be parsed
251: */
252: public static Duration parseXsdDuration(String duration)
253: throws ParseException {
254: Duration res = new Duration();
255: Matcher m = parsePat.matcher(duration);
256: if (!m.matches()) {
257: throw new ParseException(
258: "Invalid duration specification: \"" + duration
259: + "\".", 0);
260: }
261: int sign = 1;
262: String g = m.group(1);
263: if (g != null) {
264: sign = -1;
265: }
266: try {
267: g = m.group(3);
268: if (g != null) {
269: res.setYears(sign * Integer.parseInt(g));
270: }
271: g = m.group(5);
272: if (g != null) {
273: res.setMonths(sign * Integer.parseInt(g));
274: }
275: g = m.group(7);
276: if (g != null) {
277: res.setDays(sign * Integer.parseInt(g));
278: }
279: g = m.group(10);
280: if (g != null) {
281: res.setHours(sign * Integer.parseInt(g));
282: }
283: g = m.group(12);
284: if (g != null) {
285: res.setMinutes(sign * Integer.parseInt(g));
286: }
287: g = m.group(14);
288: if (g != null) {
289: res.setSeconds(sign * Float.parseFloat(g));
290: }
291: return res;
292: } catch (NumberFormatException e) {
293: throw new ParseException(e.getMessage(), 0);
294: }
295: }
296:
297: /**
298: * Parses a float or double string as specified in <a
299: * href="http://www.w3.org/TR/xmlschema-2/#float"> XML Schema
300: * Part 2</a>.
301: * @param number the string to parse.
302: * @return parsed value.
303: * @throws NumberFormatException if the string cannot be parsed.
304: */
305: public static double parseXsdDouble(String number)
306: throws NumberFormatException {
307: number = number.trim();
308: if (number.equals("INF")) {
309: return Double.POSITIVE_INFINITY;
310: }
311: if (number.equals("-INF")) {
312: return Double.NEGATIVE_INFINITY;
313: }
314: return Double.parseDouble(number);
315: }
316:
317: /**
318: * Parses a boolean string as specified in <a
319: * href="http://www.w3.org/TR/xmlschema-2/#boolean"> XML Schema
320: * Part 2</a>.
321: * @param value the string to parse.
322: * @return parsed result.
323: * @throws ParseException if the string cannot be parsed.
324: */
325: public static boolean parseXsdBoolean(String value)
326: throws ParseException {
327: value = value.trim();
328: if (value.equals("true") || value.equals("1")) {
329: return true;
330: }
331: if (value.equals("false") || value.equals("0")) {
332: return false;
333: }
334: throw new ParseException("Not an xsd boolean: " + value, 0);
335: }
336:
337: /**
338: * Returns the text contents of this node, similar to
339: * <a href="http://java.sun.com/j2se/1.5.0/docs/api/org/w3c/dom/Node.html#getTextContent()">
340: * http://java.sun.com/j2se/1.5.0/docs/api/org/w3c/dom/Node.html#getTextContent()</a>
341: * but without recursion.
342: *
343: * @param node the node.
344: * @return text content.
345: */
346: public static String getFirstLevelTextContent(Node node) {
347: if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
348: return node.getNodeValue();
349: }
350: StringBuffer buffer = new StringBuffer();
351:
352: for (Node child = node.getFirstChild(); child != null; child = child
353: .getNextSibling()) {
354: if (child.getNodeType() == Node.TEXT_NODE
355: || child.getNodeType() == Node.CDATA_SECTION_NODE) {
356: buffer.append(child.getNodeValue());
357: }
358: }
359: return buffer.toString();
360: }
361:
362: /**
363: * Converts a W3C DOM tree to an XML string.
364: * @param tree the W3C DOM root
365: * @return the result
366: */
367: public static String w3cDomToString(Element tree) {
368: try {
369: Transformer t = TransformerFactory.newInstance()
370: .newTransformer();
371: t.setOutputProperty("indent", "yes");
372: StringWriter out = new StringWriter();
373: t.transform(new DOMSource(tree), new StreamResult(out));
374: return (out.toString());
375: } catch (TransformerConfigurationException e) {
376: return "<exception>" + e.getMessage() + "</exception>";
377: } catch (TransformerFactoryConfigurationError e) {
378: return "<exception>" + e.getMessage() + "</exception>";
379: } catch (TransformerException e) {
380: return "<exception>" + e.getMessage() + "</exception>";
381: }
382: }
383: }
|