/**
   * Verify that NetworkLink's generated by the reflector do not include a BBOX parameter, since
   * that would override the BBOX provided by Google Earth.
   *
   * @see <a href="https://osgeo-org.atlassian.net/browse/GEOS-2185">GEOS-2185</a>
   */
  @Test
  public void testNoBBOXInHREF() throws Exception {
    final String layerName = MockData.BASIC_POLYGONS.getLocalPart();
    final XpathEngine xpath = XMLUnit.newXpathEngine();
    String requestURL = "wms/kml?mode=refresh&layers=" + layerName;
    Document dom = getAsDOM(requestURL);
    print(dom);
    assertXpathEvaluatesTo("1", "count(kml:kml/kml:Document)", dom);
    assertXpathEvaluatesTo("1", "count(kml:kml/kml:Document/kml:NetworkLink)", dom);
    assertXpathEvaluatesTo("1", "count(kml:kml/kml:Document/kml:LookAt)", dom);

    assertXpathEvaluatesTo(layerName, "kml:kml/kml:Document/kml:NetworkLink[1]/kml:name", dom);
    assertXpathEvaluatesTo("1", "kml:kml/kml:Document/kml:NetworkLink[1]/kml:open", dom);
    assertXpathEvaluatesTo("1", "kml:kml/kml:Document/kml:NetworkLink[1]/kml:visibility", dom);

    assertXpathEvaluatesTo(
        "onStop", "kml:kml/kml:Document/kml:NetworkLink[1]/kml:Url/kml:viewRefreshMode", dom);
    assertXpathEvaluatesTo(
        "1.0", "kml:kml/kml:Document/kml:NetworkLink[1]/kml:Url/kml:viewRefreshTime", dom);
    assertXpathEvaluatesTo(
        "1.0", "kml:kml/kml:Document/kml:NetworkLink[1]/kml:Url/kml:viewBoundScale", dom);
    Map<String, Object> expectedKVP =
        KvpUtils.parseQueryString(
            "http://localhost:80/geoserver/wms?format_options=MODE%3Arefresh%3Bautofit%3Atrue%3BKMPLACEMARK%3Afalse%3BKMATTR%3Atrue%3BKMSCORE%3A40%3BSUPEROVERLAY%3Afalse&service=wms&srs=EPSG%3A4326&width=2048&styles=BasicPolygons&height=2048&transparent=false&request=GetMap&layers=cite%3ABasicPolygons&format=application%2Fvnd.google-earth.kml+xml&version=1.1.1");
    Map<String, Object> resultedKVP =
        KvpUtils.parseQueryString(
            xpath.evaluate("kml:kml/kml:Document/kml:NetworkLink[1]/kml:Url/kml:href", dom));

    assertMapsEqual(expectedKVP, resultedKVP);

    String href = xpath.evaluate("kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href", dom);
    Pattern badPattern = Pattern.compile("&bbox=", Pattern.CASE_INSENSITIVE);
    assertFalse(badPattern.matcher(href).matches());
  }
  /** @see {@link KMLReflector#organizeFormatOptionsParams(Map, Map)} */
  @Test
  public void testKmlFormatOptionsAsKVP() throws Exception {
    final String layerName =
        MockData.BASIC_POLYGONS.getPrefix() + ":" + MockData.BASIC_POLYGONS.getLocalPart();

    final String baseUrl = "wms/kml?layers=" + layerName + "&styles=&mode=superoverlay";
    final String requestUrl =
        baseUrl + "&kmltitle=myCustomLayerTitle&kmscore=10&legend=true&kmattr=true";
    Document dom = getAsDOM(requestUrl);
    XpathEngine xpath = XMLUnit.newXpathEngine();

    // print(dom);
    // all the kvp parameters (which should be set as format_options now are correctly parsed)
    String result = xpath.evaluate("//kml:NetworkLink/kml:Link/kml:href", dom);
    Map<String, Object> kvp = KvpUtils.parseQueryString(result);
    List<String> formatOptions = Arrays.asList(((String) kvp.get("format_options")).split(";"));
    assertEquals(9, formatOptions.size());
    assertTrue(formatOptions.contains("LEGEND:true"));
    assertTrue(formatOptions.contains("SUPEROVERLAY:true"));
    assertTrue(formatOptions.contains("AUTOFIT:true"));
    assertTrue(formatOptions.contains("KMPLACEMARK:false"));
    assertTrue(formatOptions.contains("OVERLAYMODE:auto"));
    assertTrue(formatOptions.contains("KMSCORE:10"));
    assertTrue(formatOptions.contains("MODE:superoverlay"));
    assertTrue(formatOptions.contains("KMATTR:true"));
    assertTrue(formatOptions.contains("KMLTITLE:myCustomLayerTitle"));
  }
  @Test
  public void testWmsRepeatedLayerWithNonStandardStyleAndCqlFiler() throws Exception {
    final String layerName =
        MockData.BASIC_POLYGONS.getPrefix() + ":" + MockData.BASIC_POLYGONS.getLocalPart();
    final String titleName = MockData.BASIC_POLYGONS.getLocalPart();
    final String abstractValue = "abstract about " + titleName;

    String requestUrl =
        "wms/kml?mode=refresh&layers="
            + layerName
            + ","
            + layerName
            + "&styles=Default,Default&cql_filter=att1<10;att1>1000";
    Document dom = getAsDOM(requestUrl);

    assertEquals("kml", dom.getDocumentElement().getLocalName());

    assertXpathEvaluatesTo("2", "count(kml:kml/kml:Document/kml:NetworkLink)", dom);
    assertXpathEvaluatesTo(titleName, "kml:kml/kml:Document/kml:NetworkLink[1]/kml:name", dom);
    assertXpathEvaluatesTo(
        abstractValue, "kml:kml/kml:Document/kml:NetworkLink[1]/kml:description", dom);
    assertXpathEvaluatesTo(titleName, "kml:kml/kml:Document/kml:NetworkLink[2]/kml:name", dom);
    assertXpathEvaluatesTo(
        abstractValue, "kml:kml/kml:Document/kml:NetworkLink[2]/kml:description", dom);

    XpathEngine xpath = XMLUnit.newXpathEngine();

    String url1 = xpath.evaluate("/kml:kml/kml:Document/kml:NetworkLink[1]/kml:Url/kml:href", dom);
    String url2 = xpath.evaluate("/kml:kml/kml:Document/kml:NetworkLink[2]/kml:Url/kml:href", dom);

    assertNotNull(url1);
    assertNotNull(url2);

    Map<String, Object> kvp1 = KvpUtils.parseQueryString(url1);
    Map<String, Object> kvp2 = KvpUtils.parseQueryString(url2);

    assertEquals(layerName, kvp1.get("layers"));
    assertEquals(layerName, kvp2.get("layers"));

    assertEquals("Default", kvp1.get("styles"));
    assertEquals("Default", kvp2.get("styles"));

    assertEquals("att1<10", kvp1.get("cql_filter"));
    assertEquals("att1>1000", kvp2.get("cql_filter"));
  }
  /** Tokenizes the value and delegates to {@link #parseToken(String)} to parse each token. */
  public final Object parse(String value) throws Exception {
    List tokens = KvpUtils.readFlat(value, delimiter);
    List parsed = new ArrayList(tokens.size());
    final int size = tokens.size();
    for (int i = 0; i < size; i++) {
      String token = (String) tokens.get(i);
      parsed.add(parseToken(token));
    }

    return parse(parsed);
  }
  public Object parse(String value) throws Exception {
    // create the parser
    final Configuration configuration = getParserConfiguration();
    final Parser parser = new Parser(configuration);
    parser.setEntityResolver(entityResolverProvider.getEntityResolver());

    // seperate the individual filter strings
    List unparsed = KvpUtils.readFlat(value, KvpUtils.OUTER_DELIMETER);
    List filters = new ArrayList();

    Iterator i = unparsed.listIterator();

    while (i.hasNext()) {
      String string = (String) i.next();
      if ("".equals(string.trim())) {
        filters.add(Filter.INCLUDE);
      } else {
        InputStream input = new ByteArrayInputStream(string.getBytes());

        try {
          Filter filter = (Filter) parser.parse(input);

          if (filter == null) {
            throw new NullPointerException();
          }

          filters.add(filter);
        } catch (Exception e) {
          // parsing failed, fall back to old parser
          String msg = "Unable to parse filter: " + string;
          LOGGER.log(Level.WARNING, msg, e);

          Filter filter = parseXMLFilterWithOldParser(new StringReader(string));

          if (filter != null) {
            filters.add(filter);
          }
        }
      }
    }

    return filters;
  }