aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache/fop/util')
-rw-r--r--src/java/org/apache/fop/util/BreakUtil.java65
-rw-r--r--src/java/org/apache/fop/util/CharUtilities.java5
-rw-r--r--src/java/org/apache/fop/util/ColorUtil.java294
-rw-r--r--src/java/org/apache/fop/util/LogUtil.java2
-rw-r--r--src/java/org/apache/fop/util/QName.java190
-rw-r--r--src/java/org/apache/fop/util/UnitConv.java2
-rw-r--r--src/java/org/apache/fop/util/XMLResourceBundle.java398
-rw-r--r--src/java/org/apache/fop/util/XMLizable.java13
-rw-r--r--src/java/org/apache/fop/util/text/AdvancedMessageFormat.java487
-rw-r--r--src/java/org/apache/fop/util/text/ChoiceFieldPart.java91
-rw-r--r--src/java/org/apache/fop/util/text/EqualsFieldPart.java92
-rw-r--r--src/java/org/apache/fop/util/text/GlyphNameFieldPart.java89
-rw-r--r--src/java/org/apache/fop/util/text/HexFieldPart.java84
-rw-r--r--src/java/org/apache/fop/util/text/IfFieldPart.java116
-rw-r--r--src/java/org/apache/fop/util/text/LocatorFormatter.java42
15 files changed, 1664 insertions, 306 deletions
diff --git a/src/java/org/apache/fop/util/BreakUtil.java b/src/java/org/apache/fop/util/BreakUtil.java
new file mode 100644
index 000000000..c0528464d
--- /dev/null
+++ b/src/java/org/apache/fop/util/BreakUtil.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import org.apache.fop.fo.Constants;
+
+/**
+ * A utility class for manipulating break classes (the break-before and break-after properties).
+ */
+public final class BreakUtil {
+
+ private BreakUtil() { }
+
+ // TODO replace that with a proper 1.5 enumeration ASAP
+ private static int getBreakClassPriority(int breakClass) {
+ switch (breakClass) {
+ case Constants.EN_AUTO: return 0;
+ case Constants.EN_COLUMN: return 1;
+ case Constants.EN_PAGE: return 2;
+ case Constants.EN_EVEN_PAGE: return 3;
+ case Constants.EN_ODD_PAGE: return 3;
+ default: throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Compares the given break classes and return the one that wins. even-page and
+ * odd-page win over page, which wins over column, which wins over auto. If even-page
+ * and odd-page are compared to each other, which one will be returned is undefined.
+ *
+ * @param break1 a break class, one of {@link Constants#EN_AUTO},
+ * {@link Constants#EN_COLUMN}, {@link Constants#EN_PAGE},
+ * {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}
+ * @param break2 another break class
+ * @return the break class that wins the comparison
+ */
+ public static int compareBreakClasses(int break1, int break2) {
+ // TODO implement some warning mechanism if even-page and odd-page are being compared
+ int p1 = getBreakClassPriority(break1);
+ int p2 = getBreakClassPriority(break2);
+ if (p1 < p2) {
+ return break2;
+ } else {
+ return break1;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/util/CharUtilities.java b/src/java/org/apache/fop/util/CharUtilities.java
index bfcc90a64..4910a371c 100644
--- a/src/java/org/apache/fop/util/CharUtilities.java
+++ b/src/java/org/apache/fop/util/CharUtilities.java
@@ -68,7 +68,10 @@ public class CharUtilities {
public static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF';
/** soft hyphen */
public static final char SOFT_HYPHEN = '\u00AD';
-
+ /** missing ideograph */
+ public static final char MISSING_IDEOGRAPH = '\u25A1';
+ /** Unicode value indicating the the character is "not a character". */
+ public static final char NOT_A_CHARACTER = '\uFFFF';
/**
* Utility class: Constructor prevents instantiating when subclassed.
diff --git a/src/java/org/apache/fop/util/ColorUtil.java b/src/java/org/apache/fop/util/ColorUtil.java
index 37762b1e8..fbfc68c36 100644
--- a/src/java/org/apache/fop/util/ColorUtil.java
+++ b/src/java/org/apache/fop/util/ColorUtil.java
@@ -22,13 +22,11 @@ package org.apache.fop.util;
import java.awt.Color;
import java.awt.color.ColorSpace;
import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
-import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fo.expr.PropertyException;
@@ -76,8 +74,8 @@ public final class ColorUtil {
* <li>system-color(colorname)</li>
* <li>transparent</li>
* <li>colorname</li>
- * <li>fop-rgb-icc</li>
- * <li>cmyk</li>
+ * <li>fop-rgb-icc(r,g,b,cs,cs-src,[num]+) (r/g/b: 0..1, num: 0..1)</li>
+ * <li>cmyk(c,m,y,k) (0..1)</li>
* </ul>
*
* @param foUserAgent FOUserAgent object
@@ -167,30 +165,28 @@ public final class ColorUtil {
try {
if (poss != -1 && pose != -1) {
value = value.substring(poss + 1, pose);
- StringTokenizer st = new StringTokenizer(value, ",");
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- red = Float.parseFloat(str.substring(2)) / 255f;
- }
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- green = Float.parseFloat(str.substring(2)) / 255f;
- }
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- blue = Float.parseFloat(str.substring(2)) / 255f;
- } else {
- throw new NumberFormatException();
+ String[] args = value.split(",");
+ if (args.length != 3) {
+ throw new PropertyException(
+ "Invalid number of arguments for a java.awt.Color: " + value);
}
- if ((red < 0.0 || red > 1.0) || (green < 0.0 || green > 1.0)
+
+ red = Float.parseFloat(args[0].trim().substring(2)) / 255f;
+ green = Float.parseFloat(args[1].trim().substring(2)) / 255f;
+ blue = Float.parseFloat(args[2].trim().substring(2)) / 255f;
+ if ((red < 0.0 || red > 1.0)
+ || (green < 0.0 || green > 1.0)
|| (blue < 0.0 || blue > 1.0)) {
throw new PropertyException("Color values out of range");
}
} else {
- throw new NullPointerException();
+ throw new IllegalArgumentException(
+ "Invalid format for a java.awt.Color: " + value);
}
+ } catch (PropertyException pe) {
+ throw pe;
} catch (Exception e) {
- throw new PropertyException("Unknown color format: " + value);
+ throw new PropertyException(e);
}
return new Color(red, green, blue);
}
@@ -210,44 +206,46 @@ public final class ColorUtil {
int pose = value.indexOf(")");
if (poss != -1 && pose != -1) {
value = value.substring(poss + 1, pose);
- StringTokenizer st = new StringTokenizer(value, ",");
try {
+ String[] args = value.split(",");
+ if (args.length != 3) {
+ throw new PropertyException(
+ "Invalid number of arguments: rgb(" + value + ")");
+ }
float red = 0.0f, green = 0.0f, blue = 0.0f;
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- red = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- red = Float.parseFloat(str) / 255f;
- }
+ String str = args[0].trim();
+ if (str.endsWith("%")) {
+ red = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100f;
+ } else {
+ red = Float.parseFloat(str) / 255f;
}
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- green = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- green = Float.parseFloat(str) / 255f;
- }
+ str = args[1].trim();
+ if (str.endsWith("%")) {
+ green = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100f;
+ } else {
+ green = Float.parseFloat(str) / 255f;
}
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- blue = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- blue = Float.parseFloat(str) / 255f;
- }
+ str = args[2].trim();
+ if (str.endsWith("%")) {
+ blue = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100f;
+ } else {
+ blue = Float.parseFloat(str) / 255f;
}
- if ((red < 0.0 || red > 1.0) || (green < 0.0 || green > 1.0)
+ if ((red < 0.0 || red > 1.0)
+ || (green < 0.0 || green > 1.0)
|| (blue < 0.0 || blue > 1.0)) {
throw new PropertyException("Color values out of range");
}
parsedColor = new Color(red, green, blue);
+ } catch (PropertyException pe) {
+ //simply re-throw
+ throw pe;
} catch (Exception e) {
- throw new PropertyException(
- "Arguments to rgb() must be [0..255] or [0%..100%]");
+ //wrap in a PropertyException
+ throw new PropertyException(e);
}
} else {
throw new PropertyException("Unknown color format: " + value
@@ -269,29 +267,28 @@ public final class ColorUtil {
Color parsedColor = null;
try {
int len = value.length();
- if ((len >= 4) && (len <= 5)) {
- // note: divide by 15 so F = FF = 1 and so on
- float red = Integer.parseInt(value.substring(1, 2), 16) / 15f;
- float green = Integer.parseInt(value.substring(2, 3), 16) / 15f;
- float blue = Integer.parseInt(value.substring(3, 4), 16) / 15f;
- float alpha = 1.0f;
- if (len == 5) {
- alpha = Integer.parseInt(value.substring(4), 16) / 15f;
- }
- parsedColor = new Color(red, green, blue, alpha);
+ int alpha;
+ if (len == 5 || len == 9) {
+ alpha = Integer.parseInt(
+ value.substring((len == 5) ? 3 : 7), 16);
+ } else {
+ alpha = 0xFF;
+ }
+ int red = 0, green = 0, blue = 0;
+ if ((len == 4) || (len == 5)) {
+ //multiply by 0x11 = 17 = 255/15
+ red = Integer.parseInt(value.substring(1, 2), 16) * 0x11;
+ green = Integer.parseInt(value.substring(2, 3), 16) * 0x11;
+ blue = Integer.parseInt(value.substring(3, 4), 16) * 0X11;
} else if ((len == 7) || (len == 9)) {
- int red = Integer.parseInt(value.substring(1, 3), 16);
- int green = Integer.parseInt(value.substring(3, 5), 16);
- int blue = Integer.parseInt(value.substring(5, 7), 16);
- int alpha = 255;
- if (len == 9) {
- alpha = Integer.parseInt(value.substring(7), 16);
- }
- parsedColor = new Color(red, green, blue, alpha);
+ red = Integer.parseInt(value.substring(1, 3), 16);
+ green = Integer.parseInt(value.substring(3, 5), 16);
+ blue = Integer.parseInt(value.substring(5, 7), 16);
} else {
throw new NumberFormatException();
}
- } catch (NumberFormatException e) {
+ parsedColor = new Color(red, green, blue, alpha);
+ } catch (Exception e) {
throw new PropertyException("Unknown color format: " + value
+ ". Must be #RGB. #RGBA, #RRGGBB, or #RRGGBBAA");
}
@@ -311,61 +308,50 @@ public final class ColorUtil {
int poss = value.indexOf("(");
int pose = value.indexOf(")");
if (poss != -1 && pose != -1) {
- value = value.substring(poss + 1, pose);
- StringTokenizer st = new StringTokenizer(value, ",");
+ String[] args = value.substring(poss + 1, pose).split(",");
+
try {
- float red = 0.0f, green = 0.0f, blue = 0.0f;
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- red = Float.parseFloat(str);
- }
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- green = Float.parseFloat(str);
+ if (args.length < 5) {
+ throw new PropertyException("Too few arguments for rgb-icc() function");
}
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- blue = Float.parseFloat(str);
- }
- /* Verify rgb replacement arguments */
- if ((red < 0.0 || red > 1.0)
- || (green < 0.0 || green > 1.0)
- || (blue < 0.0 || blue > 1.0)) {
- throw new PropertyException("Color values out of range");
- }
/* Get and verify ICC profile name */
- String iccProfileName = null;
- if (st.hasMoreTokens()) {
- iccProfileName = st.nextToken().trim();
- }
- if (iccProfileName == null || iccProfileName.length() == 0) {
+ String iccProfileName = args[3].trim();
+ if (iccProfileName == null || "".equals(iccProfileName)) {
throw new PropertyException("ICC profile name missing");
}
/* Get and verify ICC profile source */
- String iccProfileSrc = null;
- if (st.hasMoreTokens()) {
- iccProfileSrc = st.nextToken().trim();
- // Strip quotes
- iccProfileSrc = iccProfileSrc.substring(1, iccProfileSrc.length() - 1);
- }
- if (iccProfileSrc == null || iccProfileSrc.length() == 0) {
+ String iccProfileSrc = args[4].trim();
+ if (iccProfileSrc == null || "".equals(iccProfileSrc)) {
throw new PropertyException("ICC profile source missing");
}
- /* ICC profile arguments */
- List iccArgList = new LinkedList();
- while (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- iccArgList.add(new Float(str));
+ if (iccProfileSrc.startsWith("\"") || iccProfileSrc.startsWith("'")) {
+ iccProfileSrc = iccProfileSrc.substring(1);
+ }
+ if (iccProfileSrc.endsWith("\"") || iccProfileSrc.endsWith("'")) {
+ iccProfileSrc = iccProfileSrc.substring(0, iccProfileSrc.length() - 1);
}
- /* Copy ICC profile arguments from list to array */
- float[] iccComponents = new float[iccArgList.size()];
- for (int ix = 0; ix < iccArgList.size(); ix++) {
- iccComponents[ix] = ((Float)iccArgList.get(ix)).floatValue();
+ /* ICC profile arguments */
+ float[] iccComponents = new float[args.length - 5];
+ for (int ix = 4; ++ix < args.length;) {
+ iccComponents[ix - 5] = Float.parseFloat(args[ix].trim());
}
/* Ask FOP factory to get ColorSpace for the specified ICC profile source */
ColorSpace colorSpace = (foUserAgent != null
? foUserAgent.getFactory().getColorSpace(
foUserAgent.getBaseURL(), iccProfileSrc) : null);
+
+ float red = 0, green = 0, blue = 0;
+ red = Float.parseFloat(args[0].trim());
+ green = Float.parseFloat(args[1].trim());
+ blue = Float.parseFloat(args[2].trim());
+ /* Verify rgb replacement arguments */
+ if ((red < 0 || red > 1)
+ || (green < 0 || green > 1)
+ || (blue < 0 || blue > 1)) {
+ throw new PropertyException("Color values out of range. "
+ + "Fallback RGB arguments to fop-rgb-icc() must be [0..1]");
+ }
+
if (colorSpace != null) {
// ColorSpace available - create ColorExt (keeps track of replacement rgb
// values for possible later colorTOsRGBString call
@@ -375,15 +361,19 @@ public final class ColorUtil {
// ICC profile could not be loaded - use rgb replacement values */
log.warn("Color profile '" + iccProfileSrc
+ "' not found. Using rgb replacement values.");
- parsedColor = new Color(red, green, blue);
+ parsedColor = new Color(Math.round(red * 255),
+ Math.round(green * 255), Math.round(blue * 255));
}
+ } catch (PropertyException pe) {
+ //simply re-throw
+ throw pe;
} catch (Exception e) {
- throw new PropertyException(
- "Arguments to rgb-icc() must be [0..255] or [0%..100%]");
+ //wrap in a PropertyException
+ throw new PropertyException(e);
}
} else {
throw new PropertyException("Unknown color format: " + value
- + ". Must be fop-rgb-icc(r,g,b,NCNAME,\"src\",....)");
+ + ". Must be fop-rgb-icc(r,g,b,NCNAME,src,....)");
}
return parsedColor;
}
@@ -403,61 +393,58 @@ public final class ColorUtil {
int pose = value.indexOf(")");
if (poss != -1 && pose != -1) {
value = value.substring(poss + 1, pose);
- StringTokenizer st = new StringTokenizer(value, ",");
+ String[] args = value.split(",");
try {
+ if (args.length != 4) {
+ throw new PropertyException(
+ "Invalid number of arguments: cmyk(" + value + ")");
+ }
float cyan = 0.0f, magenta = 0.0f, yellow = 0.0f, black = 0.0f;
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- cyan = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- cyan = Float.parseFloat(str);
- }
+ String str = args[0].trim();
+ if (str.endsWith("%")) {
+ cyan = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100.0f;
+ } else {
+ cyan = Float.parseFloat(str);
+ }
+ str = args[1].trim();
+ if (str.endsWith("%")) {
+ magenta = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100.0f;
+ } else {
+ magenta = Float.parseFloat(str);
}
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- magenta = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- magenta = Float.parseFloat(str);
- }
+ str = args[2].trim();
+ if (str.endsWith("%")) {
+ yellow = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100.0f;
+ } else {
+ yellow = Float.parseFloat(str);
}
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- yellow = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- yellow = Float.parseFloat(str);
- }
+ str = args[3].trim();
+ if (str.endsWith("%")) {
+ black = Float.parseFloat(str.substring(0,
+ str.length() - 1)) / 100.0f;
+ } else {
+ black = Float.parseFloat(str);
}
- if (st.hasMoreTokens()) {
- String str = st.nextToken().trim();
- if (str.endsWith("%")) {
- black = Float.parseFloat(str.substring(0,
- str.length() - 1)) / 100.0f;
- } else {
- black = Float.parseFloat(str);
- }
- }
+
if ((cyan < 0.0 || cyan > 1.0)
|| (magenta < 0.0 || magenta > 1.0)
|| (yellow < 0.0 || yellow > 1.0)
|| (black < 0.0 || black > 1.0)) {
- throw new PropertyException("Color values out of range");
+ throw new PropertyException("Color values out of range"
+ + "Arguments to cmyk() must be in the range [0%-100%] or [0.0-1.0]");
}
float[] cmyk = new float[] {cyan, magenta, yellow, black};
CMYKColorSpace cmykCs = CMYKColorSpace.getInstance();
float[] rgb = cmykCs.toRGB(cmyk);
parsedColor = ColorExt.createFromFoRgbIcc(rgb[0], rgb[1], rgb[2],
null, "#CMYK", cmykCs, cmyk);
-
-
+ } catch (PropertyException pe) {
+ throw pe;
} catch (Exception e) {
- throw new PropertyException(
- "Arguments to cmyk() must be in the range [0%-100%] or [0.0-1.0]");
+ throw new PropertyException(e);
}
} else {
throw new PropertyException("Unknown color format: " + value
@@ -668,7 +655,6 @@ public final class ColorUtil {
colorMap.put("whitesmoke", new Color(245, 245, 245));
colorMap.put("yellow", new Color(255, 255, 0));
colorMap.put("yellowgreen", new Color(154, 205, 50));
-
colorMap.put("transparent", new Color(0, 0, 0, 0));
}
diff --git a/src/java/org/apache/fop/util/LogUtil.java b/src/java/org/apache/fop/util/LogUtil.java
index 60eafb51a..e33397fcb 100644
--- a/src/java/org/apache/fop/util/LogUtil.java
+++ b/src/java/org/apache/fop/util/LogUtil.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-/* $Id: $ */
+/* $Id$ */
package org.apache.fop.util;
diff --git a/src/java/org/apache/fop/util/QName.java b/src/java/org/apache/fop/util/QName.java
index 0794d3088..132f5b4dc 100644
--- a/src/java/org/apache/fop/util/QName.java
+++ b/src/java/org/apache/fop/util/QName.java
@@ -1,138 +1,52 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* $Id$ */
-
-package org.apache.fop.util;
-
-import java.io.Serializable;
-
-/**
- * Represents a qualified name of an XML element or an XML attribute.
- * <p>
- * Note: This class allows to carry a namespace prefix but it is not used in the equals() and
- * hashCode() methods.
- */
-public class QName implements Serializable {
-
- private static final long serialVersionUID = -5225376740044770690L;
-
- private String namespaceURI;
- private String localName;
- private String prefix;
- private int hashCode;
-
- /**
- * Main constructor.
- * @param namespaceURI the namespace URI
- * @param prefix the namespace prefix, may be null
- * @param localName the local name
- */
- public QName(String namespaceURI, String prefix, String localName) {
- if (localName == null) {
- throw new NullPointerException("Parameter localName must not be null");
- }
- if (localName.length() == 0) {
- throw new IllegalArgumentException("Parameter localName must not be empty");
- }
- this.namespaceURI = namespaceURI;
- this.prefix = prefix;
- this.localName = localName;
- this.hashCode = toHashString().hashCode();
- }
-
- /**
- * Main constructor.
- * @param namespaceURI the namespace URI
- * @param qName the qualified name
- */
- public QName(String namespaceURI, String qName) {
- if (qName == null) {
- throw new NullPointerException("Parameter localName must not be null");
- }
- if (qName.length() == 0) {
- throw new IllegalArgumentException("Parameter localName must not be empty");
- }
- this.namespaceURI = namespaceURI;
- int p = qName.indexOf(':');
- if (p > 0) {
- this.prefix = qName.substring(0, p);
- this.localName = qName.substring(p + 1);
- } else {
- this.prefix = null;
- this.localName = qName;
- }
- this.hashCode = toHashString().hashCode();
- }
-
- /** @return the namespace URI */
- public String getNamespaceURI() {
- return this.namespaceURI;
- }
-
- /** @return the namespace prefix */
- public String getPrefix() {
- return this.prefix;
- }
-
- /** @return the local name */
- public String getLocalName() {
- return this.localName;
- }
-
- /** @return the fully qualified name */
- public String getQName() {
- return getPrefix() != null ? getPrefix() + ':' + getLocalName() : getLocalName();
- }
-
- /** {@inheritDoc} */
- public int hashCode() {
- return this.hashCode;
- }
-
- /** {@inheritDoc} */
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- } else if (obj == this) {
- return true;
- } else {
- if (obj instanceof QName) {
- QName other = (QName)obj;
- if ((getNamespaceURI() == null && other.getNamespaceURI() == null)
- || getNamespaceURI().equals(other.getNamespaceURI())) {
- return getLocalName().equals(other.getLocalName());
- }
- }
- }
- return false;
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return prefix != null
- ? (prefix + ":" + localName)
- : toHashString();
- }
-
- private String toHashString() {
- return (namespaceURI != null
- ? ("{" + namespaceURI + "}" + localName)
- : localName);
- }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+/**
+ * Represents a qualified name of an XML element or an XML attribute.
+ * <p>
+ * Note: This class allows to carry a namespace prefix but it is not used in the equals() and
+ * hashCode() methods.
+ * @deprecated Use the XML Graphics Commons variant instead!
+ */
+public class QName extends org.apache.xmlgraphics.util.QName {
+
+ private static final long serialVersionUID = -5225376740044770690L;
+
+ /**
+ * Main constructor.
+ * @param namespaceURI the namespace URI
+ * @param prefix the namespace prefix, may be null
+ * @param localName the local name
+ */
+ public QName(String namespaceURI, String prefix, String localName) {
+ super(namespaceURI, prefix, localName);
+ }
+
+ /**
+ * Main constructor.
+ * @param namespaceURI the namespace URI
+ * @param qName the qualified name
+ */
+ public QName(String namespaceURI, String qName) {
+ super(namespaceURI, qName);
+ }
+
+}
diff --git a/src/java/org/apache/fop/util/UnitConv.java b/src/java/org/apache/fop/util/UnitConv.java
index 626321ff9..cd3276b9f 100644
--- a/src/java/org/apache/fop/util/UnitConv.java
+++ b/src/java/org/apache/fop/util/UnitConv.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-/* $Id: FixedLength.java 279656 2005-09-08 22:06:48Z pietsch $ */
+/* $Id$ */
package org.apache.fop.util;
diff --git a/src/java/org/apache/fop/util/XMLResourceBundle.java b/src/java/org/apache/fop/util/XMLResourceBundle.java
new file mode 100644
index 000000000..1b320816b
--- /dev/null
+++ b/src/java/org/apache/fop/util/XMLResourceBundle.java
@@ -0,0 +1,398 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Stack;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamSource;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.xmlgraphics.util.QName;
+
+/**
+ * This class is a ResourceBundle that loads its contents from XML files instead of properties
+ * files (like PropertiesResourceBundle).
+ * <p>
+ * The XML format for this resource bundle implementation is the following
+ * (the same as Apache Cocoon's XMLResourceBundle):
+ * <pre>
+ * &lt;catalogue xml:lang="en"&gt;
+ * &lt;message key="key1"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
+ * &lt;message key="key2"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
+ * ...
+ * &lt;/catalogue&gt;
+ * </pre>
+ */
+public class XMLResourceBundle extends ResourceBundle {
+
+ //Note: Some code here has been copied and adapted from Apache Harmony!
+
+ private Properties resources = new Properties();
+
+ private Locale locale;
+
+ private static SAXTransformerFactory tFactory
+ = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+ /**
+ * Creates a resource bundle from an InputStream.
+ * @param in the stream to read from
+ * @throws IOException if an I/O error occurs
+ */
+ public XMLResourceBundle(InputStream in) throws IOException {
+ try {
+ Transformer transformer = tFactory.newTransformer();
+ StreamSource src = new StreamSource(in);
+ SAXResult res = new SAXResult(new CatalogueHandler());
+ transformer.transform(src, res);
+ } catch (TransformerException e) {
+ throw new IOException("Error while parsing XML resource bundle: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Gets a resource bundle using the specified base name, default locale, and class loader.
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @param loader the class loader from which to load the resource bundle
+ * @return a resource bundle for the given base name and the default locale
+ * @throws MissingResourceException if no resource bundle for the specified base name can be
+ * found
+ * @see java.util.ResourceBundle#getBundle(String)
+ */
+ public static ResourceBundle getXMLBundle(String baseName, ClassLoader loader)
+ throws MissingResourceException {
+ return getXMLBundle(baseName, Locale.getDefault(), loader);
+ }
+
+ /**
+ * Gets a resource bundle using the specified base name, locale, and class loader.
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @param locale the locale for which a resource bundle is desired
+ * @param loader the class loader from which to load the resource bundle
+ * @return a resource bundle for the given base name and locale
+ * @throws MissingResourceException if no resource bundle for the specified base name can be
+ * found
+ * @see java.util.ResourceBundle#getBundle(String, Locale, ClassLoader)
+ */
+ public static ResourceBundle getXMLBundle(String baseName, Locale locale, ClassLoader loader)
+ throws MissingResourceException {
+ if (loader == null) {
+ throw new NullPointerException("loader must not be null");
+ }
+ if (baseName == null) {
+ throw new NullPointerException("baseName must not be null");
+ }
+
+ ResourceBundle bundle;
+ if (!locale.equals(Locale.getDefault())) {
+ bundle = handleGetXMLBundle(baseName, "_" + locale, false, loader);
+ if (bundle != null) {
+ return bundle;
+ }
+ }
+ bundle = handleGetXMLBundle(baseName, "_" + Locale.getDefault(), true, loader);
+ if (bundle != null) {
+ return bundle;
+ }
+ throw new MissingResourceException(
+ baseName + " (" + locale + ")", baseName + '_' + locale, null);
+ }
+
+ static class MissingBundle extends ResourceBundle {
+ public Enumeration getKeys() {
+ return null;
+ }
+
+ public Object handleGetObject(String name) {
+ return null;
+ }
+ }
+
+ private static final ResourceBundle MISSING = new MissingBundle();
+ private static final ResourceBundle MISSINGBASE = new MissingBundle();
+
+ private static Map cache = new java.util.WeakHashMap();
+ //<Object, Hashtable<String, ResourceBundle>>
+
+ private static ResourceBundle handleGetXMLBundle(String base, String locale,
+ boolean loadBase, final ClassLoader loader) {
+ XMLResourceBundle bundle = null;
+ String bundleName = base + locale;
+ Object cacheKey = loader != null ? (Object) loader : (Object) "null";
+ Hashtable loaderCache; //<String, ResourceBundle>
+ synchronized (cache) {
+ loaderCache = (Hashtable)cache.get(cacheKey);
+ if (loaderCache == null) {
+ loaderCache = new Hashtable();
+ cache.put(cacheKey, loaderCache);
+ }
+ }
+ ResourceBundle result = (ResourceBundle)loaderCache.get(bundleName);
+ if (result != null) {
+ if (result == MISSINGBASE) {
+ return null;
+ }
+ if (result == MISSING) {
+ if (!loadBase) {
+ return null;
+ }
+ String extension = strip(locale);
+ if (extension == null) {
+ return null;
+ }
+ return handleGetXMLBundle(base, extension, loadBase, loader);
+ }
+ return result;
+ }
+
+ final String fileName = bundleName.replace('.', '/') + ".xml";
+ InputStream stream = (InputStream)AccessController
+ .doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ return loader == null
+ ? ClassLoader.getSystemResourceAsStream(fileName)
+ : loader.getResourceAsStream(fileName);
+ }
+ });
+ if (stream != null) {
+ try {
+ try {
+ bundle = new XMLResourceBundle(stream);
+ } finally {
+ stream.close();
+ }
+ bundle.setLocale(locale);
+ } catch (IOException e) {
+ throw new MissingResourceException(e.getMessage(), base, null);
+ }
+ }
+
+ String extension = strip(locale);
+ if (bundle != null) {
+ if (extension != null) {
+ ResourceBundle parent = handleGetXMLBundle(base, extension, true,
+ loader);
+ if (parent != null) {
+ bundle.setParent(parent);
+ }
+ }
+ loaderCache.put(bundleName, bundle);
+ return bundle;
+ }
+
+ if (extension != null) {
+ ResourceBundle fallback = handleGetXMLBundle(base, extension, loadBase, loader);
+ if (fallback != null) {
+ loaderCache.put(bundleName, fallback);
+ return fallback;
+ }
+ }
+ loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING);
+ return null;
+ }
+
+ private void setLocale(String name) {
+ String language = "", country = "", variant = "";
+ if (name.length() > 1) {
+ int nextIndex = name.indexOf('_', 1);
+ if (nextIndex == -1) {
+ nextIndex = name.length();
+ }
+ language = name.substring(1, nextIndex);
+ if (nextIndex + 1 < name.length()) {
+ int index = nextIndex;
+ nextIndex = name.indexOf('_', nextIndex + 1);
+ if (nextIndex == -1) {
+ nextIndex = name.length();
+ }
+ country = name.substring(index + 1, nextIndex);
+ if (nextIndex + 1 < name.length()) {
+ variant = name.substring(nextIndex + 1, name.length());
+ }
+ }
+ }
+ this.locale = new Locale(language, country, variant);
+ }
+
+ private static String strip(String name) {
+ int index = name.lastIndexOf('_');
+ if (index != -1) {
+ return name.substring(0, index);
+ }
+ return null;
+ }
+
+ private Enumeration getLocalKeys() {
+ return (Enumeration)resources.propertyNames();
+ }
+
+ /** {@inheritDoc} */
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+ /** {@inheritDoc} */
+ public Enumeration getKeys() {
+ if (parent == null) {
+ return getLocalKeys();
+ }
+ return new Enumeration() {
+ private Enumeration local = getLocalKeys();
+ private Enumeration pEnum = parent.getKeys();
+
+ private Object nextElement;
+
+ private boolean findNext() {
+ if (nextElement != null) {
+ return true;
+ }
+ while (pEnum.hasMoreElements()) {
+ Object next = pEnum.nextElement();
+ if (!resources.containsKey(next)) {
+ nextElement = next;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasMoreElements() {
+ if (local.hasMoreElements()) {
+ return true;
+ }
+ return findNext();
+ }
+
+ public Object nextElement() {
+ if (local.hasMoreElements()) {
+ return local.nextElement();
+ }
+ if (findNext()) {
+ Object result = nextElement;
+ nextElement = null;
+ return result;
+ }
+ // Cause an exception
+ return pEnum.nextElement();
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ protected Object handleGetObject(String key) {
+ if (key == null) {
+ throw new NullPointerException("key must not be null");
+ }
+ return resources.get(key);
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "XMLResourceBundle: " + getLocale();
+ }
+
+ private class CatalogueHandler extends DefaultHandler {
+
+ private static final String CATALOGUE = "catalogue";
+ private static final String MESSAGE = "message";
+
+ private StringBuffer valueBuffer = new StringBuffer();
+ private Stack elementStack = new Stack();
+ private String currentKey = null;
+
+ private boolean isOwnNamespace(String uri) {
+ return ("".equals(uri));
+ }
+
+ private QName getParentElementName() {
+ return (QName)elementStack.peek();
+ }
+
+ /** {@inheritDoc} */
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ super.startElement(uri, localName, qName, atts);
+ QName elementName = new QName(uri, qName);
+ if (isOwnNamespace(uri)) {
+ if (CATALOGUE.equals(localName)) {
+ //nop
+ } else if (MESSAGE.equals(localName)) {
+ if (!CATALOGUE.equals(getParentElementName().getLocalName())) {
+ throw new SAXException(MESSAGE + " must be a child of " + CATALOGUE);
+ }
+ this.currentKey = atts.getValue("key");
+ } else {
+ throw new SAXException("Invalid element name: " + elementName);
+ }
+ } else {
+ //ignore
+ }
+ this.valueBuffer.setLength(0);
+ elementStack.push(elementName);
+ }
+
+ /** {@inheritDoc} */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ super.endElement(uri, localName, qName);
+ elementStack.pop();
+ if (isOwnNamespace(uri)) {
+ if (CATALOGUE.equals(localName)) {
+ //nop
+ } else if (MESSAGE.equals(localName)) {
+ if (this.currentKey == null) {
+ throw new SAXException(
+ "current key is null (attribute 'key' might be mistyped)");
+ }
+ resources.put(this.currentKey, this.valueBuffer.toString());
+ this.currentKey = null;
+ }
+ } else {
+ //ignore
+ }
+ this.valueBuffer.setLength(0);
+ }
+
+ /** {@inheritDoc} */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ super.characters(ch, start, length);
+ valueBuffer.append(ch, start, length);
+ }
+
+ }
+
+}
diff --git a/src/java/org/apache/fop/util/XMLizable.java b/src/java/org/apache/fop/util/XMLizable.java
index c1213638b..a16131989 100644
--- a/src/java/org/apache/fop/util/XMLizable.java
+++ b/src/java/org/apache/fop/util/XMLizable.java
@@ -25,20 +25,11 @@ package org.apache.fop.util;
* src/java/org/apache/excalibur/xml/sax/XMLizable.java
*/
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-
/**
* This interface can be implemented by classes willing to provide an XML representation
* of their current state as SAX events.
+ * @deprecated Use the interface in Apache XML Graphics Commons instead.
*/
-public interface XMLizable {
-
- /**
- * Generates SAX events representing the object's state.
- * @param handler ContentHandler instance to send the SAX events to
- * @throws SAXException if there's a problem generating the SAX events
- */
- void toSAX(ContentHandler handler) throws SAXException;
+public interface XMLizable extends org.apache.xmlgraphics.util.XMLizable {
}
diff --git a/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java b/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java
new file mode 100644
index 000000000..a2169156a
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java
@@ -0,0 +1,487 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.xmlgraphics.util.Service;
+
+
+/**
+ * Formats messages based on a template and with a set of named parameters. This is similar to
+ * {@link java.util.MessageFormat} but uses named parameters and supports conditional sub-groups.
+ * <p>
+ * Example:
+ * </p>
+ * <p><code>Missing field "{fieldName}"[ at location: {location}]!</code></p>
+ * <ul>
+ * <li>Curly brackets ("{}") are used for fields.</li>
+ * <li>Square brackets ("[]") are used to delimit conditional sub-groups. A sub-group is
+ * conditional when all fields inside the sub-group have a null value. In the case, everything
+ * between the brackets is skipped.</li>
+ * </ul>
+ */
+public class AdvancedMessageFormat {
+
+ /** Regex that matches "," but not "\," (escaped comma) */
+ static final Pattern COMMA_SEPARATOR_REGEX = Pattern.compile("(?<!\\\\),");
+
+ private static final Map PART_FACTORIES = new java.util.HashMap();
+ private static final List OBJECT_FORMATTERS = new java.util.ArrayList();
+ private static final Map FUNCTIONS = new java.util.HashMap();
+
+ private CompositePart rootPart;
+
+ static {
+ Iterator iter;
+ iter = Service.providers(PartFactory.class, true);
+ while (iter.hasNext()) {
+ PartFactory factory = (PartFactory)iter.next();
+ PART_FACTORIES.put(factory.getFormat(), factory);
+ }
+ iter = Service.providers(ObjectFormatter.class, true);
+ while (iter.hasNext()) {
+ OBJECT_FORMATTERS.add((ObjectFormatter)iter.next());
+ }
+ iter = Service.providers(Function.class, true);
+ while (iter.hasNext()) {
+ Function function = (Function)iter.next();
+ FUNCTIONS.put(function.getName(), function);
+ }
+ }
+
+ /**
+ * Construct a new message format.
+ * @param pattern the message format pattern.
+ */
+ public AdvancedMessageFormat(CharSequence pattern) {
+ parsePattern(pattern);
+ }
+
+ private void parsePattern(CharSequence pattern) {
+ rootPart = new CompositePart(false);
+ StringBuffer sb = new StringBuffer();
+ parseInnerPattern(pattern, rootPart, sb, 0);
+ }
+
+ private int parseInnerPattern(CharSequence pattern, CompositePart parent,
+ StringBuffer sb, int start) {
+ assert sb.length() == 0;
+ int i = start;
+ int len = pattern.length();
+ loop:
+ while (i < len) {
+ char ch = pattern.charAt(i);
+ switch (ch) {
+ case '{':
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ i++;
+ int nesting = 1;
+ while (i < len) {
+ ch = pattern.charAt(i);
+ if (ch == '{') {
+ nesting++;
+ } else if (ch == '}') {
+ nesting--;
+ if (nesting == 0) {
+ i++;
+ break;
+ }
+ }
+ sb.append(ch);
+ i++;
+ }
+ parent.addChild(parseField(sb.toString()));
+ sb.setLength(0);
+ break;
+ case ']':
+ i++;
+ break loop; //Current composite is finished
+ case '[':
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ i++;
+ CompositePart composite = new CompositePart(true);
+ parent.addChild(composite);
+ i += parseInnerPattern(pattern, composite, sb, i);
+ break;
+ case '|':
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ parent.newSection();
+ i++;
+ break;
+ case '\\':
+ if (i < len - 1) {
+ i++;
+ ch = pattern.charAt(i);
+ }
+ //no break here! Must be right before "default" section
+ default:
+ sb.append(ch);
+ i++;
+ }
+ }
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ return i - start;
+ }
+
+ private Part parseField(String field) {
+ String[] parts = COMMA_SEPARATOR_REGEX.split(field, 3);
+ String fieldName = parts[0];
+ if (parts.length == 1) {
+ if (fieldName.startsWith("#")) {
+ return new FunctionPart(fieldName.substring(1));
+ } else {
+ return new SimpleFieldPart(fieldName);
+ }
+ } else {
+ String format = parts[1];
+ PartFactory factory = (PartFactory)PART_FACTORIES.get(format);
+ if (factory == null) {
+ throw new IllegalArgumentException(
+ "No PartFactory available under the name: " + format);
+ }
+ if (parts.length == 2) {
+ return factory.newPart(fieldName, null);
+ } else {
+ return factory.newPart(fieldName, parts[2]);
+ }
+ }
+ }
+
+ private static Function getFunction(String functionName) {
+ return (Function)FUNCTIONS.get(functionName);
+ }
+
+ /**
+ * Formats a message with the given parameters.
+ * @param params a Map of named parameters (Contents: <String, Object>)
+ * @return the formatted message
+ */
+ public String format(Map params) {
+ StringBuffer sb = new StringBuffer();
+ format(params, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Formats a message with the given parameters.
+ * @param params a Map of named parameters (Contents: <String, Object>)
+ * @param target the target StringBuffer to write the formatted message to
+ */
+ public void format(Map params, StringBuffer target) {
+ rootPart.write(target, params);
+ }
+
+ /**
+ * Represents a message template part. This interface is implemented by various variants of
+ * the single curly braces pattern ({field}, {field,if,yes,no} etc.).
+ */
+ public interface Part {
+
+ /**
+ * Writes the formatted part to a string buffer.
+ * @param sb the target string buffer
+ * @param params the parameters to work with
+ */
+ void write(StringBuffer sb, Map params);
+
+ /**
+ * Indicates whether there is any content that is generated by this message part.
+ * @param params the parameters to work with
+ * @return true if the part has content
+ */
+ boolean isGenerated(Map params);
+ }
+
+ /**
+ * Implementations of this interface parse a field part and return message parts.
+ */
+ public interface PartFactory {
+
+ /**
+ * Creates a new part by parsing the values parameter to configure the part.
+ * @param fieldName the field name
+ * @param values the unparsed parameter values
+ * @return the new message part
+ */
+ Part newPart(String fieldName, String values);
+
+ /**
+ * Returns the name of the message part format.
+ * @return the name of the message part format
+ */
+ String getFormat();
+ }
+
+ /**
+ * Implementations of this interface format certain objects to strings.
+ */
+ public interface ObjectFormatter {
+
+ /**
+ * Formats an object to a string and writes the result to a string buffer.
+ * @param sb the target string buffer
+ * @param obj the object to be formatted
+ */
+ void format(StringBuffer sb, Object obj);
+
+ /**
+ * Indicates whether a given object is supported.
+ * @param obj the object
+ * @return true if the object is supported by the formatter
+ */
+ boolean supportsObject(Object obj);
+ }
+
+ /**
+ * Implementations of this interface do some computation based on the message parameters
+ * given to it. Note: at the moment, this has to be done in a local-independent way since
+ * there is no locale information.
+ */
+ public interface Function {
+
+ /**
+ * Executes the function.
+ * @param params the message parameters
+ * @return the function result
+ */
+ Object evaluate(Map params);
+
+ /**
+ * Returns the name of the function.
+ * @return the name of the function
+ */
+ Object getName();
+ }
+
+ private static class TextPart implements Part {
+
+ private String text;
+
+ public TextPart(String text) {
+ this.text = text;
+ }
+
+ public void write(StringBuffer sb, Map params) {
+ sb.append(text);
+ }
+
+ public boolean isGenerated(Map params) {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return this.text;
+ }
+ }
+
+ private static class SimpleFieldPart implements Part {
+
+ private String fieldName;
+
+ public SimpleFieldPart(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public void write(StringBuffer sb, Map params) {
+ if (!params.containsKey(fieldName)) {
+ throw new IllegalArgumentException(
+ "Message pattern contains unsupported field name: " + fieldName);
+ }
+ Object obj = params.get(fieldName);
+ formatObject(obj, sb);
+ }
+
+ public boolean isGenerated(Map params) {
+ Object obj = params.get(fieldName);
+ return obj != null;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + "}";
+ }
+ }
+
+ /**
+ * Formats an object to a string and writes the result to a string buffer. This method
+ * usually uses the object's <code>toString()</code> method unless there is an
+ * {@link ObjectFormatter} that supports the object. {@link ObjectFormatter}s are registered
+ * through the service provider mechanism defined by the JAR specification.
+ * @param obj the object to be formatted
+ * @param target the target string buffer
+ */
+ public static void formatObject(Object obj, StringBuffer target) {
+ if (obj instanceof String) {
+ target.append(obj);
+ } else {
+ boolean handled = false;
+ Iterator iter = OBJECT_FORMATTERS.iterator();
+ while (iter.hasNext()) {
+ ObjectFormatter formatter = (ObjectFormatter)iter.next();
+ if (formatter.supportsObject(obj)) {
+ formatter.format(target, obj);
+ handled = true;
+ break;
+ }
+ }
+ if (!handled) {
+ target.append(String.valueOf(obj));
+ }
+ }
+ }
+
+ private static class FunctionPart implements Part {
+
+ private Function function;
+
+ public FunctionPart(String functionName) {
+ this.function = getFunction(functionName);
+ if (this.function == null) {
+ throw new IllegalArgumentException("Unknown function: " + functionName);
+ }
+ }
+
+ public void write(StringBuffer sb, Map params) {
+ Object obj = this.function.evaluate(params);
+ formatObject(obj, sb);
+ }
+
+ public boolean isGenerated(Map params) {
+ Object obj = this.function.evaluate(params);
+ return obj != null;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{#" + this.function.getName() + "}";
+ }
+ }
+
+ private static class CompositePart implements Part {
+
+ protected List parts = new java.util.ArrayList();
+ private boolean conditional;
+ private boolean hasSections = false;
+
+ public CompositePart(boolean conditional) {
+ this.conditional = conditional;
+ }
+
+ private CompositePart(List parts) {
+ this.parts.addAll(parts);
+ this.conditional = true;
+ }
+
+ public void addChild(Part part) {
+ if (part == null) {
+ throw new NullPointerException("part must not be null");
+ }
+ if (hasSections) {
+ CompositePart composite = (CompositePart)this.parts.get(this.parts.size() - 1);
+ composite.addChild(part);
+ } else {
+ this.parts.add(part);
+ }
+ }
+
+ public void newSection() {
+ if (!hasSections) {
+ List p = this.parts;
+ //Dropping into a different mode...
+ this.parts = new java.util.ArrayList();
+ this.parts.add(new CompositePart(p));
+ hasSections = true;
+ }
+ this.parts.add(new CompositePart(true));
+ }
+
+ public void write(StringBuffer sb, Map params) {
+ if (hasSections) {
+ Iterator iter = this.parts.iterator();
+ while (iter.hasNext()) {
+ CompositePart part = (CompositePart)iter.next();
+ if (part.isGenerated(params)) {
+ part.write(sb, params);
+ break;
+ }
+ }
+ } else {
+ if (isGenerated(params)) {
+ Iterator iter = this.parts.iterator();
+ while (iter.hasNext()) {
+ Part part = (Part)iter.next();
+ part.write(sb, params);
+ }
+ }
+ }
+ }
+
+ public boolean isGenerated(Map params) {
+ if (hasSections) {
+ Iterator iter = this.parts.iterator();
+ while (iter.hasNext()) {
+ Part part = (Part)iter.next();
+ if (part.isGenerated(params)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ if (conditional) {
+ Iterator iter = this.parts.iterator();
+ while (iter.hasNext()) {
+ Part part = (Part)iter.next();
+ if (!part.isGenerated(params)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return this.parts.toString();
+ }
+ }
+
+
+ static String unescapeComma(String string) {
+ return string.replaceAll("\\\\,", ",");
+ }
+}
diff --git a/src/java/org/apache/fop/util/text/ChoiceFieldPart.java b/src/java/org/apache/fop/util/text/ChoiceFieldPart.java
new file mode 100644
index 000000000..df457a02b
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/ChoiceFieldPart.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.text.ChoiceFormat;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Defines a "choice" field part that works like {@link ChoiceFormat}.
+ */
+public class ChoiceFieldPart implements Part {
+
+ private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([^\\}]+)\\}");
+
+ private String fieldName;
+ private ChoiceFormat choiceFormat;
+
+ /**
+ * Creates a new choice part.
+ * @param fieldName the field name to work on
+ * @param choicesPattern the choices pattern (as used by {@link ChoiceFormat})
+ */
+ public ChoiceFieldPart(String fieldName, String choicesPattern) {
+ this.fieldName = fieldName;
+ this.choiceFormat = new ChoiceFormat(choicesPattern);
+ }
+
+ /** {@inheritDoc} */
+ public boolean isGenerated(Map params) {
+ Object obj = params.get(fieldName);
+ return obj != null;
+ }
+
+ /** {@inheritDoc} */
+ public void write(StringBuffer sb, Map params) {
+ Object obj = params.get(fieldName);
+ Number num = (Number)obj;
+ String result = this.choiceFormat.format(num.doubleValue());
+ Matcher m = VARIABLE_REGEX.matcher(result);
+ if (m.find()) {
+ //Resolve inner variables
+ AdvancedMessageFormat f = new AdvancedMessageFormat(result);
+ f.format(params, sb);
+ } else {
+ sb.append(result);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + ",choice, ....}";
+ }
+
+ /** Factory for ChoiceFieldPart. */
+ public static class Factory implements PartFactory {
+
+ /** {@inheritDoc} */
+ public Part newPart(String fieldName, String values) {
+ return new ChoiceFieldPart(fieldName, values);
+ }
+
+ /** {@inheritDoc} */
+ public String getFormat() {
+ return "choice";
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/java/org/apache/fop/util/text/EqualsFieldPart.java b/src/java/org/apache/fop/util/text/EqualsFieldPart.java
new file mode 100644
index 000000000..2114b0d00
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/EqualsFieldPart.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Defines an "equals" field part that can compare a field's string value against another string.
+ * It returns either of two possible values attached as additional part parameters. Example:
+ * <code>{field,equals,new,This is new!,This is old!}</code>
+ */
+public class EqualsFieldPart extends IfFieldPart {
+
+ private String equalsValue;
+
+ /**
+ * Creates a new "equals" field part.
+ * @param fieldName the field name
+ * @param values the unparsed parameter values
+ */
+ public EqualsFieldPart(String fieldName, String values) {
+ super(fieldName, values);
+ }
+
+ /** {@inheritDoc} */
+ protected void parseValues(String values) {
+ String[] parts = AdvancedMessageFormat.COMMA_SEPARATOR_REGEX.split(values, 3);
+ this.equalsValue = parts[0];
+ if (parts.length == 1) {
+ throw new IllegalArgumentException(
+ "'equals' format must have at least 2 parameters");
+ }
+ if (parts.length == 3) {
+ ifValue = AdvancedMessageFormat.unescapeComma(parts[1]);
+ elseValue = AdvancedMessageFormat.unescapeComma(parts[2]);
+ } else {
+ ifValue = AdvancedMessageFormat.unescapeComma(parts[1]);
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected boolean isTrue(Map params) {
+ Object obj = params.get(fieldName);
+ if (obj != null) {
+ return String.valueOf(obj).equals(this.equalsValue);
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + ", equals " + this.equalsValue + "}";
+ }
+
+ /**
+ * Part factory for "equals".
+ */
+ public static class Factory implements PartFactory {
+
+ /** {@inheritDoc} */
+ public Part newPart(String fieldName, String values) {
+ return new EqualsFieldPart(fieldName, values);
+ }
+
+ /** {@inheritDoc} */
+ public String getFormat() {
+ return "equals";
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java b/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java
new file mode 100644
index 000000000..5d78cdfad
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.xmlgraphics.fonts.Glyphs;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Function formatting a character to a glyph name.
+ */
+public class GlyphNameFieldPart implements Part {
+
+ private String fieldName;
+
+ /**
+ * Creates a new glyph name field part
+ * @param fieldName the field name
+ */
+ public GlyphNameFieldPart(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isGenerated(Map params) {
+ Object obj = params.get(fieldName);
+ return obj != null && getGlyphName(obj).length() > 0;
+ }
+
+ private String getGlyphName(Object obj) {
+ if (obj instanceof Character) {
+ return Glyphs.charToGlyphName(((Character)obj).charValue());
+ } else {
+ throw new IllegalArgumentException(
+ "Value for glyph name part must be a Character but was: "
+ + obj.getClass().getName());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void write(StringBuffer sb, Map params) {
+ if (!params.containsKey(fieldName)) {
+ throw new IllegalArgumentException(
+ "Message pattern contains unsupported field name: " + fieldName);
+ }
+ Object obj = params.get(fieldName);
+ sb.append(getGlyphName(obj));
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + ",glyph-name}";
+ }
+
+ /** Factory for {@link GlyphNameFieldPart}. */
+ public static class Factory implements PartFactory {
+
+ /** {@inheritDoc} */
+ public Part newPart(String fieldName, String values) {
+ return new GlyphNameFieldPart(fieldName);
+ }
+
+ /** {@inheritDoc} */
+ public String getFormat() {
+ return "glyph-name";
+ }
+
+ }
+}
diff --git a/src/java/org/apache/fop/util/text/HexFieldPart.java b/src/java/org/apache/fop/util/text/HexFieldPart.java
new file mode 100644
index 000000000..19f47f3d7
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/HexFieldPart.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Function formatting a number or character to a hex value.
+ */
+public class HexFieldPart implements Part {
+
+ private String fieldName;
+
+ /**
+ * Creates a new hex field part
+ * @param fieldName the field name
+ */
+ public HexFieldPart(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isGenerated(Map params) {
+ Object obj = params.get(fieldName);
+ return obj != null;
+ }
+
+ /** {@inheritDoc} */
+ public void write(StringBuffer sb, Map params) {
+ if (!params.containsKey(fieldName)) {
+ throw new IllegalArgumentException(
+ "Message pattern contains unsupported field name: " + fieldName);
+ }
+ Object obj = params.get(fieldName);
+ if (obj instanceof Character) {
+ sb.append(Integer.toHexString(((Character)obj).charValue()));
+ } else if (obj instanceof Number) {
+ sb.append(Integer.toHexString(((Number)obj).intValue()));
+ } else {
+ throw new IllegalArgumentException("Incompatible value for hex field part: "
+ + obj.getClass().getName());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + ",hex}";
+ }
+
+ /** Factory for {@link HexFieldPart}. */
+ public static class Factory implements PartFactory {
+
+ /** {@inheritDoc} */
+ public Part newPart(String fieldName, String values) {
+ return new HexFieldPart(fieldName);
+ }
+
+ /** {@inheritDoc} */
+ public String getFormat() {
+ return "hex";
+ }
+
+ }
+}
diff --git a/src/java/org/apache/fop/util/text/IfFieldPart.java b/src/java/org/apache/fop/util/text/IfFieldPart.java
new file mode 100644
index 000000000..31cd8f36c
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/IfFieldPart.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Defines an "if" field part that checks if field's value is true or false.
+ * It returns either of two possible values attached as additional part parameters. Example:
+ * <code>{field,if,Yes,No}</code>
+ */
+public class IfFieldPart implements Part {
+
+ /** the field name for the part */
+ protected String fieldName;
+ /** the value being returned if the field is true */
+ protected String ifValue;
+ /** the value being returned if the field is false */
+ protected String elseValue;
+
+ /**
+ * Creates a new "if" field part.
+ * @param fieldName the field name
+ * @param values the unparsed parameter values
+ */
+ public IfFieldPart(String fieldName, String values) {
+ this.fieldName = fieldName;
+ parseValues(values);
+ }
+
+ /**
+ * Parses the parameter values
+ * @param values the unparsed parameter values
+ */
+ protected void parseValues(String values) {
+ String[] parts = AdvancedMessageFormat.COMMA_SEPARATOR_REGEX.split(values, 2);
+ if (parts.length == 2) {
+ ifValue = AdvancedMessageFormat.unescapeComma(parts[0]);
+ elseValue = AdvancedMessageFormat.unescapeComma(parts[1]);
+ } else {
+ ifValue = AdvancedMessageFormat.unescapeComma(values);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void write(StringBuffer sb, Map params) {
+ boolean isTrue = isTrue(params);
+ if (isTrue) {
+ sb.append(ifValue);
+ } else if (elseValue != null) {
+ sb.append(elseValue);
+ }
+ }
+
+ /**
+ * Indicates whether the field's value is true. If the field is not a boolen, it is true
+ * if the field is not null.
+ * @param params the message parameters
+ * @return true the field's value as boolean
+ */
+ protected boolean isTrue(Map params) {
+ Object obj = params.get(fieldName);
+ if (obj instanceof Boolean) {
+ return ((Boolean)obj).booleanValue();
+ } else {
+ return (obj != null);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean isGenerated(Map params) {
+ return isTrue(params) || (elseValue != null);
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + ", if...}";
+ }
+
+ /**
+ * Part factory for "if".
+ */
+ public static class Factory implements PartFactory {
+
+ /** {@inheritDoc} */
+ public Part newPart(String fieldName, String values) {
+ return new IfFieldPart(fieldName, values);
+ }
+
+ /** {@inheritDoc} */
+ public String getFormat() {
+ return "if";
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/fop/util/text/LocatorFormatter.java b/src/java/org/apache/fop/util/text/LocatorFormatter.java
new file mode 100644
index 000000000..d9532c66d
--- /dev/null
+++ b/src/java/org/apache/fop/util/text/LocatorFormatter.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.ObjectFormatter;
+
+/**
+ * Object formatter for the SAX Locator object.
+ */
+public class LocatorFormatter implements ObjectFormatter {
+
+ /** {@inheritDoc} */
+ public void format(StringBuffer sb, Object obj) {
+ Locator loc = (Locator)obj;
+ sb.append(loc.getLineNumber()).append(":").append(loc.getColumnNumber());
+ }
+
+ /** {@inheritDoc} */
+ public boolean supportsObject(Object obj) {
+ return obj instanceof Locator;
+ }
+
+} \ No newline at end of file