/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2015, Open Source Geospatial Foundation (OSGeo)
*
* This library 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;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotools.styling;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import junit.framework.Assert;
import org.geotools.factory.CommonFactoryFinder;
import org.junit.Test;
import org.opengis.filter.expression.Expression;
import org.opengis.style.ContrastMethod;
import org.opengis.style.GraphicalSymbol;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
*
*
* @source $URL$
*/
public class SLDParserTest {
public static String SLD = "<StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" version=\"1.0.0\">"
+ " <NamedLayer>" + " <Name>layer</Name>" + " <UserStyle>" + " <Name>style</Name>"
+ " <FeatureTypeStyle>" + " <Rule>" + " <PolygonSymbolizer>" + " <Fill>"
+ " <CssParameter name=\"fill\">#FF0000</CssParameter>" + " </Fill>"
+ " </PolygonSymbolizer>" + " </Rule>" + " </FeatureTypeStyle>"
+ " </UserStyle>" + " </NamedLayer>" + "</StyledLayerDescriptor>";
public static String LocalizedSLD = "<StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" version=\"1.0.0\">"
+ " <NamedLayer>" + " <Name>layer</Name>" + " <UserStyle>" + " <Name>style</Name>"
+ " <FeatureTypeStyle>" + " <Rule>" + " <Title>sldtitle"
+ " <Localized lang=\"en\">english</Localized>"
+ " <Localized lang=\"it\">italian</Localized>"
+ " <Localized lang=\"fr\">french</Localized>"
+ " <Localized lang=\"fr_CA\">canada french</Localized>" + " </Title>"
+ " <Abstract>sld abstract"
+ " <Localized lang=\"en\">english abstract</Localized>"
+ " <Localized lang=\"it\">italian abstract</Localized>"
+ " <Localized lang=\"fr\">french abstract</Localized>" + " </Abstract>"
+ " <PolygonSymbolizer>" + " <Fill>"
+ " <CssParameter name=\"fill\">#FF0000</CssParameter>" + " </Fill>"
+ " </PolygonSymbolizer>" + " </Rule>" + " </FeatureTypeStyle>"
+ " </UserStyle>" + " </NamedLayer>" + "</StyledLayerDescriptor>";
public static String EmptyTitleSLD = "<StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" version=\"1.0.0\">"
+ " <NamedLayer>" + " <Name>layer</Name>" + " <UserStyle>" + " <Name>style</Name>"
+ " <FeatureTypeStyle>" + " <Rule>" + " <Title></Title>"
+ " <PolygonSymbolizer>" + " <Fill>"
+ " <CssParameter name=\"fill\">#FF0000</CssParameter>" + " </Fill>"
+ " </PolygonSymbolizer>" + " </Rule>" + " </FeatureTypeStyle>"
+ " </UserStyle>" + " </NamedLayer>" + "</StyledLayerDescriptor>";
public static String EmptyAbstractSLD = "<StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" version=\"1.0.0\">"
+ " <NamedLayer>" + " <Name>layer</Name>" + " <UserStyle>" + " <Name>style</Name>"
+ " <FeatureTypeStyle>" + " <Rule>" + " <Abstract></Abstract>"
+ " <PolygonSymbolizer>" + " <Fill>"
+ " <CssParameter name=\"fill\">#FF0000</CssParameter>" + " </Fill>"
+ " </PolygonSymbolizer>" + " </Rule>" + " </FeatureTypeStyle>"
+ " </UserStyle>" + " </NamedLayer>" + "</StyledLayerDescriptor>";
static String SLD_DEFAULT_POINT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<StyledLayerDescriptor version=\"1.0.0\" \n"
+ " xsi:schemaLocation=\"http://www.opengis.net/sld StyledLayerDescriptor.xsd\" \n"
+ " xmlns=\"http://www.opengis.net/sld\" xmlns:ogc=\"http://www.opengis.net/ogc\" \n"
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ " <UserStyle>\n" + " <Name>Default Styler</Name>\n"
+ " <Title>Default Styler</Title>\n" + " <Abstract></Abstract>\n"
+ " <FeatureTypeStyle>\n"
+ " <FeatureTypeName>Feature</FeatureTypeName>\n" + " <Rule>\n"
+ " <PointSymbolizer>\n" + " <Graphic>\n"
+ " </Graphic>\n" + " </PointSymbolizer>\n"
+ " </Rule>\n" + " </FeatureTypeStyle>\n" + " </UserStyle>\n"
+ "</StyledLayerDescriptor>";
static String SLD_EXTERNALENTITY = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE StyledLayerDescriptor [\n"
+ "<!ENTITY c SYSTEM \"file:///this/file/is/top/secret\">\n" + "]>\n"
+ "<StyledLayerDescriptor version=\"1.0.0\" xmlns=\"http://www.opengis.net/sld\" xmlns:ogc=\"http://www.opengis.net/ogc\"\n"
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd\">\n"
+ " <NamedLayer>\n" + " <Name>tiger:poi</Name>\n" + " <UserStyle>\n"
+ " <Name>poi</Name>\n" + " <Title>Points of interest</Title>\n"
+ " <Abstract>Manhattan points of interest</Abstract>\n"
+ " <FeatureTypeStyle>\n" + " <Rule>\n" + " <PointSymbolizer>\n"
+ " <Graphic>\n" + " <Mark>\n"
+ " <WellKnownName>circle</WellKnownName>\n" + " <Fill>\n"
+ " <CssParameter name=\"fill\">#0000FF</CssParameter>\n"
+ " <CssParameter name=\"fill-opacity\">1.0</CssParameter>\n"
+ " </Fill>\n" + " </Mark>\n"
+ " <Size>11</Size>\n" + " </Graphic>\n"
+ " </PointSymbolizer>\n" + " <PointSymbolizer>\n"
+ " <Graphic>\n" + " <Mark>\n"
+ " <WellKnownName>circle</WellKnownName>\n" + " <Fill>\n"
+ " <CssParameter name=\"fill\">#ED0000</CssParameter>\n"
+ " <CssParameter name=\"fill-opacity\">1.0</CssParameter>\n"
+ " </Fill>\n" + " </Mark>\n"
+ " <Size>7</Size>\n" + " </Graphic>\n"
+ " </PointSymbolizer>\n" + " </Rule>\n" + " <Rule>\n"
+ " <MaxScaleDenominator>32000</MaxScaleDenominator>\n"
+ " <TextSymbolizer>\n" + " <Label>&c;</Label>\n"
+ " <Font>\n"
+ " <CssParameter name=\"font-family\">Arial</CssParameter>\n"
+ " <CssParameter name=\"font-weight\">Bold</CssParameter>\n"
+ " <CssParameter name=\"font-size\">14</CssParameter>\n"
+ " </Font>\n" + " <LabelPlacement>\n"
+ " <PointPlacement>\n" + " <AnchorPoint>\n"
+ " <AnchorPointX>0.5</AnchorPointX>\n"
+ " <AnchorPointY>0.5</AnchorPointY>\n"
+ " </AnchorPoint>\n" + " <Displacement>\n"
+ " <DisplacementX>0</DisplacementX>\n"
+ " <DisplacementY>-15</DisplacementY>\n"
+ " </Displacement>\n" + " </PointPlacement>\n"
+ " </LabelPlacement>\n" + " <Halo>\n"
+ " <Radius>\n" + " <ogc:Literal>2</ogc:Literal>\n"
+ " </Radius>\n" + " <Fill>\n"
+ " <CssParameter name=\"fill\">#FFFFFF</CssParameter>\n"
+ " </Fill>\n" + " </Halo>\n" + " <Fill>\n"
+ " <CssParameter name=\"fill\">#000000</CssParameter>\n"
+ " </Fill>\n" + " </TextSymbolizer>\n" + " </Rule>\n"
+ " </FeatureTypeStyle>\n" + " </UserStyle>\n" + " </NamedLayer>\n"
+ "</StyledLayerDescriptor>";
static String SLD_EXTERNAL_GRAPHIC = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<StyledLayerDescriptor version=\"1.0.0\" \n"
+ " xsi:schemaLocation=\"http://www.opengis.net/sld StyledLayerDescriptor.xsd\" \n"
+ " xmlns=\"http://www.opengis.net/sld\" xmlns:ogc=\"http://www.opengis.net/ogc\" \n"
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ " <UserStyle>\n" + " <Name>Default Styler</Name>\n"
+ " <Title>Default Styler</Title>\n" + " <Abstract></Abstract>\n"
+ " <FeatureTypeStyle>\n"
+ " <FeatureTypeName>Feature</FeatureTypeName>\n" + " <Rule>\n"
+ " <PointSymbolizer>\n" + " <Graphic>\n"
+ " <ExternalGraphic>\n"
+ " <OnlineResource xlink:type=\"simple\"\n"
+ " xlink:href=\"test-data/blob.gif\">\n"
+ " </OnlineResource>\n"
+ " <Format>image/png</Format>\n"
+ " </ExternalGraphic>\n"
+ " <Size>20</Size>\n" + " </Graphic>\n"
+ " </PointSymbolizer>\n" + " </Rule>\n"
+ " </FeatureTypeStyle>\n" + " </UserStyle>\n" + "</StyledLayerDescriptor>";
static String color = "00AA00";
static String formattedCssStrokeParameter = "<Stroke>" + "\n\t<CssParameter name=\"stroke\">#"
+ "\n\t\t<ogc:Function name=\"env\">"
+ "\n\t\t\t<ogc:Literal>stroke_color</ogc:Literal>" + "\n\t\t\t<ogc:Literal>" + color
+ "</ogc:Literal>" + "\n\t\t</ogc:Function>" + "\n\t</CssParameter>" + "</Stroke>";
static String contrastEnhance = " <ContrastEnhancement> " + "\n\t<Normalize> "
+ "\n\t<VendorOption name=\"Algorithm\">ClipToMinimumMaximum</VendorOption> "
+ "\n\t<VendorOption name=\"minValue\">1</VendorOption>"
+ "\n\t<VendorOption name=\"maxValue\">27.0</VendorOption>" + "\n\t</Normalize>"
+ "\n\t</ContrastEnhancement>";
static String contrastEnhanceOther = " <ContrastEnhancement> " + "\n\t<METHOD/> "
+ "\n\t</ContrastEnhancement>";
static String contrastEnhancelogExp = " <ContrastEnhancement> " + "\n\t<METHOD> "
+ "\n\t<VendorOption name='correctionFactor'>0.1</VendorOption>"
+ "\n\t<VendorOption name='normalizationFactor'>10.0</VendorOption>" + "\n\t</METHOD> "
+ "\n\t</ContrastEnhancement>";
static String contrastEnhanceBroken = " <ContrastEnhancement> " + "\n\t<Normalize> "
+ "\n\t<Algorithm/> " + "\n\t<Parameter >1</Parameter>"
+ "\n\t<Parameter name=\"maxValue\"/>" + "\n\t</Normalize>"
+ "\n\t</ContrastEnhancement>";
static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null);
@Test
public void testBasic() throws Exception {
SLDParser parser = new SLDParser(styleFactory, input(SLD));
Style[] styles = parser.readXML();
assertStyles(styles);
}
@Test
public void testLocalizedRuleTitle() throws Exception {
SLDParser parser = new SLDParser(styleFactory, input(LocalizedSLD));
Style[] styles = parser.readXML();
assertEquals("sldtitle", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString(Locale.JAPAN));
assertEquals("english", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString(Locale.ENGLISH));
assertEquals("english", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString(Locale.US));
assertEquals("english", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString(Locale.US));
assertEquals("italian", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString(Locale.ITALY));
assertEquals("french", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString(Locale.FRENCH));
assertEquals("canada french", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getTitle().toString(Locale.CANADA_FRENCH));
assertEquals("sld abstract", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getAbstract().toString(Locale.JAPAN));
assertEquals("english abstract", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getAbstract().toString(Locale.ENGLISH));
assertEquals("english abstract", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getAbstract().toString(Locale.US));
assertEquals("italian abstract", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getAbstract().toString(Locale.ITALY));
assertEquals("french abstract", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getAbstract().toString(Locale.FRENCH));
assertEquals("french abstract", styles[0].getFeatureTypeStyles()[0].getRules()[0]
.getDescription().getAbstract().toString(Locale.CANADA_FRENCH));
assertStyles(styles);
}
@Test
public void testEmptyTitle() throws Exception {
SLDParser parser = new SLDParser(styleFactory, input(EmptyTitleSLD));
Style[] styles = parser.readXML();
assertEquals("", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getTitle().toString());
}
@Test
public void testEmptyAbstract() throws Exception {
SLDParser parser = new SLDParser(styleFactory, input(EmptyAbstractSLD));
Style[] styles = parser.readXML();
assertEquals("", styles[0].getFeatureTypeStyles()[0].getRules()[0].getDescription()
.getAbstract().toString());
}
@Test
public void testMultipleParse() throws Exception {
SLDParser parser = new SLDParser(styleFactory, input(SLD));
Style[] styles = parser.readXML();
assertStyles(styles);
styles = parser.readDOM();
assertStyles(styles);
try {
parser.readXML();
fail("Parsing again Should have thrown exception");
} catch (Exception e) {
}
}
@Test
public void testDefaultPoint() throws Exception {
// fixes for GEOS-3111 broke default point symbsolizer handling
SLDParser parser = new SLDParser(styleFactory, input(SLD_DEFAULT_POINT));
Style[] styles = parser.readXML();
assertEquals(1, styles.length);
List<FeatureTypeStyle> fts = styles[0].featureTypeStyles();
assertEquals(1, fts.size());
List<Rule> rules = fts.get(0).rules();
assertEquals(1, rules.size());
List<Symbolizer> symbolizers = rules.get(0).symbolizers();
assertEquals(1, symbolizers.size());
PointSymbolizer ps = (PointSymbolizer) symbolizers.get(0);
// here we would have had two instead of one
List<GraphicalSymbol> graphicalSymbols = ps.getGraphic().graphicalSymbols();
assertEquals(1, graphicalSymbols.size());
Mark mark = (Mark) graphicalSymbols.get(0);
assertEquals(mark, CommonFactoryFinder.getStyleFactory(null).createMark());
}
@Test
public void testOnlineResourceLocator() throws MalformedURLException {
// test whether the configured resolver is called and the url is used correctly
SLDParser parser = new SLDParser(styleFactory, input(SLD_EXTERNAL_GRAPHIC));
parser.setOnLineResourceLocator(new ResourceLocator() {
public URL locateResource(String uri) {
Assert.assertEquals("test-data/blob.gif", uri);
return getClass().getResource(uri);
}
});
Style[] styles = parser.readXML();
assertEquals(1, styles.length);
List<FeatureTypeStyle> fts = styles[0].featureTypeStyles();
assertEquals(1, fts.size());
List<Rule> rules = fts.get(0).rules();
assertEquals(1, rules.size());
List<Symbolizer> symbolizers = rules.get(0).symbolizers();
assertEquals(1, symbolizers.size());
PointSymbolizer ps = (PointSymbolizer) symbolizers.get(0);
// here we would have had two instead of one
List<GraphicalSymbol> graphicalSymbols = ps.getGraphic().graphicalSymbols();
assertEquals(1, graphicalSymbols.size());
ExternalGraphic graphic = (ExternalGraphic) graphicalSymbols.get(0);
assertEquals(getClass().getResource("test-data/blob.gif"), graphic.getLocation());
}
@Test
public void testExternalEntitiesDisabled() {
// this SLD file references as external entity a file on the local filesystem
SLDParser parser = new SLDParser(styleFactory, input(SLD_EXTERNALENTITY));
// without a custom EntityResolver, the parser will try to read the entity file on the local file system
try {
parser.readXML();
fail("parsing should thrown an error");
} catch (RuntimeException e) {
assertTrue(e.getCause() instanceof FileNotFoundException);
}
parser = new SLDParser(styleFactory, input(SLD_EXTERNALENTITY));
// Set an EntityResolver implementation to prevent reading entities from the local file system.
// When resolving an XML entity, the empty InputSource returned by this resolver provokes
// a MalformedURLException
parser.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
return new InputSource();
}
});
try {
parser.readXML();
fail("parsing should thrown an error");
} catch (RuntimeException e) {
assertTrue(e.getCause() instanceof MalformedURLException);
}
parser = new SLDParser(styleFactory, input(SLD_EXTERNALENTITY));
// Set another EntityResolver
parser.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if ("file:///this/file/is/top/secret".equals(systemId)) {
return new InputSource(new StringReader("hello"));
} else {
return new InputSource();
}
}
});
// now parsing shouldn't throw an exception
parser.readXML();
}
@Test
public void testStrokeColorWithEnv() throws Exception {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
org.w3c.dom.Document node = builder
.parse(new StringBufferInputStream(formattedCssStrokeParameter));
SLDParser parser = new SLDParser(styleFactory);
Stroke stroke = parser.parseStroke(node.getDocumentElement());
// <strConcat([#], [env([stroke_color], [" + color + "])])>";
Assert.assertEquals("#" + color, stroke.getColor().evaluate(Color.decode("#" + color)));
}
@Test
public void testContrastEnhancement() throws Exception {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
org.w3c.dom.Document node = builder
.parse(new ByteArrayInputStream(contrastEnhance.getBytes()));
// First check the happy path for normalize
SLDParser parser = new SLDParser(styleFactory);
ContrastEnhancement ce = parser.parseContrastEnhancement(node.getDocumentElement());
ContrastMethod method = ce.getMethod();
assertNotNull(ce);
assertEquals("Wrong method type", "normalize", method.name().toLowerCase());
assertTrue("No Algotrithm set", ce.hasOption("Algorithm"));
assertEquals("wrong Algorithm", "ClipToMinimumMaximum",
ce.getOption("Algorithm").evaluate(null));
Map<String, Expression> params = ce.getOptions();
assertEquals("Wrong number of parameters", 3, params.size());
assertEquals("wrong param returned", "1", params.get("minValue").evaluate(null));
assertEquals("wrong param returned", "27.0", params.get("maxValue").evaluate(null));
// check the other methods still work
for (String methodName : new String[] { "Normalize", "Logarithmic", "Exponential",
"Histogram" }) {
String target = contrastEnhanceOther.replace("METHOD", methodName);
node = builder.parse(new ByteArrayInputStream(target.getBytes()));
ce = parser.parseContrastEnhancement(node.getDocumentElement());
method = ce.getMethod();
assertNotNull(method);
assertEquals("Wrong method returned", methodName.toLowerCase(),
method.name().toLowerCase());
}
for (String methodName : new String[] { "Logarithmic", "Exponential" }) {
String target = contrastEnhancelogExp.replace("METHOD", methodName);
System.out.println(target);
node = builder.parse(new ByteArrayInputStream(target.getBytes()));
ce = parser.parseContrastEnhancement(node.getDocumentElement());
method = ce.getMethod();
assertNotNull(method);
assertEquals("Wrong method returned", methodName.toLowerCase(),
method.name().toLowerCase());
params = ce.getOptions();
assertEquals("wrong number of parameters", 2, params.size());
assertEquals("wrong param returned", "10.0",
params.get("normalizationFactor").evaluate(null));
assertEquals("wrong param returned", "0.1",
params.get("correctionFactor").evaluate(null));
}
// now see what happens if we break things
node = builder.parse(new ByteArrayInputStream(contrastEnhanceBroken.getBytes()));
ce = parser.parseContrastEnhancement(node.getDocumentElement());
method = ce.getMethod();
assertNotNull(method);
assertNull("Algorithm set when it's not defined in SLD", ce.getOption("Algorithm"));
params = ce.getOptions();
assertNotNull(params);
assertTrue("Params should be empty", params.isEmpty());
}
void assertStyles(Style[] styles) {
assertEquals(1, styles.length);
assertEquals("style", styles[0].getName());
}
InputStream input(String sld) {
return new ByteArrayInputStream(sld.getBytes());
}
}