/*
* org.openmicroscopy.shoola.util.roi.io.OutputStrategy
*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2007 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.roi.io;
// Java imports
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
// Third-party libraries
import static org.jhotdraw.samples.svg.SVGAttributeKeys.FILL_GRADIENT;
import static org.jhotdraw.samples.svg.SVGAttributeKeys.STROKE_GRADIENT;
import static org.jhotdraw.samples.svg.SVGAttributeKeys.TRANSFORM;
import org.jhotdraw.draw.AttributeKey;
import org.jhotdraw.draw.TextFigure;
import org.jhotdraw.draw.TextHolderFigure;
import org.jhotdraw.draw.AttributeKeys.WindingRule;
import org.jhotdraw.geom.BezierPath;
import org.jhotdraw.samples.svg.LinearGradient;
import org.jhotdraw.samples.svg.RadialGradient;
import net.n3.nanoxml.IXMLElement;
import net.n3.nanoxml.XMLElement;
import net.n3.nanoxml.XMLWriter;
// Application-internal dependencies
import org.openmicroscopy.shoola.util.roi.exception.ParsingException;
import org.openmicroscopy.shoola.util.roi.figures.MeasureBezierFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureEllipseFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureLineConnectionFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureLineFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasurePointFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureRectangleFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureTextFigure;
import org.openmicroscopy.shoola.util.roi.figures.ROIFigure;
import org.openmicroscopy.shoola.util.roi.ROIComponent;
import org.openmicroscopy.shoola.util.roi.model.ROI;
import org.openmicroscopy.shoola.util.roi.model.ROIShape;
import org.openmicroscopy.shoola.util.roi.model.annotation.AnnotationKey;
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
import org.openmicroscopy.shoola.util.roi.model.util.Coord3D;
/**
*
*
* @author Jean-Marie Burel <a
* href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald <a
* href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0 <small> (<b>Internal version:</b> $Revision: $Date: $)
* </small>
* @since OME3.0
*/
public class OutputStrategy
{
private final static HashMap<Integer, String> strokeLinejoinMap;
static
{
strokeLinejoinMap=new HashMap<Integer, String>();
strokeLinejoinMap.put(BasicStroke.JOIN_MITER, "miter");
strokeLinejoinMap.put(BasicStroke.JOIN_ROUND, "round");
strokeLinejoinMap.put(BasicStroke.JOIN_BEVEL, "bevel");
}
private final static HashMap<Integer, String> strokeLinecapMap;
static
{
strokeLinecapMap=new HashMap<Integer, String>();
strokeLinecapMap.put(BasicStroke.CAP_BUTT, "butt");
strokeLinecapMap.put(BasicStroke.CAP_ROUND, "round");
strokeLinecapMap.put(BasicStroke.CAP_SQUARE, "square");
}
private IXMLElement document;
private IXMLElement defs;
/**
* This is a counter used to create the next unique identification.
*/
private int nextId;
/**
* In this hash map we store all elements to which we have assigned an id.
*/
private Map<IXMLElement, String> identifiedElements;
/** Creates a new instance. */
OutputStrategy()
{
nextId = 0;
identifiedElements = new HashMap<IXMLElement, String>();
}
/**
* Gets a unique ID for the specified element.
*/
private String getId(IXMLElement element)
{
if (identifiedElements.containsKey(element))
{
return identifiedElements.get(element);
}
else
{
String id=Integer.toString(nextId++, Character.MAX_RADIX);
identifiedElements.put(element, id);
return id;
}
}
public void write(OutputStream out, ROIComponent roiComponent)
throws ParsingException
{
document=
new XMLElement(IOConstants.ROISET_TAG,
IOConstants.ROI_NAMESPACE);
document.setAttribute(IOConstants.VERSION_TAG, IOConstants.ROI_VERSION);
defs=new XMLElement(IOConstants.DEFS_TAG);
document.addChild(defs);
ROIComponent collection=roiComponent;
TreeMap<Long, ROI> roiMap=collection.getROIMap();
Iterator iterator=roiMap.values().iterator();
try
{
while (iterator.hasNext())
{
write(document, (ROI) iterator.next());
}
new XMLWriter(out).write(document,true);
}
catch (Exception e)
{
throw new ParsingException("Cannot create XML output", e);
}
}
private void write(IXMLElement document, ROI roi) throws ParsingException
{
XMLElement roiElement=new XMLElement(IOConstants.ROI_TAG);
document.addChild(roiElement);
writeROIAnnotations(roiElement, roi);
TreeMap<Coord3D, ROIShape> roiShapes=roi.getShapes();
Iterator iterator=roiShapes.values().iterator();
while (iterator.hasNext())
writeROIShape(roiElement, (ROIShape) iterator.next());
}
/**
* Writes the annotations.
*
* @param roiElement The element to handle.
* @param roi The roi.
*/
private void writeROIAnnotations(IXMLElement roiElement, ROI roi)
{
roiElement.setAttribute(IOConstants.ROI_ID_ATTRIBUTE, roi.getID()+"");
Map<AnnotationKey, Object> annotationMap=roi.getAnnotation();
Iterator i = annotationMap.entrySet().iterator();
Entry entry;
AnnotationKey key;
XMLElement annotation;
while (i.hasNext())
{
entry = (Entry) i.next();
key = (AnnotationKey) entry.getKey();
annotation = new XMLElement(key.getKey());
addAttributes(annotation, entry.getValue());
}
}
/**
* Writes the shapes.
*
* @param shapeElement The XML element to handle.
* @param shape The shape.
*/
private void writeROIShapeAnnotations(IXMLElement shapeElement,
ROIShape shape)
{
Map<AnnotationKey, Object> annotationMap=shape.getAnnotation();
IXMLElement annotationLeaf=new XMLElement(IOConstants.ANNOTATION_TAG);
Iterator i = annotationMap.entrySet().iterator();
Entry entry;
AnnotationKey key;
XMLElement annotation;
while (i.hasNext())
{
entry = (Entry) i.next();
key = (AnnotationKey) entry.getKey();
annotation = new XMLElement(key.getKey());
addAttributes(annotation, entry.getValue());
annotationLeaf.addChild(annotation);
}
shapeElement.addChild(annotationLeaf);
}
/**
* Adds the attributes to the passed object.
*
* @param annotation The annotation to handle.
* @param value The value.
*/
private void addAttributes(XMLElement annotation, Object value)
{
if (value instanceof Double||value instanceof Float
||value instanceof Integer||value instanceof Long
||value instanceof Boolean)
{
if (value instanceof Double) annotation.setAttribute(
IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_DOUBLE);
if (value instanceof Float) annotation.setAttribute(
IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_FLOAT);
if (value instanceof Integer) annotation.setAttribute(
IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_INTEGER);
if (value instanceof Long) annotation.setAttribute(
IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_LONG);
if (value instanceof Boolean) annotation.setAttribute(
IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_BOOLEAN);
annotation.setAttribute(IOConstants.VALUE_ATTRIBUTE, value+"");
}
else if (value instanceof Color)
{
Color colour=(Color) value;
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_COLOUR);
annotation.setAttribute(IOConstants.RED_ATTRIBUTE, colour.getRed()
+"");
annotation.setAttribute(IOConstants.GREEN_ATTRIBUTE, colour
.getGreen()
+"");
annotation.setAttribute(IOConstants.BLUE_ATTRIBUTE, colour
.getBlue()
+"");
annotation.setAttribute(IOConstants.ALPHA_ATTRIBUTE, colour
.getAlpha()
+"");
}
else if (value instanceof Rectangle2D)
{
Rectangle2D object=(Rectangle2D) value;
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_RECTANGLE2D);
annotation.setAttribute(IOConstants.X_ATTRIBUTE, object.getX()+"");
annotation.setAttribute(IOConstants.Y_ATTRIBUTE, object.getY()+"");
annotation.setAttribute(IOConstants.WIDTH_ATTRIBUTE, object
.getWidth()+"");
annotation.setAttribute(IOConstants.HEIGHT_ATTRIBUTE, object
.getHeight()+"");
}
else if (value instanceof Ellipse2D)
{
Ellipse2D object=(Ellipse2D) value;
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_ELLIPSE2D);
annotation.setAttribute(IOConstants.X_ATTRIBUTE, object.getX()+"");
annotation.setAttribute(IOConstants.Y_ATTRIBUTE, object.getY()+"");
annotation.setAttribute(IOConstants.WIDTH_ATTRIBUTE, object
.getWidth()+"");
annotation.setAttribute(IOConstants.HEIGHT_ATTRIBUTE, object
.getHeight()+"");
}
else if (value instanceof String)
{
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_STRING);
annotation
.setAttribute(IOConstants.VALUE_ATTRIBUTE, (String) value);
}
else if (value instanceof Point2D)
{
Point2D point=(Point2D) value;
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_POINT2D);
annotation.setAttribute(IOConstants.X_ATTRIBUTE, point.getX()+"");
annotation.setAttribute(IOConstants.Y_ATTRIBUTE, point.getY()+"");
}
else if (value instanceof Coord3D)
{
Coord3D coord=(Coord3D) value;
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_COORD3D);
annotation.setAttribute(IOConstants.T_ATTRIBUTE, coord
.getTimePoint() +"");
annotation.setAttribute(IOConstants.Z_ATTRIBUTE, coord
.getZSection()+"");
}
else if (value instanceof ArrayList)
{
ArrayList list=(ArrayList) value;
annotation.setAttribute(IOConstants.DATATYPE_ATTRIBUTE,
IOConstants.ATTRIBUTE_DATATYPE_ARRAYLIST);
annotation.setAttribute(IOConstants.SIZE_ATTRIBUTE, list.size()+"");
for (int i=0; i<list.size(); i++)
{
XMLElement valueElement=new XMLElement(IOConstants.VALUE_TAG);
Object object=list.get(i);
addAttributes(valueElement, object);
annotation.addChild(valueElement);
}
}
}
private void writeROIShape(XMLElement roiElement, ROIShape shape)
throws ParsingException
{
XMLElement shapeElement=new XMLElement(IOConstants.ROISHAPE_TAG);
roiElement.addChild(shapeElement);
shapeElement.setAttribute(IOConstants.T_ATTRIBUTE, shape.getCoord3D()
.getTimePoint()
+"");
shapeElement.setAttribute(IOConstants.Z_ATTRIBUTE, shape.getCoord3D()
.getZSection()
+"");
writeROIShapeAnnotations(shapeElement, shape);
ROIFigure figure=shape.getFigure();
figure.calculateMeasurements();
writeFigure(shapeElement, figure);
}
private void writeFigure(XMLElement shapeElement, ROIFigure figure)
throws ParsingException
{
if (figure instanceof MeasureRectangleFigure)
{
writeSVGHeader(shapeElement);
writeMeasureRectangleFigure(shapeElement,
(MeasureRectangleFigure) figure);
writeTextFigure(shapeElement, (MeasureRectangleFigure) figure);
}
else if (figure instanceof MeasureEllipseFigure)
{
writeSVGHeader(shapeElement);
writeMeasureEllipseFigure(shapeElement,
(MeasureEllipseFigure) figure);
writeTextFigure(shapeElement, (MeasureEllipseFigure) figure);
}
else if (figure instanceof MeasurePointFigure)
{
writeSVGHeader(shapeElement);
writeMeasurePointFigure(shapeElement,
(MeasurePointFigure) figure);
writeTextFigure(shapeElement, (MeasurePointFigure) figure);
}
else if (figure instanceof MeasureLineConnectionFigure)
{
writeSVGHeader(shapeElement);
writeLineConnectionFigure(shapeElement,
(MeasureLineConnectionFigure) figure);
writeTextFigure(shapeElement,
(MeasureLineConnectionFigure) figure);
}
else if (figure instanceof MeasureBezierFigure)
{
writeSVGHeader(shapeElement);
writeMeasureBezierFigure(shapeElement,
(MeasureBezierFigure) figure);
writeTextFigure(shapeElement, (MeasureBezierFigure) figure);
}
else if (figure instanceof MeasureLineFigure)
{
writeSVGHeader(shapeElement);
writeMeasureLineFigure(shapeElement,
(MeasureLineFigure) figure);
writeTextFigure(shapeElement, (MeasureLineFigure) figure);
}
else if (figure instanceof MeasureTextFigure)
{
writeSVGHeader(shapeElement);
writeTextFigure(shapeElement, (MeasureTextFigure) figure);
}
}
private void writeSVGHeader(XMLElement shapeElement)
{
XMLElement svgElement=
new XMLElement(IOConstants.SVG_TAG, IOConstants.SVG_NAMESPACE);
svgElement.setAttribute(IOConstants.XLINK_ATTRIBUTE,
IOConstants.SVG_XLINK_VALUE);
svgElement.setAttribute(IOConstants.VERSION_TAG,
IOConstants.SVG_VERSION);
shapeElement.addChild(svgElement);
}
private void writeTextFigure(XMLElement shapeElement, MeasureTextFigure fig)
throws ParsingException
{
writeTextFigure(shapeElement, (TextFigure) fig);
}
private void writeTextFigure(XMLElement shapeElement, TextHolderFigure fig)
throws ParsingException
{
XMLElement textElement=new XMLElement(IOConstants.TEXT_TAG);
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
svgElement.addChild(textElement);
textElement.setContent(fig.getText());
textElement.setAttribute(IOConstants.X_ATTRIBUTE, fig.getStartPoint()
.getX()
+"");
textElement.setAttribute(IOConstants.Y_ATTRIBUTE, fig.getStartPoint()
.getY()
+"");
writeTransformAttribute(textElement, fig.getAttributes());
writeFontAttributes(textElement, fig.getAttributes());
}
private void writeLineConnectionFigure(XMLElement shapeElement,
MeasureLineConnectionFigure fig) throws ParsingException
{
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
XMLElement lineConnectionElement=new XMLElement(IOConstants.LINE_TAG);
svgElement.addChild(lineConnectionElement);
ROIFigure startConnection=
(ROIFigure) fig.getStartConnector().getOwner();
ROIFigure endConnection=(ROIFigure) fig.getEndConnector().getOwner();
lineConnectionElement.setAttribute(
IOConstants.CONNECTION_FROM_ATTRIBUTE, startConnection.getROI()
.getID()+"");
lineConnectionElement.setAttribute(IOConstants.CONNECTION_TO_ATTRIBUTE,
endConnection.getROI().getID()+"");
if (fig.getNodeCount()==2)
{
lineConnectionElement.setAttribute(IOConstants.X1_ATTRIBUTE, fig
.getNode(0).x[0]+"");
lineConnectionElement.setAttribute(IOConstants.Y1_ATTRIBUTE, fig
.getNode(0).y[0]+"");
lineConnectionElement.setAttribute(IOConstants.X2_ATTRIBUTE, fig
.getNode(1).x[0]+"");
lineConnectionElement.setAttribute(IOConstants.Y2_ATTRIBUTE, fig
.getNode(1).y[0]+"");
}
else
{
LinkedList<Point2D.Double> points=new LinkedList<Point2D.Double>();
BezierPath bezier=fig.getBezierPath();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
}
String pointsValues=
toPoints(points.toArray(new Point2D.Double[points.size()]));
lineConnectionElement.setAttribute(IOConstants.POINTS_ATTRIBUTE,
pointsValues);
}
writeShapeAttributes(lineConnectionElement, fig.getAttributes());
writeTransformAttribute(lineConnectionElement, fig.getAttributes());
}
private void writeMeasureBezierFigure(XMLElement shapeElement,
MeasureBezierFigure fig) throws ParsingException
{
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
if (fig.isClosed()) writePolygonFigure(svgElement, fig);
else writePolylineFigure(svgElement, fig);
}
private void writePolygonFigure(IXMLElement svgElement,
MeasureBezierFigure fig) throws ParsingException
{
XMLElement bezierElement=new XMLElement(IOConstants.POLYGON_TAG);
svgElement.addChild(bezierElement);
LinkedList<Point2D.Double> points=new LinkedList<Point2D.Double>();
LinkedList<Point2D.Double> points1=new LinkedList<Point2D.Double>();
LinkedList<Point2D.Double> points2=new LinkedList<Point2D.Double>();
LinkedList<Integer> maskList=new LinkedList<Integer>();
BezierPath bezier=fig.getBezierPath();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
points1.add(new Point2D.Double(node.x[1], node.y[1]));
points2.add(new Point2D.Double(node.x[2], node.y[2]));
maskList.add(Integer.valueOf(node.getMask()));
}
String pointsValues =
toPoints(points.toArray(new Point2D.Double[points.size()]));
String points1Values =
toPoints(points1.toArray(new Point2D.Double[points1.size()]));
String points2Values =
toPoints(points2.toArray(new Point2D.Double[points2.size()]));
StringBuffer maskValues = new StringBuffer();
for( int i = 0 ; i < maskList.size()-1; i++) {
maskValues.append(maskList.get(i));
maskValues.append(",");
}
maskValues.append(maskList.get(maskList.size()-1));
bezierElement.setAttribute(IOConstants.POINTS_ATTRIBUTE, pointsValues);
bezierElement.setAttribute(IOConstants.POINTS_CONTROL1_ATTRIBUTE,
points1Values);
bezierElement.setAttribute(IOConstants.POINTS_CONTROL2_ATTRIBUTE,
points2Values);
bezierElement.setAttribute(IOConstants.POINTS_MASK_ATTRIBUTE,
maskValues.toString());
writeShapeAttributes(bezierElement, fig.getAttributes());
writeTransformAttribute(bezierElement, fig.getAttributes());
}
private void writePolylineFigure(IXMLElement svgElement,
MeasureBezierFigure fig) throws ParsingException
{
XMLElement bezierElement=new XMLElement(IOConstants.POLYLINE_TAG);
svgElement.addChild(bezierElement);
LinkedList<Point2D.Double> points=new LinkedList<Point2D.Double>();
LinkedList<Point2D.Double> points1=new LinkedList<Point2D.Double>();
LinkedList<Point2D.Double> points2=new LinkedList<Point2D.Double>();
LinkedList<Integer> maskList=new LinkedList<Integer>();
BezierPath bezier=fig.getBezierPath();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
points1.add(new Point2D.Double(node.x[1], node.y[1]));
points2.add(new Point2D.Double(node.x[2], node.y[2]));
maskList.add(Integer.valueOf(node.getMask()));
}
String pointsValues =
toPoints(points.toArray(new Point2D.Double[points.size()]));
String points1Values =
toPoints(points1.toArray(new Point2D.Double[points1.size()]));
String points2Values =
toPoints(points2.toArray(new Point2D.Double[points2.size()]));
StringBuffer maskValues = new StringBuffer();
for( int i = 0 ; i < maskList.size()-1; i++) {
maskValues.append(maskList.get(i));
maskValues.append(",");
}
maskValues.append(maskList.get(maskList.size()-1));
bezierElement.setAttribute(IOConstants.POINTS_ATTRIBUTE, pointsValues);
bezierElement.setAttribute(IOConstants.POINTS_CONTROL1_ATTRIBUTE,
points1Values);
bezierElement.setAttribute(IOConstants.POINTS_CONTROL2_ATTRIBUTE,
points2Values);
bezierElement.setAttribute(IOConstants.POINTS_MASK_ATTRIBUTE,
maskValues.toString());
writeShapeAttributes(bezierElement, fig.getAttributes());
writeTransformAttribute(bezierElement, fig.getAttributes());
}
private void writeMeasureLineFigure(XMLElement shapeElement,
MeasureLineFigure fig) throws ParsingException
{
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
XMLElement lineElement=new XMLElement(IOConstants.LINE_TAG);
svgElement.addChild(lineElement);
if (fig.getNodeCount()==2)
{
lineElement.setAttribute(IOConstants.X1_ATTRIBUTE,
fig.getNode(0).x[0]+"");
lineElement.setAttribute(IOConstants.Y1_ATTRIBUTE,
fig.getNode(0).y[0]+"");
lineElement.setAttribute(IOConstants.X2_ATTRIBUTE,
fig.getNode(1).x[0]+"");
lineElement.setAttribute(IOConstants.Y2_ATTRIBUTE,
fig.getNode(1).y[0]+"");
}
else
{
LinkedList<Point2D.Double> points=new LinkedList<Point2D.Double>();
BezierPath bezier=fig.getBezierPath();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
}
String pointsValues=
toPoints(points.toArray(new Point2D.Double[points.size()]));
lineElement
.setAttribute(IOConstants.POINTS_ATTRIBUTE, pointsValues);
}
writeShapeAttributes(lineElement, fig.getAttributes());
writeTransformAttribute(lineElement, fig.getAttributes());
}
private void writeMeasureEllipseFigure(XMLElement shapeElement,
MeasureEllipseFigure fig) throws ParsingException
{
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
XMLElement ellipseElement=new XMLElement(IOConstants.ELLIPSE_TAG);
svgElement.addChild(ellipseElement);
double rx=fig.getEllipse().getWidth()/2d;
double ry=fig.getEllipse().getHeight()/2d;
double cx=fig.getEllipse().getCenterX();
double cy=fig.getEllipse().getCenterY();
ellipseElement.setAttribute(IOConstants.CX_ATTRIBUTE, cx+"");
ellipseElement.setAttribute(IOConstants.CY_ATTRIBUTE, cy+"");
ellipseElement.setAttribute(IOConstants.RX_ATTRIBUTE, rx+"");
ellipseElement.setAttribute(IOConstants.RY_ATTRIBUTE, ry+"");
writeShapeAttributes(ellipseElement, fig.getAttributes());
writeTransformAttribute(ellipseElement, fig.getAttributes());
}
private void writeMeasurePointFigure(XMLElement shapeElement,
MeasurePointFigure fig) throws ParsingException
{
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
XMLElement ellipseElement=new XMLElement(IOConstants.POINT_TAG);
svgElement.addChild(ellipseElement);
double cx=fig.getCentre().getX();// fig.getX() + fig.getWidth() / 2d;
double cy=fig.getCentre().getY();// fig.getY() + fig.getHeight() /
// 2d;
double rx=fig.getWidth()/2d;
double ry=fig.getHeight()/2d;
ellipseElement.setAttribute(IOConstants.CX_ATTRIBUTE, cx+"");
ellipseElement.setAttribute(IOConstants.CY_ATTRIBUTE, cy+"");
ellipseElement.setAttribute(IOConstants.RX_ATTRIBUTE, rx+"");
ellipseElement.setAttribute(IOConstants.RY_ATTRIBUTE, ry+"");
writeShapeAttributes(ellipseElement, fig.getAttributes());
writeTransformAttribute(ellipseElement, fig.getAttributes());
}
private void writeMeasureRectangleFigure(XMLElement shapeElement,
MeasureRectangleFigure fig) throws ParsingException
{
IXMLElement svgElement=
shapeElement.getFirstChildNamed(IOConstants.SVG_TAG);
XMLElement rectElement=new XMLElement(IOConstants.RECT_TAG);
svgElement.addChild(rectElement);
rectElement.setAttribute(IOConstants.X_ATTRIBUTE, fig.getX()+"");
rectElement.setAttribute(IOConstants.Y_ATTRIBUTE, fig.getY()+"");
rectElement
.setAttribute(IOConstants.WIDTH_ATTRIBUTE, fig.getWidth()+"");
rectElement.setAttribute(IOConstants.HEIGHT_ATTRIBUTE, fig.getHeight()
+"");
writeShapeAttributes(rectElement, fig.getAttributes());
writeTransformAttribute(rectElement, fig.getAttributes());
}
protected void writeShapeAttributes(IXMLElement elem,
Map<AttributeKey, Object> f) throws ParsingException
{
// 'color'
// Value: <color> | inherit
// Initial: depends on user agent
// Applies to: None. Indirectly affects other properties via
// currentColor
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified <color> value, except inherit
//
// Nothing to do: Attribute 'color' is not needed.
// 'color-rendering'
// Value: auto | optimizeSpeed | optimizeQuality | inherit
// Initial: auto
// Applies to: container elements , graphics elements and 'animateColor'
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
//
// Nothing to do: Attribute 'color-rendering' is not needed.
// 'fill'
// Value: <paint> | inherit (See Specifying paint)
// Initial: black
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: "none", system paint, specified <color> value or
// absolute IRI
Object gradient=FILL_GRADIENT.get(f);
if (gradient!=null)
{
IXMLElement gradientElem;
if (gradient instanceof LinearGradient)
{
LinearGradient lg=(LinearGradient) gradient;
gradientElem=
createLinearGradient(document, lg.getX1(), lg.getY1(),
lg.getX2(), lg.getY2(), lg.getStopOffsets(), lg
.getStopColors(), lg.isRelativeToFigureBounds());
}
else
/* if (gradient instanceof RadialGradient) */{
RadialGradient rg=(RadialGradient) gradient;
gradientElem=
createRadialGradient(document, rg.getCX(), rg.getCY(),
rg.getR(), rg.getStopOffsets(), rg.getStopColors(),
rg.isRelativeToFigureBounds());
}
String id=getId(gradientElem);
gradientElem.setAttribute("id", "xml", id);
defs.addChild(gradientElem);
writeAttribute(elem, "fill", "url(#"+id+")", "#000");
}
else
{
writeAttribute(elem, "fill", toColor(MeasurementAttributes.FILL_COLOR.get(f)), "#000");
}
// 'fill-opacity'
// Value: <opacity-value> | inherit
// Initial: 1
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "fill-opacity",
MeasurementAttributes.FILL_COLOR.get(f).getAlpha()/255.0, 1d);
// 'fill-rule'
// Value: nonzero | evenodd | inherit
// Initial: nonzero
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
if (MeasurementAttributes.WINDING_RULE.get(f)!=WindingRule.NON_ZERO)
{
writeAttribute(elem, "fill-rule", "evenodd", "nonzero");
}
// 'stroke'
// Value: <paint> | inherit (See Specifying paint)
// Initial: none
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: "none", system paint, specified <color> value
// or absolute IRI
gradient=STROKE_GRADIENT.get(f);
if (gradient!=null)
{
IXMLElement gradientElem;
if (gradient instanceof LinearGradient)
{
LinearGradient lg=(LinearGradient) gradient;
gradientElem=
createLinearGradient(document, lg.getX1(), lg.getY1(),
lg.getX2(), lg.getY2(), lg.getStopOffsets(), lg
.getStopColors(), lg.isRelativeToFigureBounds());
}
else
/* if (gradient instanceof RadialGradient) */{
RadialGradient rg=(RadialGradient) gradient;
gradientElem=
createRadialGradient(document, rg.getCX(), rg.getCY(),
rg.getR(), rg.getStopOffsets(), rg.getStopColors(),
rg.isRelativeToFigureBounds());
}
String id=getId(gradientElem);
gradientElem.setAttribute("id", "xml", id);
defs.addChild(gradientElem);
writeAttribute(elem, "stroke", "url(#"+id+")", "none");
}
else
{
writeAttribute(elem, "stroke", toColor(MeasurementAttributes.STROKE_COLOR.get(f)), "none");
}
// 'stroke-dasharray'
// Value: none | <dasharray> | inherit
// Initial: none
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes (non-additive)
// Computed value: Specified value, except inherit
double[] dashes=MeasurementAttributes.STROKE_DASHES.get(f);
if (dashes!=null)
{
StringBuilder buf=new StringBuilder();
for (int i=0; i<dashes.length; i++)
{
if (i!=0)
{
buf.append(',');
}
buf.append(toNumber(dashes[i]));
}
writeAttribute(elem, "stroke-dasharray", buf.toString(), null);
}
// 'stroke-dashoffset'
// Value: <length> | inherit
// Initial: 0
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "stroke-dashoffset", MeasurementAttributes.STROKE_DASH_PHASE.get(f), 0d);
// 'stroke-linecap'
// Value: butt | round | square | inherit
// Initial: butt
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "stroke-linecap", strokeLinecapMap.get(MeasurementAttributes.STROKE_CAP
.get(f)), "butt");
// 'stroke-linejoin'
// Value: miter | round | bevel | inherit
// Initial: miter
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "stroke-linejoin", strokeLinejoinMap
.get(MeasurementAttributes.STROKE_JOIN.get(f)), "miter");
// 'stroke-miterlimit'
// Value: <miterlimit> | inherit
// Initial: 4
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "stroke-miterlimit", MeasurementAttributes.STROKE_MITER_LIMIT.get(f), 4d);
// 'stroke-opacity'
// Value: <opacity-value> | inherit
// Initial: 1
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "stroke-opacity",
MeasurementAttributes.STROKE_COLOR.get(f).getAlpha()/255.0, 1d);
// 'stroke-width'
// Value: <length> | inherit
// Initial: 1
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "stroke-width", MeasurementAttributes.STROKE_WIDTH.get(f), 1d);
writeAttribute(elem, MeasurementAttributes.SHOWMEASUREMENT.getKey(),
MeasurementAttributes.SHOWMEASUREMENT.get(f).toString(), "false");
writeAttribute(elem, MeasurementAttributes.SHOWTEXT.getKey(),
MeasurementAttributes.SHOWTEXT.get(f).toString(), "false");
}
/*
* Writes the transform attribute as specified in
* http://www.w3.org/TR/SVGMobile12/coords.html#TransformAttribute
*
*/
protected void writeTransformAttribute(IXMLElement elem,
Map<AttributeKey, Object> a) throws ParsingException
{
AffineTransform t=TRANSFORM.get(a);
if (t!=null)
{
writeAttribute(elem, "transform", toTransform(t), "none");
}
}
/*
* Writes font attributes as listed in
* http://www.w3.org/TR/SVGMobile12/feature.html#Font
*/
private void writeFontAttributes(IXMLElement elem,
Map<AttributeKey, Object> a) throws ParsingException
{
Object gradient=FILL_GRADIENT.get(a);
if (gradient!=null)
{
IXMLElement gradientElem;
if (gradient instanceof LinearGradient)
{
LinearGradient lg=(LinearGradient) gradient;
gradientElem=
createLinearGradient(document, lg.getX1(), lg.getY1(),
lg.getX2(), lg.getY2(), lg.getStopOffsets(), lg
.getStopColors(), lg.isRelativeToFigureBounds());
}
else
/* if (gradient instanceof RadialGradient) */{
RadialGradient rg=(RadialGradient) gradient;
gradientElem=
createRadialGradient(document, rg.getCX(), rg.getCY(),
rg.getR(), rg.getStopOffsets(), rg.getStopColors(),
rg.isRelativeToFigureBounds());
}
String id=getId(gradientElem);
gradientElem.setAttribute("id", "xml", id);
defs.addChild(gradientElem);
writeAttribute(elem, "fill", "url(#"+id+")", "#000");
}
else
{
writeAttribute(elem, "fill", toColor(MeasurementAttributes.TEXT_COLOR.get(a)), "#000");
}
// 'fill-opacity'
// Value: <opacity-value> | inherit
// Initial: 1
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "fill-opacity",
MeasurementAttributes.TEXT_COLOR.get(a).getAlpha()/255.0, 1d);
// 'font-family'
// Value: [[ <family-name> |
// <generic-family> ],]* [<family-name> |
// <generic-family>] | inherit
// Initial: depends on user agent
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "font-family", MeasurementAttributes.FONT_FACE.get(a).getFamily(),
"Dialog");
// 'font-size'
// Value: <absolute-size> | <relative-size> |
// <length> | inherit
// Initial: medium
// Applies to: text content elements
// Inherited: yes, the computed value is inherited
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Absolute length
writeAttribute(elem, "font-size", MeasurementAttributes.FONT_SIZE.get(a), 0d);
// 'font-style'
// Value: normal | italic | oblique | inherit
// Initial: normal
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
writeAttribute(elem, "font-style", (MeasurementAttributes.FONT_ITALIC.get(a)) ? "italic"
: "normal", "normal");
// 'font-variant'
// Value: normal | small-caps | inherit
// Initial: normal
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: no
// Computed value: Specified value, except inherit
writeAttribute(elem, "font-variant", "normal", "normal");
// 'font-weight'
// Value: normal | bold | bolder | lighter | 100 | 200 | 300
// | 400 | 500 | 600 | 700 | 800 | 900 | inherit
// Initial: normal
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: one of the legal numeric values, non-numeric
// values shall be converted to numeric values according to the rules
// defined below.
writeAttribute(elem, "font-weight", (MeasurementAttributes.FONT_BOLD.get(a)) ? "bold"
: "normal", "normal");
}
private static String toPath(BezierPath[] paths)
{
StringBuilder buf=new StringBuilder();
for (int j=0; j<paths.length; j++)
{
BezierPath path=paths[j];
if (path.size()==0)
{
// nothing to do
}
else if (path.size()==1)
{
BezierPath.Node current=path.get(0);
buf.append("M ");
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
buf.append(" L ");
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]+1);
}
else
{
BezierPath.Node previous;
BezierPath.Node current;
previous=current=path.get(0);
buf.append("M ");
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
for (int i=1, n=path.size(); i<n; i++)
{
previous=current;
current=path.get(i);
if ((previous.mask&BezierPath.C2_MASK)==0)
{
if ((current.mask&BezierPath.C1_MASK)==0)
{
buf.append(" L ");
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
else
{
buf.append(" Q ");
buf.append(current.x[1]);
buf.append(' ');
buf.append(current.y[1]);
buf.append(' ');
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
}
else
{
if ((current.mask&BezierPath.C1_MASK)==0)
{
buf.append(" Q ");
buf.append(current.x[2]);
buf.append(' ');
buf.append(current.y[2]);
buf.append(' ');
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
else
{
buf.append(" C ");
buf.append(previous.x[2]);
buf.append(' ');
buf.append(previous.y[2]);
buf.append(' ');
buf.append(current.x[1]);
buf.append(' ');
buf.append(current.y[1]);
buf.append(' ');
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
}
}
if (path.isClosed())
{
if (path.size()>1)
{
previous=path.get(path.size()-1);
current=path.get(0);
if ((previous.mask&BezierPath.C2_MASK)==0)
{
if ((current.mask&BezierPath.C1_MASK)==0)
{
buf.append(" L ");
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
else
{
buf.append(" Q ");
buf.append(current.x[1]);
buf.append(' ');
buf.append(current.y[1]);
buf.append(' ');
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
}
else
{
if ((current.mask&BezierPath.C1_MASK)==0)
{
buf.append(" Q ");
buf.append(previous.x[2]);
buf.append(' ');
buf.append(previous.y[2]);
buf.append(' ');
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
else
{
buf.append(" C ");
buf.append(previous.x[2]);
buf.append(' ');
buf.append(previous.y[2]);
buf.append(' ');
buf.append(current.x[1]);
buf.append(' ');
buf.append(current.y[1]);
buf.append(' ');
buf.append(current.x[0]);
buf.append(' ');
buf.append(current.y[0]);
}
}
}
buf.append(" Z");
}
}
}
return buf.toString();
}
protected void writeAttribute(IXMLElement elem, String name, String value,
String defaultValue)
{
writeAttribute(elem, name, "", value, defaultValue);
}
protected void writeAttribute(IXMLElement elem, String name,
String namespace, String value, String defaultValue)
{
elem.setAttribute(name, value);
}
protected void writeAttribute(IXMLElement elem, String name, Color color,
Color defaultColor)
{
writeAttribute(elem, name, IOConstants.SVG_NAMESPACE, toColor(color),
toColor(defaultColor));
}
protected void writeAttribute(IXMLElement elem, String name, double value,
double defaultValue)
{
writeAttribute(elem, name, IOConstants.SVG_NAMESPACE, value,
defaultValue);
}
protected void writeAttribute(IXMLElement elem, String name,
String namespace, double value, double defaultValue)
{
elem.setAttribute(name, toNumber(value));
}
/**
* Returns a double array as a number attribute value.
*/
private static String toNumber(double number)
{
String str=Double.toString(number);
if (str.endsWith(".0"))
{
str=str.substring(0, str.length()-2);
}
return str;
}
/**
* Returns a Point2D.Double array as a Points attribute value. as specified
* in http://www.w3.org/TR/SVGMobile12/shapes.html#PointsBNF
*/
private static String toPoints(Point2D.Double[] points)
throws ParsingException
{
StringBuilder buf=new StringBuilder();
for (int i=0; i<points.length; i++)
{
if (i!=0)
{
buf.append(", ");
}
buf.append(toNumber(points[i].x));
buf.append(',');
buf.append(toNumber(points[i].y));
}
return buf.toString();
}
/*
* Converts an AffineTransform into an SVG transform attribute value as
* specified in
* http://www.w3.org/TR/SVGMobile12/coords.html#TransformAttribute
*/
private static String toTransform(AffineTransform t)
throws ParsingException
{
StringBuilder buf=new StringBuilder();
switch (t.getType())
{
case AffineTransform.TYPE_IDENTITY:
buf.append("none");
break;
case AffineTransform.TYPE_TRANSLATION:
// translate(<tx> [<ty>]), specifies a translation by tx and ty.
// If <ty> is not provided, it is assumed to be zero.
buf.append("translate(");
buf.append(toNumber(t.getTranslateX()));
if (t.getTranslateY()!=0d)
{
buf.append(' ');
buf.append(toNumber(t.getTranslateY()));
}
buf.append(')');
break;
/*
* case AffineTransform.TYPE_GENERAL_ROTATION : case
* AffineTransform.TYPE_QUADRANT_ROTATION : case
* AffineTransform.TYPE_MASK_ROTATION : // rotate(<rotate-angle> [<cx>
* <cy>]), specifies a rotation by // <rotate-angle> degrees about a
* given point. // If optional parameters <cx> and <cy> are not
* supplied, the // rotate is about the origin of the current user
* coordinate // system. The operation corresponds to the matrix //
* [cos(a) sin(a) -sin(a) cos(a) 0 0]. // If optional parameters
* <cx> and <cy> are supplied, the rotate // is about the point (<cx>,
* <cy>). The operation represents the // equivalent of the
* following specification: // translate(<cx>, <cy>) rotate(<rotate-angle>) //
* translate(-<cx>, -<cy>). buf.append("rotate(");
* buf.append(toNumber(t.getScaleX())); buf.append(')'); break;
*/
case AffineTransform.TYPE_UNIFORM_SCALE:
// scale(<sx> [<sy>]), specifies a scale operation by sx
// and sy. If <sy> is not provided, it is assumed to be equal
// to <sx>.
buf.append("scale(");
buf.append(toNumber(t.getScaleX()));
buf.append(')');
break;
case AffineTransform.TYPE_GENERAL_SCALE:
case AffineTransform.TYPE_MASK_SCALE:
// scale(<sx> [<sy>]), specifies a scale operation by sx
// and sy. If <sy> is not provided, it is assumed to be equal
// to <sx>.
buf.append("scale(");
buf.append(toNumber(t.getScaleX()));
buf.append(' ');
buf.append(toNumber(t.getScaleY()));
buf.append(')');
break;
default:
// matrix(<a> <b> <c> <d> <e> <f>), specifies a transformation
// in the form of a transformation matrix of six values.
// matrix(a,b,c,d,e,f) is equivalent to applying the
// transformation matrix [a b c d e f].
buf.append("matrix(");
double[] matrix=new double[6];
t.getMatrix(matrix);
for (int i=0; i<matrix.length; i++)
{
if (i!=0)
{
buf.append(' ');
}
buf.append(toNumber(matrix[i]));
}
buf.append(')');
break;
}
return buf.toString();
}
private static String toColor(Color color)
{
if (color==null) { return "none"; }
String value;
value="000000"+Integer.toHexString(color.getRGB());
value="#"+value.substring(value.length()-6);
if (value.charAt(1)==value.charAt(2)&&value.charAt(3)==value.charAt(4)
&&value.charAt(5)==value.charAt(6))
{
value="#"+value.charAt(1)+value.charAt(3)+value.charAt(5);
}
return value;
}
protected IXMLElement createLinearGradient(IXMLElement doc, double x1,
double y1, double x2, double y2, double[] stopOffsets,
Color[] stopColors, boolean isRelativeToFigureBounds)
throws ParsingException
{
IXMLElement elem=doc.createElement("linearGradient");
writeAttribute(elem, "x1", toNumber(x1), "0");
writeAttribute(elem, "y1", toNumber(y1), "0");
writeAttribute(elem, "x2", toNumber(x2), "1");
writeAttribute(elem, "y2", toNumber(y2), "0");
writeAttribute(elem, "gradientUnits",
(isRelativeToFigureBounds) ? "objectBoundingBox" : "useSpaceOnUse",
"objectBoundingBox");
for (int i=0; i<stopOffsets.length; i++)
{
IXMLElement stop=new XMLElement("stop");
writeAttribute(stop, "offset", toNumber(stopOffsets[i]), null);
writeAttribute(stop, "stop-color", toColor(stopColors[i]), null);
writeAttribute(stop, "stop-opacity", toNumber(stopColors[i]
.getAlpha()/255d), "1");
elem.addChild(stop);
}
return elem;
}
protected IXMLElement createRadialGradient(IXMLElement doc, double cx,
double cy, double r, double[] stopOffsets, Color[] stopColors,
boolean isRelativeToFigureBounds) throws ParsingException
{
IXMLElement elem=doc.createElement("radialGradient");
writeAttribute(elem, "cx", toNumber(cx), "0.5");
writeAttribute(elem, "cy", toNumber(cy), "0.5");
writeAttribute(elem, "r", toNumber(r), "0.5");
writeAttribute(elem, "gradientUnits",
(isRelativeToFigureBounds) ? "objectBoundingBox" : "useSpaceOnUse",
"objectBoundingBox");
for (int i=0; i<stopOffsets.length; i++)
{
IXMLElement stop=new XMLElement("stop");
writeAttribute(stop, "offset", toNumber(stopOffsets[i]), null);
writeAttribute(stop, "stop-color", toColor(stopColors[i]), null);
writeAttribute(stop, "stop-opacity", toNumber(stopColors[i]
.getAlpha()/255d), "1");
elem.addChild(stop);
}
return elem;
}
}