gov.nasa.worldwind.ogc.kml.impl.KMLBalloonTextDecoder Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.ogc.kml.impl;
import gov.nasa.worldwind.ogc.kml.*;
import gov.nasa.worldwind.util.*;
import java.util.*;
import java.util.regex.*;
/**
* Text decoder that performs entity substitution for KML description balloons. This class is thread safe.
*
* @author pabercrombie
* @version $Id: KMLBalloonTextDecoder.java 1944 2014-04-18 16:50:49Z tgaskins $
*/
public class KMLBalloonTextDecoder extends BasicTextDecoder
{
/**
* True if there are entities in the balloon text which may refer to unresolved schema. False if all entities have
* been resolved, or are known to be unresolvable (because the data they refer to does not exist).
*/
protected boolean isUnresolved;
/**
* Keep a cache of entities that have been resolved so that we don't have to re-resolve them every time the decoded
* text is requested.
*/
protected Map entityCache = new HashMap();
/** Feature to use as context for entity replacements. */
protected KMLAbstractFeature feature;
/**
* Create a decoder to generate balloon text for a feature. The feature becomes the context of the entity
* replacements (for example "$[name]" will be replaced with the feature's name.
*
* @param feature Feature that is the context of entity replacements.
*/
public KMLBalloonTextDecoder(KMLAbstractFeature feature)
{
if (feature == null)
{
String message = Logging.getMessage("nullValue.FeatureIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.feature = feature;
}
/**
* Get the balloon text after entity substitutions (for example, $[name], $[description], etc) have been made. See
* the KML specification for details on entity replacement. If some entities could not be resolved because they
* refer to unresolved schema, the decoder will try to decode the string again the next time this method is called.
*
* @return String after replacements have been made. Returns null if the input text is null.
*/
@Override
public synchronized String getDecodedText()
{
if (this.decodedText == null)
this.lastUpdateTime = System.currentTimeMillis();
if (this.decodedText == null || this.isUnresolved)
this.decodedText = this.decode(this.text);
// If the text was fully decoded we can release our reference to the original text.
if (!this.isUnresolved)
this.text = null;
return this.decodedText;
}
/** Perform entity substitution. */
@Override
protected String decode(String textToDecode)
{
if (textToDecode == null)
return null;
this.isUnresolved = false;
Pattern p = Pattern.compile("\\$\\[(.*?)\\]");
Matcher m = p.matcher(textToDecode);
StringBuffer sb = new StringBuffer();
while (m.find())
{
String entity = m.group(1);
// Check the entity cache to see if we've already resolved this entity.
String r = this.entityCache.get(entity);
if (r == null)
{
// Try to resolve the entity
r = this.resolveEntityReference(entity);
if (r != null)
{
// Save the resolved entity in the cache, and set the last update time. Resolving this entity
// has changed the decoded string.
this.entityCache.put(entity, r);
this.lastUpdateTime = System.currentTimeMillis();
}
}
m.appendReplacement(sb, Matcher.quoteReplacement(r != null ? r : ""));
}
m.appendTail(sb);
return sb.toString();
}
/** {@inheritDoc} */
@Override
public synchronized void setText(String input)
{
super.setText(input);
this.entityCache.clear();
}
/**
* Resolve an entity reference. The pattern can be in one of these forms:
*
* FeatureField
* DataField
* DataField/displayName
* SchemaName/SchemaField
* SchemaName/SchemaField/displayName
*
* See the KML spec for details.
*
* @param pattern Pattern of entity to resolve.
*
* @return Return the replacement string for the entity, or null if no replacement can be found.
*/
protected String resolveEntityReference(String pattern)
{
KMLAbstractFeature feature = this.getFeature();
if ("geDirections".equals(pattern))
return this.getGeDirectionsText();
// First look for a field in the Feature
Object replacement = feature.getField(pattern);
if (replacement instanceof KMLAbstractObject)
return ((KMLAbstractObject) replacement).getCharacters();
else if (replacement != null)
return replacement.toString();
// Before searching data fields, split the pattern into name, field, and display name components
String name; // Name of data element or schema
String field = ""; // Schema field (does not apply to data fields)
boolean isDisplayName; // Indicates that the replacement is the display name of the data, not the data itself
isDisplayName = pattern.endsWith("/displayName");
String[] parts = pattern.split("/");
name = parts[0]; // Name is always the first field, or the whole pattern if there were no slashes
if ((parts.length == 2 && !isDisplayName) || (parts.length > 2)) // name/field or name/field/displayName
{
field = parts[1];
}
// Next look for a data field in the Feature's extended data
KMLExtendedData extendedData = feature.getExtendedData();
if (extendedData != null)
{
// Search through untyped data first
for (KMLData data : extendedData.getData())
{
if (name.equals(data.getName()))
{
if (isDisplayName)
{
if (!WWUtil.isEmpty(data.getDisplayName()))
return data.getDisplayName();
else
return data.getName();
}
else
{
return data.getValue();
}
}
}
// Search through typed schema data fields
boolean schemaUnresolved = false;
List schemaDataList = extendedData.getSchemaData();
for (KMLSchemaData schemaData : schemaDataList)
{
// Try to resolve the schema. The schema may not be available immediately
final String url = schemaData.getSchemaUrl();
KMLSchema schema = (KMLSchema) feature.getRoot().resolveReference(url);
if (schema != null && name.equals(schema.getName()))
{
// We found the schema. Now we are looking for either the display name or the value of
// one of the fields.
if (isDisplayName)
{
// Search schema fields
for (KMLSimpleField simpleField : schema.getSimpleFields())
{
if (field.equals(simpleField.getName()))
{
return simpleField.getDisplayName();
}
}
}
else
{
// Search data fields
for (KMLSimpleData simpleData : schemaData.getSimpleData())
{
if (field.equals(simpleData.getName()))
{
return simpleData.getCharacters();
}
}
}
}
else if (schema == null)
{
schemaUnresolved = true;
}
}
// Set the balloon text to unresolved if we still haven't found a match, and there is at least one
// unresolved schema, and the pattern could refer to a schema
if (schemaUnresolved && !WWUtil.isEmpty(name) && !WWUtil.isEmpty(field))
{
this.isUnresolved = true;
}
}
return null; // No match found
}
/**
* Get the feature used as context for resolving entity references.
*
* @return The context feature.
*/
public KMLAbstractFeature getFeature()
{
return this.feature;
}
/**
* Get the text used to replace the $[geDirections] entity. This implementation returns an empty string. Subclasses
* can override this method to provide directions text.
*
* @return Text to replace the $[geDirections] entity.
*/
protected String getGeDirectionsText()
{
return "";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy