/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, 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. * * Created on 03 July 2002, 10:21 */ package org.geotools.filter; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.logging.Logger; import org.geotools.factory.CommonFactoryFinder; import org.opengis.filter.FilterFactory2; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.helpers.NamespaceSupport; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.TopologyException; /** * parsez short sections of gml for use in expressions and filters Hopefully we * can get away without a full parser here. * * @author iant * * @source $URL$ */ public final class ExpressionDOMParser { /** The logger for the filter module. */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter"); /** Factory for creating filters. */ private FilterFactory2 ff; /** Namespace Context for creating expressions */ private NamespaceSupport namespaceContext = null; /** Factory for creating geometry objects */ private static GeometryFactory gfac = new GeometryFactory(); /** int representation of a box */ private static final int GML_BOX = 1; /** int representation of a polygon */ private static final int GML_POLYGON = 2; /** int representation of a linestring */ private static final int GML_LINESTRING = 3; /** int representation of a point */ private static final int GML_POINT = 4; /** number of coordinates in a box */ private static final int NUM_BOX_COORDS = 5; /** * Creates a new instance of ExpressionXmlParser */ private ExpressionDOMParser() { this( CommonFactoryFinder.getFilterFactory2( null ) ); LOGGER.finer("made new logic factory"); } /** Constructor injection */ public ExpressionDOMParser( FilterFactory2 factory ){ ff = factory != null ? factory : CommonFactoryFinder.getFilterFactory2( null ); } /** Setter injection */ public void setFilterFactory( FilterFactory2 factory ){ ff = factory; } /** Namespace Support Setter */ public void setNamespaceContext( NamespaceSupport namespaceContext ){ this.namespaceContext = namespaceContext; } /** * * @deprecated Please use ExpressionDOMParser.expression * @param root */ public static Expression parseExpression( Node root ){ ExpressionDOMParser parser = new ExpressionDOMParser(); return parser.expression( root ); } /** * parses an expression for a filter. * * @param root the root node to parse, should be an filter expression. * * @return the geotools representation of the expression held in the node. */ public Expression expression(Node root) { LOGGER.finer("parsingExpression " + root.getLocalName()); //NodeList children = root.getChildNodes(); //LOGGER.finest("children "+children); if ((root == null) || (root.getNodeType() != Node.ELEMENT_NODE)) { LOGGER.finer("bad node input "); return null; } LOGGER.finer("processing root " + root.getLocalName()); Node child = root; String childName = (child.getLocalName() != null) ? child.getLocalName() : child.getNodeName(); if (childName.indexOf(':') != -1) { //the DOM parser wasnt properly set to handle namespaces... childName = childName.substring(childName.indexOf(':')+1); } if (childName.equalsIgnoreCase("Literal")) { LOGGER.finer("processing literal " + child); NodeList kidList = child.getChildNodes(); LOGGER.finest("literal elements (" + kidList.getLength() + ") " + kidList.toString()); for (int i = 0; i < kidList.getLength(); i++) { Node kid = kidList.item(i); LOGGER.finest("kid " + i + " " + kid); if (kid == null) { LOGGER.finest("Skipping "); continue; } if (kid.getNodeValue() == null) { /* it might be a gml string so we need to convert it into * a geometry this is a bit tricky since our standard * gml parser is SAX based and we're a DOM here. */ LOGGER.finer("node " + kid.getNodeValue() + " namespace " + kid.getNamespaceURI()); LOGGER.fine("a literal gml string?"); try { Geometry geom = parseGML(kid); if (geom != null) { LOGGER.finer("built a " + geom.getGeometryType() + " from gml"); LOGGER.finer("\tpoints: " + geom.getNumPoints()); } else { LOGGER.finer( "got a null geometry back from gml parser"); } return ff.literal(geom); } catch (IllegalFilterException ife) { LOGGER.warning("Problem building GML/JTS object: " + ife); } return null; } if (kid.getNodeValue().trim().length() == 0) { LOGGER.finest("empty text element"); continue; } // debuging only /*switch(kid.getNodeType()){ case Node.ELEMENT_NODE: LOGGER.finer("element :"+kid); break; case Node.TEXT_NODE: LOGGER.finer("text :"+kid); break; case Node.ATTRIBUTE_NODE: LOGGER.finer("Attribute :"+kid); break; case Node.CDATA_SECTION_NODE: LOGGER.finer("Cdata :"+kid); break; case Node.COMMENT_NODE: LOGGER.finer("comment :"+kid); break; } */ String nodeValue = kid.getNodeValue(); LOGGER.finer("processing " + nodeValue); try { //always store internal values as strings. We might lose info otherwise. return ff.literal(nodeValue); } catch (IllegalFilterException ife) { LOGGER.finer("Unable to build expression " + ife); return null; } } // creates an empty literal expression if there is nothing inside the literal return ff.literal(""); } if (childName.equalsIgnoreCase("add")) { try { LOGGER.fine("processing an Add"); //Node left = null; //Node right = null; Node value = child.getFirstChild(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add left value -> " + value + "<-"); Expression left = parseExpression(value); value = value.getNextSibling(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add right value -> " + value + "<-"); Expression right = parseExpression(value); return ff.add( left, right ); } catch (IllegalFilterException ife) { LOGGER.warning("Unable to build expression " + ife); return null; } } if (childName.equalsIgnoreCase("sub")) { try { //NodeList kids = child.getChildNodes(); Node value = child.getFirstChild(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add left value -> " + value + "<-"); Expression left = parseExpression(value); value = value.getNextSibling(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add right value -> " + value + "<-"); Expression right = parseExpression(value); return ff.subtract( left, right ); } catch (IllegalFilterException ife) { LOGGER.warning("Unable to build expression " + ife); return null; } } if (childName.equalsIgnoreCase("mul")) { try { //NodeList kids = child.getChildNodes(); Node value = child.getFirstChild(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add left value -> " + value + "<-"); Expression left = parseExpression(value); value = value.getNextSibling(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add right value -> " + value + "<-"); Expression right = parseExpression(value); return ff.multiply( left, right ); } catch (IllegalFilterException ife) { LOGGER.warning("Unable to build expression " + ife); return null; } } if (childName.equalsIgnoreCase("div")) { try { Node value = child.getFirstChild(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add left value -> " + value + "<-"); Expression left = parseExpression(value); value = value.getNextSibling(); while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); } LOGGER.finer("add right value -> " + value + "<-"); Expression right = parseExpression(value); return ff.divide( left, right ); } catch (IllegalFilterException ife) { LOGGER.warning("Unable to build expression " + ife); return null; } } if (childName.equalsIgnoreCase("PropertyName")) { try { //JD: trim whitespace here String value = child.getFirstChild().getNodeValue(); value = value != null ? value.trim() : value; PropertyName attribute = ff.property( value, namespaceContext ); // attribute.setAttributePath(child.getFirstChild().getNodeValue()); return attribute; } catch (IllegalFilterException ife) { LOGGER.warning("Unable to build expression: " + ife); return null; } } if (childName.equalsIgnoreCase("Function")) { Element param = (Element) child; NamedNodeMap map = param.getAttributes(); String funcName = null; for (int k = 0; k < map.getLength(); k++) { String res = map.item(k).getNodeValue(); String name = map.item(k).getLocalName(); if (name == null) { name = map.item(k).getNodeName(); } if (name.indexOf(':') != -1) { //the DOM parser was not properly set to handle namespaces... name = name.substring(name.indexOf(':')+1); } LOGGER.fine("attribute " + name + " with value of " + res); if (name.equalsIgnoreCase("name")) { funcName = res; } } if (funcName == null) { LOGGER.severe("failed to find a function name in " + child); return null; } ArrayList<Expression> args = new ArrayList<Expression>(); Node value = child.getFirstChild(); ARGS: while( value != null ){ while (value.getNodeType() != Node.ELEMENT_NODE) { value = value.getNextSibling(); if( value == null ) break ARGS; } args.add( parseExpression(value) ); value = value.getNextSibling(); } Expression[] array = args.toArray( new Expression[0]); return ff.function( funcName, array ); } if (child.getNodeType() == Node.TEXT_NODE) { LOGGER.finer("processing a text node " + root.getNodeValue()); String nodeValue = root.getNodeValue(); LOGGER.finer("Text name " + nodeValue); // see if it's an int try { try { Integer intLiteral = new Integer(nodeValue); return ff.literal(intLiteral); } catch (NumberFormatException e) { /* really empty */ } try { Double doubleLit = new Double(nodeValue); return ff.literal(doubleLit); } catch (NumberFormatException e) { /* really empty */ } return ff.literal(nodeValue); } catch (IllegalFilterException ife) { LOGGER.finer("Unable to build expression " + ife); } } return null; } /** * @deprecated Please use ExpressionDOMParser.gml * @param root * @return the java representation of the geometry contained in root. */ public static Geometry parseGML(Node root) { ExpressionDOMParser parser = new ExpressionDOMParser(); return parser.gml( root ); } /** * Parses the gml of this node to jts. * * @param root the parent node of the gml to parse. * @return the java representation of the geometry contained in root. */ public Geometry gml(Node root) { LOGGER.finer("processing gml " + root); List coordList; int type = 0; Node child = root; //Jesus I hate DOM. I have no idea why this was checking for localname //and then nodename - lead to non-deterministic behavior, that for //some reason only failed if the filter parser was used within the //SLDparser. I really would like that class redone, so we don't have //to use this crappy DOM GML parser. String childName = child.getNodeName(); if(childName == null) { childName = child.getLocalName(); } if(!childName.startsWith("gml:")) { childName = "gml:" + childName; } if (childName.equalsIgnoreCase("gml:box")) { type = GML_BOX; coordList = parseCoords(child); com.vividsolutions.jts.geom.Envelope env = new com.vividsolutions.jts.geom.Envelope(); for (int i = 0; i < coordList.size(); i++) { env.expandToInclude((Coordinate) coordList.get(i)); } Coordinate[] coords = new Coordinate[NUM_BOX_COORDS]; coords[0] = new Coordinate(env.getMinX(), env.getMinY()); coords[1] = new Coordinate(env.getMinX(), env.getMaxY()); coords[2] = new Coordinate(env.getMaxX(), env.getMaxY()); coords[3] = new Coordinate(env.getMaxX(), env.getMinY()); coords[4] = new Coordinate(env.getMinX(), env.getMinY()); //return new ReferencedEnvelope( env, null ); com.vividsolutions.jts.geom.LinearRing ring = null; try { ring = gfac.createLinearRing(coords); } catch (com.vividsolutions.jts.geom.TopologyException tope) { LOGGER.fine("Topology Exception in GMLBox" + tope); return null; } return gfac.createPolygon(ring, null); } //check for geometry properties if (childName.equalsIgnoreCase("gml:polygonmember") || childName.equalsIgnoreCase( "gml:pointmember") || childName.equalsIgnoreCase( "gml:linestringmember") || childName.equalsIgnoreCase( "gml:linearringmember" ) ) { for ( int i = 0; i < child.getChildNodes().getLength(); i++ ) { Node newChild = child.getChildNodes().item( i ); if ( newChild.getNodeType() == Node.ELEMENT_NODE ) { childName = newChild.getNodeName(); if ( !childName.startsWith("gml:" ) ) { childName = "gml:" + childName; } root = newChild; child = newChild; break; } } } if (childName.equalsIgnoreCase("gml:polygon")) { LOGGER.finer("polygon"); type = GML_POLYGON; LinearRing outer = null; List inner = new ArrayList(); NodeList kids = root.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node kid = kids.item(i); LOGGER.finer("doing " + kid); String kidName = kid.getNodeName(); if(kidName == null) { kidName = child.getLocalName(); } if(!kidName.startsWith("gml:")) { kidName = "gml:" + kidName; } if (kidName.equalsIgnoreCase("gml:outerBoundaryIs")) { outer = (LinearRing) parseGML(kid); } if (kidName.equalsIgnoreCase("gml:innerBoundaryIs")) { inner.add((LinearRing) parseGML(kid)); } } if (inner.size() > 0) { return gfac.createPolygon(outer, (LinearRing[]) inner.toArray(new LinearRing[0])); } else { return gfac.createPolygon(outer, null); } } if (childName.equalsIgnoreCase("gml:outerBoundaryIs") || childName.equalsIgnoreCase("gml:innerBoundaryIs")) { LOGGER.finer("Boundary layer"); NodeList kids = ((Element) child).getElementsByTagName( "gml:LinearRing"); if (kids.getLength() ==0) kids = ((Element) child).getElementsByTagName("LinearRing"); return parseGML(kids.item(0)); } if (childName.equalsIgnoreCase("gml:linearRing")) { LOGGER.finer("LinearRing"); coordList = parseCoords(child); com.vividsolutions.jts.geom.LinearRing ring = null; try { ring = gfac.createLinearRing((Coordinate[]) coordList.toArray( new Coordinate[] { })); } catch (TopologyException te) { LOGGER.finer("Topology Exception build linear ring: " + te); return null; } return ring; } if (childName.equalsIgnoreCase("gml:linestring")) { LOGGER.finer("linestring"); type = GML_LINESTRING; coordList = parseCoords(child); com.vividsolutions.jts.geom.LineString line = null; line = gfac.createLineString((Coordinate[]) coordList.toArray( new Coordinate[] { })); return line; } if (childName.equalsIgnoreCase("gml:point")) { LOGGER.finer("point"); type = GML_POINT; coordList = parseCoords(child); com.vividsolutions.jts.geom.Point point = null; point = gfac.createPoint((Coordinate) coordList.get(0)); return point; } if (childName.toLowerCase().startsWith("gml:multipolygon") || childName.toLowerCase().startsWith("gml:multilinestring") || childName.toLowerCase().startsWith("gml:multipoint")) { List multi = new ArrayList(); // parse all children thru parseGML NodeList kids = child.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { if ( kids.item(i).getNodeType() == Node.ELEMENT_NODE ) { multi.add(parseGML(kids.item(i))); } } if ( childName.toLowerCase().startsWith("gml:multipolygon") ) { LOGGER.finer( "MultiPolygon" ); return gfac.createMultiPolygon((Polygon[]) multi.toArray( new Polygon[0])); } else if ( childName.toLowerCase().startsWith("gml:multilinestring") ) { LOGGER.finer( "MultiLineString" ); return gfac.createMultiLineString((LineString[]) multi.toArray( new LineString[0])); } else { LOGGER.finer( "MultiPoint" ); return gfac.createMultiPoint((Point[])multi.toArray(new Point[0]) ); } } return null; } /** * Parse a DOM node into a coordiante list. * * @deprecated please use ExpressionDOMParser.coords() * @param root the root node representation of gml:coordinates. * @return the coordinates in a list. */ public static java.util.List parseCoords(Node root) { ExpressionDOMParser parser = new ExpressionDOMParser(); return parser.coords( root ); } /** * Parses a dom node into a coordinate list. * * @param root the root node representation of gml:coordinates. * @return the coordinates in a list. */ public java.util.List coords(Node root) { LOGGER.finer("parsing coordinate(s) " + root); List clist = new ArrayList(); NodeList kids = root.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node child = kids.item(i); LOGGER.finer("doing " + child); //if (child.getLocalName().equalsIgnoreCase("gml:coordinate")) { // String internal = child.getNodeValue(); //} String childName = child.getNodeName(); if(childName == null) { childName = child.getLocalName(); } if(!childName.startsWith("gml:")) { childName = "gml:" + childName; } if (childName.equalsIgnoreCase("gml:coord")) { //DJB: adding support for: //<gml:coord><gml:X>-180.0</gml:X><gml:Y>-90.0</gml:Y></gml:coord> //<gml:coord><gml:X>180.0</gml:X><gml:Y>90.0</gml:Y></gml:coord> Coordinate c = new Coordinate(); NodeList grandChildren = child.getChildNodes(); for (int t = 0; t < grandChildren.getLength(); t++) { Node grandChild = grandChildren.item(t); String grandChildName = grandChild.getNodeName(); if(grandChildName == null) grandChildName = grandChild.getLocalName(); if(!grandChildName.startsWith("gml:")) grandChildName = "gml:" + grandChildName; if (grandChildName.equalsIgnoreCase("gml:x")) { c.x = Double.parseDouble(grandChild.getChildNodes().item(0).getNodeValue().trim()); } else if (grandChildName.equalsIgnoreCase("gml:y")) { c.y = Double.parseDouble(grandChild.getChildNodes().item(0).getNodeValue().trim()); } else if (grandChildName.equalsIgnoreCase("gml:z")) { c.z = Double.parseDouble(grandChild.getChildNodes().item(0).getNodeValue().trim()); } } clist.add(c); } if (childName.equalsIgnoreCase("gml:coordinates")) { LOGGER.finer("coordinates " + child.getFirstChild().getNodeValue()); NodeList grandKids = child.getChildNodes(); for (int k = 0; k < grandKids.getLength(); k++) { Node grandKid = grandKids.item(k); if (grandKid.getNodeValue() == null) { continue; } if (grandKid.getNodeValue().trim().length() == 0) { continue; } String outer = grandKid.getNodeValue().trim(); //handle newline and tab whitespace along with space StringTokenizer ost = new StringTokenizer(outer, " \n\t"); while (ost.hasMoreTokens()) { String internal = ost.nextToken(); StringTokenizer ist = new StringTokenizer(internal, ","); double xCoord = Double.parseDouble(ist.nextToken()); double yCoord = Double.parseDouble(ist.nextToken()); double zCoord = Double.NaN; if (ist.hasMoreTokens()) { zCoord = Double.parseDouble(ist.nextToken()); } clist.add(new Coordinate(xCoord, yCoord, zCoord)); } } } } return clist; } }