vtm/vtm/src/org/oscim/theme/RenderThemeHandler.java
Hannes Janetzek 027c2e7618 theme: change some theme attribute names and fix renderTheme.xsd
- element '<use-* name="x">' changed to <* use="x">
- style 'name' attribute changed to 'id'
2013-11-23 21:10:18 +01:00

834 lines
26 KiB
Java

/*
* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2013 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.theme;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.Log;
import org.oscim.backend.XMLReaderAdapter;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Color;
import org.oscim.backend.canvas.Paint.Cap;
import org.oscim.backend.canvas.Paint.FontFamily;
import org.oscim.backend.canvas.Paint.FontStyle;
import org.oscim.renderer.atlas.TextureAtlas;
import org.oscim.renderer.atlas.TextureAtlas.Rect;
import org.oscim.renderer.elements.TextureItem;
import org.oscim.theme.renderinstruction.Area;
import org.oscim.theme.renderinstruction.AreaLevel;
import org.oscim.theme.renderinstruction.Circle;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.theme.renderinstruction.LineSymbol;
import org.oscim.theme.renderinstruction.RenderInstruction;
import org.oscim.theme.renderinstruction.Symbol;
import org.oscim.theme.renderinstruction.Text;
import org.oscim.theme.rule.Rule;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* SAX2 handler to parse XML render theme files.
*/
public class RenderThemeHandler extends DefaultHandler {
private final static String TAG = RenderThemeHandler.class.getName();
private static final int RENDER_THEME_VERSION = 1;
private static enum Element {
RENDER_THEME, RENDERING_INSTRUCTION, RULE, STYLE, ATLAS;
}
//private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme";
private static final String ELEMENT_NAME_MATCH = "m";
private static final String UNEXPECTED_ELEMENT = "unexpected element: ";
private static final String IMG_PATH = "styles/";
/**
* @param inputStream
* an input stream containing valid render theme XML data.
* @return a new RenderTheme which is created by parsing the XML data from
* the input stream.
* @throws SAXException
* if an error occurs while parsing the render theme XML.
* @throws ParserConfigurationException
* if an error occurs while creating the XML parser.
* @throws IOException
* if an I/O error occurs while reading from the input stream.
*/
public static IRenderTheme getRenderTheme(InputStream inputStream)
throws SAXException, IOException {
RenderThemeHandler renderThemeHandler = new RenderThemeHandler();
new XMLReaderAdapter().parse(renderThemeHandler, inputStream);
return renderThemeHandler.mRenderTheme;
}
/**
* Logs the given information about an unknown XML attribute.
*
* @param element
* the XML element name.
* @param name
* the XML attribute name.
* @param value
* the XML attribute value.
* @param attributeIndex
* the XML attribute index position.
*/
public static void logUnknownAttribute(String element, String name,
String value, int attributeIndex) {
StringBuilder sb = new StringBuilder();
sb.append("unknown attribute in element ");
sb.append(element);
sb.append(" (");
sb.append(attributeIndex);
sb.append("): ");
sb.append(name);
sb.append('=');
sb.append(value);
Log.d(TAG, sb.toString());
}
private final ArrayList<Rule> mRulesList = new ArrayList<Rule>();
private Rule mCurrentRule;
private final Stack<Element> mElementStack = new Stack<Element>();
private final Stack<Rule> mRuleStack = new Stack<Rule>();
private final HashMap<String, RenderInstruction> tmpStyleHash =
new HashMap<String, RenderInstruction>(10);
private TextureAtlas mTextureAtlas;
private int mLevel;
private RenderTheme mRenderTheme;
@Override
public void endDocument() {
if (mRenderTheme == null) {
throw new IllegalArgumentException("missing element: rules");
}
mRenderTheme.complete(mRulesList, mLevel);
mTextureAtlas = null;
mRulesList.clear();
tmpStyleHash.clear();
mRuleStack.clear();
mElementStack.clear();
}
@Override
public void endElement(String uri, String localName, String qName) {
mElementStack.pop();
if (ELEMENT_NAME_MATCH.equals(localName)) {
mRuleStack.pop();
if (mRuleStack.empty()) {
mRulesList.add(mCurrentRule);
} else {
mCurrentRule = mRuleStack.peek();
}
}
}
@Override
public void error(SAXParseException exception) {
Log.d(TAG, exception.getMessage());
}
@Override
public void warning(SAXParseException exception) {
Log.d(TAG, exception.getMessage());
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
try {
if ("rendertheme".equals(localName)) {
checkState(localName, Element.RENDER_THEME);
mRenderTheme = createRenderTheme(localName, attributes);
} else if (ELEMENT_NAME_MATCH.equals(localName)) {
checkState(localName, Element.RULE);
Rule rule = Rule.create(localName, attributes, mRuleStack);
if (!mRuleStack.empty()) {
mCurrentRule.addSubRule(rule);
}
mCurrentRule = rule;
mRuleStack.push(mCurrentRule);
} else if ("style-text".equals(localName)) {
checkState(localName, Element.STYLE);
Text text = createText(localName, attributes, false);
tmpStyleHash.put("t" + text.style, text);
} else if ("style-area".equals(localName)) {
checkState(localName, Element.STYLE);
Area area = createArea(localName, attributes, 0);
tmpStyleHash.put("a" + area.style, area);
} else if ("style-line".equals(localName)) {
checkState(localName, Element.STYLE);
String style = null;
if ((style = attributes.getValue("use")) != null) {
RenderInstruction ri = tmpStyleHash.get("l" + style);
if (ri instanceof Line) {
Line line = createLine((Line) ri, localName, attributes, 0,
false);
tmpStyleHash.put("l" + line.style, line);
} else
Log.d(TAG, "not a style: " + style);
} else {
Line line = createLine(null, localName, attributes, 0, false);
tmpStyleHash.put("l" + line.style, line);
}
} else if ("style-outline".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Line line = createLine(null, localName, attributes, mLevel++, true);
tmpStyleHash.put("o" + line.style, line);
// outlineLayers.add(line);
} else if ("area".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("use");
if (style == null) {
Area area = createArea(localName, attributes, mLevel++);
// mRuleStack.peek().addRenderingInstruction(area);
mCurrentRule.addRenderingInstruction(area);
} else {
Area area = (Area) tmpStyleHash.get("a" + style);
if (area != null)
mCurrentRule.addRenderingInstruction(new AreaLevel(area,
mLevel++));
else
Log.d(TAG, "BUG not an area style: " + style);
}
} else if ("caption".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Text text = createText(localName, attributes, true);
mCurrentRule.addRenderingInstruction(text);
if (text.symbol != null) {
if ((text.texture = mTextureAtlas.getTextureRegion(text.symbol)) == null)
Log.d(TAG, "missing texture atlas item '" + text.symbol + "'");
else
Log.d(TAG, "using atlas item '" + text.symbol + "'");
}
} else if ("circle".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Circle circle = createCircle(localName, attributes, mLevel++);
mCurrentRule.addRenderingInstruction(circle);
} else if ("line".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("use");
if (style == null) {
Line line = createLine(null, localName, attributes, mLevel++, false);
mCurrentRule.addRenderingInstruction(line);
} else {
Line line = (Line) tmpStyleHash.get("l" + style);
if (line != null) {
Line newLine = createLine(line, localName, attributes,
mLevel++, false);
mCurrentRule.addRenderingInstruction(newLine);
} else
Log.d(TAG, "BUG: not a line style: " + style);
}
} else if ("lineSymbol".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
LineSymbol lineSymbol = createLineSymbol(localName, attributes);
mCurrentRule.addRenderingInstruction(lineSymbol);
} else if ("text".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("use");
if (style == null) {
Text text = createText(localName, attributes, false);
mCurrentRule.addRenderingInstruction(text);
} else {
Text pt = (Text) tmpStyleHash.get("t" + style);
if (pt != null)
mCurrentRule.addRenderingInstruction(pt);
else
Log.d(TAG, "BUG not a path text style: " + style);
}
} else if ("symbol".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
Symbol symbol = createSymbol(localName, attributes);
mCurrentRule.addRenderingInstruction(symbol);
if (mTextureAtlas != null) {
if ((symbol.texture = mTextureAtlas.getTextureRegion(symbol.src)) == null)
Log.d(TAG, "missing texture atlas item '" + symbol.src + "'");
//else
// Log.d(TAG, "using atlas item '" + symbol.src + "'");
}
} else if ("outline".equals(localName)) {
checkState(localName, Element.RENDERING_INSTRUCTION);
String style = attributes.getValue("use");
if (style != null) {
Line line = (Line) tmpStyleHash.get("o" + style);
if (line != null && line.outline)
mCurrentRule.addRenderingInstruction(line);
else
Log.d(TAG, "BUG not an outline style: " + style);
}
} else if ("atlas".equals(localName)) {
checkState(localName, Element.ATLAS);
createAtlas(localName, attributes);
} else if ("rect".equals(localName)) {
checkState(localName, Element.ATLAS);
createTextureRegion(localName, attributes);
} else {
Log.d(TAG, "unknown element: " + localName);
//throw new SAXException("unknown element: " + localName);
}
} catch (IllegalArgumentException e) {
throw new SAXException(null, e);
} catch (IOException e) {
throw new SAXException(null, e);
}
}
private void createAtlas(String elementName, Attributes attributes) throws IOException {
String img = null;
for (int i = 0; i < attributes.getLength(); i++) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("img".equals(name)) {
img = value;
} else {
RenderThemeHandler.logUnknownAttribute(elementName, name, value, i);
}
}
if (img == null)
throw new IllegalArgumentException(
"missing attribute 'img' for element: "
+ elementName);
Bitmap bitmap = CanvasAdapter.g.loadBitmapAsset(IMG_PATH + img);
mTextureAtlas = new TextureAtlas(bitmap);
}
private void createTextureRegion(String elementName, Attributes attributes) {
String regionName = null;
Rect r = null;
for (int i = 0, n = attributes.getLength(); i < n; i++) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("id".equals(name)) {
regionName = value;
} else if ("pos".equals(name)) {
String[] pos = value.split(" ");
if (pos.length == 4) {
r = new Rect(Integer.parseInt(pos[0]),
Integer.parseInt(pos[1]),
Integer.parseInt(pos[2]),
Integer.parseInt(pos[3]));
}
} else {
RenderThemeHandler.logUnknownAttribute(elementName, name, value, i);
}
}
if (regionName == null || r == null)
throw new IllegalArgumentException(
"missing attribute 'name' or 'rect' for element: "
+ elementName);
mTextureAtlas.addTextureRegion(regionName.intern(), r);
}
private void checkElement(String elementName, Element element) throws SAXException {
Element parentElement;
switch (element) {
case RENDER_THEME:
if (!mElementStack.empty()) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case RULE:
parentElement = mElementStack.peek();
if (parentElement != Element.RENDER_THEME
&& parentElement != Element.RULE) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case STYLE:
parentElement = mElementStack.peek();
if (parentElement != Element.RENDER_THEME) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case RENDERING_INSTRUCTION:
if (mElementStack.peek() != Element.RULE) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
case ATLAS:
parentElement = mElementStack.peek();
// FIXME
if (parentElement != Element.RENDER_THEME
&& parentElement != Element.ATLAS) {
throw new SAXException(UNEXPECTED_ELEMENT + elementName);
}
return;
}
throw new SAXException("unknown enum value: " + element);
}
private void checkState(String elementName, Element element) throws SAXException {
checkElement(elementName, element);
mElementStack.push(element);
}
static RenderTheme createRenderTheme(String elementName, Attributes attributes) {
Integer version = null;
int mapBackground = Color.WHITE;
float baseStrokeWidth = 1;
float baseTextSize = 1;
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("schemaLocation".equals(name)) {
continue;
} else if ("version".equals(name)) {
version = Integer.valueOf(Integer.parseInt(value));
} else if ("map-background".equals(name)) {
mapBackground = Color.parseColor(value);
} else if ("base-stroke-width".equals(name)) {
baseStrokeWidth = Float.parseFloat(value);
} else if ("base-text-size".equals(name)) {
baseTextSize = Float.parseFloat(value);
} else {
RenderThemeHandler.logUnknownAttribute(elementName, name, value, i);
}
}
if (version == null) {
throw new IllegalArgumentException("missing attribute version for element:"
+ elementName);
} else if (version.intValue() != RENDER_THEME_VERSION) {
throw new IllegalArgumentException("invalid render theme version:" + version);
} else if (baseStrokeWidth < 0) {
throw new IllegalArgumentException("base-stroke-width must not be negative: "
+ baseStrokeWidth);
} else if (baseTextSize < 0) {
throw new IllegalArgumentException("base-text-size must not be negative: "
+ baseTextSize);
}
return new RenderTheme(mapBackground, baseStrokeWidth, baseTextSize);
}
/**
* @param elementName
* the name of the XML element.
* @param attributes
* the attributes of the XML element.
* @param caption
* ...
* @return a new Text with the given rendering attributes.
*/
private static Text createText(String elementName, Attributes attributes, boolean caption) {
String textKey = null;
FontFamily fontFamily = FontFamily.DEFAULT;
FontStyle fontStyle = FontStyle.NORMAL;
float fontSize = 0;
int fill = Color.BLACK;
int stroke = Color.BLACK;
float strokeWidth = 0;
String style = null;
float dy = 0;
int priority = Integer.MAX_VALUE;
String symbol = null;
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("id".equals(name))
style = value;
else if ("k".equals(name)) {
textKey = value.intern();
} else if ("font-family".equals(name)) {
fontFamily = FontFamily.valueOf(value.toUpperCase());
} else if ("font-style".equals(name)) {
fontStyle = FontStyle.valueOf(value.toUpperCase());
} else if ("font-size".equals(name)) {
fontSize = Float.parseFloat(value);
} else if ("fill".equals(name)) {
fill = Color.parseColor(value);
} else if ("stroke".equals(name)) {
stroke = Color.parseColor(value);
} else if ("stroke-width".equals(name)) {
strokeWidth = Float.parseFloat(value);
} else if ("caption".equals(name)) {
caption = Boolean.parseBoolean(value);
} else if ("priority".equals(name)) {
priority = Integer.parseInt(value);
} else if ("dy".equals(name)) {
dy = Float.parseFloat(value);
} else if ("symbol".equals(name)) {
symbol = value;
} else {
logUnknownAttribute(elementName, name, value, i);
}
}
validateText(elementName, textKey, fontSize, strokeWidth);
return new Text(style, textKey, fontFamily, fontStyle, fontSize, fill, stroke, strokeWidth,
dy, caption, symbol, priority);
}
private static void validateText(String elementName, String textKey, float fontSize,
float strokeWidth) {
if (textKey == null) {
throw new IllegalArgumentException("missing attribute k for element: "
+ elementName);
} else if (fontSize < 0) {
throw new IllegalArgumentException("font-size must not be negative: "
+ fontSize);
} else if (strokeWidth < 0) {
throw new IllegalArgumentException("stroke-width must not be negative: "
+ strokeWidth);
}
}
/**
* @param line
* optional: line style defaults
* @param elementName
* the name of the XML element.
* @param attributes
* the attributes of the XML element.
* @param level
* the drawing level of this instruction.
* @param isOutline
* is outline layer
* @return a new Line with the given rendering attributes.
*/
private static Line createLine(Line line, String elementName, Attributes attributes,
int level, boolean isOutline) {
// Style name
String style = null;
float width = 0;
Cap cap = Cap.ROUND;
// Extras
int fade = -1;
boolean fixed = false;
float blur = 0;
float min = 0;
// Stipple
int stipple = 0;
float stippleWidth = 0;
int color = Color.RED;
int stippleColor = Color.BLACK;
if (line != null) {
color = line.color;
fixed = line.fixed;
fade = line.fade;
cap = line.cap;
blur = line.blur;
min = line.min;
stipple = line.stipple;
stippleColor = line.stippleColor;
stippleWidth = line.stippleWidth;
}
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("id".equals(name))
style = value;
else if ("src".equals(name)) {
// src = value;
} else if ("stroke".equals(name)) {
color = Color.parseColor(value);
} else if ("width".equals(name) || "stroke-width".equals(name)) {
width = Float.parseFloat(value);
} else if ("cap".equals(name) || "stroke-linecap".equals(name)) {
cap = Cap.valueOf(value.toUpperCase());
} else if ("fix".equals(name)) {
fixed = Boolean.parseBoolean(value);
} else if ("stipple".equals(name)) {
stipple = Integer.parseInt(value);
} else if ("stipple-stroke".equals(name)) {
stippleColor = Color.parseColor(value);
} else if ("stipple-width".equals(name)) {
stippleWidth = Float.parseFloat(value);
} else if ("fade".equals(name)) {
fade = Integer.parseInt(value);
} else if ("min".equals(name)) {
min = Float.parseFloat(value);
} else if ("blur".equals(name)) {
blur = Float.parseFloat(value);
} else if ("style".equals(name)) {
// ignore
} else if ("dasharray".equals(name)) {
// ignore
} else {
logUnknownAttribute(elementName, name, value, i);
}
}
// inherit properties from 'line'
if (line != null) {
// use stroke width relative to 'line'
width = line.width + width;
if (width <= 0)
width = 1;
} else if (!isOutline) {
validateLine(width);
}
return new Line(level, style, color, width, cap, fixed,
stipple, stippleColor, stippleWidth,
fade, blur, isOutline, min);
}
private static void validateLine(float strokeWidth) {
if (strokeWidth < 0) {
throw new IllegalArgumentException("width must not be negative: "
+ strokeWidth);
}
}
/**
* @param elementName
* the name of the XML element.
* @param attributes
* the attributes of the XML element.
* @param level
* the drawing level of this instruction.
* @return a new Area with the given rendering attributes.
*/
private static Area createArea(String elementName, Attributes attributes, int level) {
String src = null;
int fill = Color.BLACK;
int stroke = Color.TRANSPARENT;
float strokeWidth = 0;
int fade = -1;
int blend = -1;
int blendFill = Color.BLACK;
String style = null;
TextureItem texture = null;
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("id".equals(name))
style = value;
else if ("src".equals(name)) {
src = value;
} else if ("fill".equals(name)) {
fill = Color.parseColor(value);
} else if ("stroke".equals(name)) {
stroke = Color.parseColor(value);
} else if ("stroke-width".equals(name)) {
strokeWidth = Float.parseFloat(value);
} else if ("fade".equals(name)) {
fade = Integer.parseInt(value);
} else if ("blend".equals(name)) {
blend = Integer.parseInt(value);
} else if ("blend-fill".equals(name)) {
blendFill = Color.parseColor(value);
} else {
logUnknownAttribute(elementName, name, value, i);
}
}
validateArea(strokeWidth);
if (src != null) {
try {
Bitmap b = CanvasAdapter.g.loadBitmapAsset(src);
if (b != null)
texture = new TextureItem(b, true);
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
}
return new Area(style, fill, stroke, strokeWidth, fade, level, blend,
blendFill, texture);
}
private static void validateArea(float strokeWidth) {
if (strokeWidth < 0) {
throw new IllegalArgumentException("stroke-width must not be negative: "
+ strokeWidth);
}
}
/**
* @param elementName
* the name of the XML element.
* @param attributes
* the attributes of the XML element.
* @param level
* the drawing level of this instruction.
* @return a new Circle with the given rendering attributes.
*/
private static Circle createCircle(String elementName, Attributes attributes, int level) {
Float radius = null;
boolean scaleRadius = false;
int fill = Color.TRANSPARENT;
int stroke = Color.TRANSPARENT;
float strokeWidth = 0;
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("r".equals(name) || "radius".equals(name)) {
radius = Float.valueOf(Float.parseFloat(value));
} else if ("scale-radius".equals(name)) {
scaleRadius = Boolean.parseBoolean(value);
} else if ("fill".equals(name)) {
fill = Color.parseColor(value);
} else if ("stroke".equals(name)) {
stroke = Color.parseColor(value);
} else if ("stroke-width".equals(name)) {
strokeWidth = Float.parseFloat(value);
} else {
logUnknownAttribute(elementName, name, value, i);
}
}
validateCircle(elementName, radius, strokeWidth);
return new Circle(radius, scaleRadius, fill, stroke, strokeWidth, level);
}
private static void validateCircle(String elementName, Float radius, float strokeWidth) {
if (radius == null) {
throw new IllegalArgumentException("missing attribute r for element: "
+ elementName);
} else if (radius.floatValue() < 0) {
throw new IllegalArgumentException("radius must not be negative: " + radius);
} else if (strokeWidth < 0) {
throw new IllegalArgumentException("stroke-width must not be negative: "
+ strokeWidth);
}
}
/**
* @param elementName
* the name of the XML element.
* @param attributes
* the attributes of the XML element.
* @return a new LineSymbol with the given rendering attributes.
*/
private static LineSymbol createLineSymbol(String elementName, Attributes attributes) {
String src = null;
boolean alignCenter = false;
boolean repeat = false;
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("src".equals(name)) {
src = value;
} else if ("align-center".equals(name)) {
alignCenter = Boolean.parseBoolean(value);
} else if ("repeat".equals(name)) {
repeat = Boolean.parseBoolean(value);
} else {
logUnknownAttribute(elementName, name, value, i);
}
}
validateLineSymbol(elementName, src);
return new LineSymbol(src, alignCenter, repeat);
}
private static void validateLineSymbol(String elementName, String src) {
if (src == null) {
throw new IllegalArgumentException("missing attribute src for element: "
+ elementName);
}
}
/**
* @param elementName
* the name of the XML element.
* @param attributes
* the attributes of the XML element.
* @return a new Symbol with the given rendering attributes.
*/
private static Symbol createSymbol(String elementName, Attributes attributes) {
String src = null;
for (int i = 0; i < attributes.getLength(); ++i) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
if ("src".equals(name)) {
src = value;
} else {
logUnknownAttribute(elementName, name, value, i);
}
}
validateSymbol(elementName, src);
return new Symbol(src);
}
private static void validateSymbol(String elementName, String src) {
if (src == null) {
throw new IllegalArgumentException("missing attribute src for element: "
+ elementName);
}
}
}