/*
* Copyright 2006, United States Government as represented by the Administrator
* for the National Aeronautics and Space Administration. No copyright is
* claimed in the United States under Title 17, U.S. Code. All Other Rights
* Reserved.
*/
package gov.nasa.ial.mde.describer;
import gov.nasa.ial.mde.properties.MdeSettings;
import gov.nasa.ial.mde.solver.Solution;
import gov.nasa.ial.mde.solver.SolvedGraph;
import gov.nasa.ial.mde.solver.Solver;
import gov.nasa.ial.mde.util.LocalResourceResolver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.text.BreakIterator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.StringTokenizer;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* <code>Describer</code> works with a MDE <code>Solver</code> object to
* create natural-language text descriptions of graphs. Describer provides
* methods for setting different <em>description modes</em> and for specifying
* the <em>output format</em>.
* <p>
* <code>Describer</code> supports two default description modes - VISUAL or
* MATH. The VISUAL mode provides qualitative descriptions without much
* mathematical jargon. The MATH mode provides a description of the mathematical
* solution. A third has be added, called STANDARD, which incorperates descriptions
* suited for the Georgia Performance Standards.
* <p>
* <code>Describer</code> also lets you define new description modes by
* specifying a new mode name and an XSLT file for transforming the MDE (XML)
* solution to a textual description.
* <p>
* Two output formats are available for textual descriptions - TEXT_OUTPUT or
* HTML_OUTPUT. Select the HTML format if you will be displaying MDE
* descriptions to a browser or other display component that supports HTML.
* <p>
* <code>Describer</code> uses a <code>MdeSettings</code> object to get
* and set description mode changes.
* <p>
* <a href="http://prime.jsc.nasa.gov/MDE">Math Description Engine Programmers
* Guide </a>
*
* @author Dat Truong
* @author Terry Hodgson
* @version 1.0
*/
public class Describer {
/**
* HTML output indicator
*/
public static final String HTML_OUTPUT = "html";
/**
* text output indicator
*/
public static final String TEXT_OUTPUT = "text";
/**
* XML output format is not implemented yet.
*/
public static final String XML_OUTPUT = "xml";
/**
* visual description mode indicator
*/
public static final String VISUAL = "visual";
/**
* math description mode indicator
*/
public static final String MATH = "math";
/**
* educational standards description mode indicator
*/
public static final String STANDARDS = "standards";
/**
* MDE <code>Solver</code> object which provides the graph data to be
* described textually.
*/
private Solver solver;
/**
* Comment for <code>tFactory</code>
*/
private TransformerFactory tFactory;
/**
* Table of initialized XSLT transformers. One per description mode.
*/
private Hashtable<String, Transformer> transformers;
/**
* Comment for <code>currentDescriptionMode</code>
*/
private String currentDescriptionMode;
/**
* Comment for <code>currentTransformer</code>
*/
private Transformer currentTransformer = null;
/**
* Comment for <code>currentOutputFormat</code>
*/
private String currentOutputFormat;
/**
* Number of words per line for text format
*/
private int wordsPerLine;
// Default constructor not allowed.
@SuppressWarnings("unused")
private Describer() {
throw new RuntimeException("Default constructor not allowed.");
}
/**
* Construct a Describer object with the given <code>Solver</code> object.
* An instance of MdeSettings will be created internally. Use default XSLT
* template files.
*
* @param solver the solver to use with this describer.
*/
public Describer(Solver solver) {
this(solver, new MdeSettings());
}
/**
* Construct a Describer object with the given <code>Solver</code> object
* and <code>MdeSettings</code> object. Use default XSLT template files.
*
* @param solver the solver to use with this desriber.
* @param settings the MDE settings.
*/
public Describer(Solver solver, MdeSettings settings) {
//set defaults
this.solver = solver;
this.currentOutputFormat = TEXT_OUTPUT;
this.wordsPerLine = 15;
configureTranformerFactory();
//TODO: The template/mode defaults should probably be set in MdeSettings
transformers = new Hashtable<String, Transformer>();
addDescriptionMode("visual", "mdeApplyVisual1.xsl");
addDescriptionMode("math", "mdeApplyMath1.xsl");
addDescriptionMode("standards", "mdeApplyStandards1.xsl");
this.currentDescriptionMode = settings.getDescriptionMode();
this.currentTransformer = transformers.get(currentDescriptionMode);
}
/**
* Return the MDE description for this equation using the current
* description mode.
*
* @param equation equation to be described
* @return the MDE description for this equation using the current
* description mode.
*/
public String getDescription(String equation) {
// Use current description mode
String result = null;
Solution[] sol = solver.get(equation);
if ((sol != null) && (sol.length > 0)) {
if (MdeSettings.DEBUG) {
System.out.println(getClass().getName() + ".getDescription() solution is not null");
}
StringBuffer sb = new StringBuffer(128);
sb.append("\n<MDE>");
SolvedGraph sg = sol[0].getFeatures();
if (sg != null) {
String sgxml = sg.getXMLString();
sb.append(sgxml);
}
sb.append("\n</MDE>");
result = transformXML(sb.toString());
}
return result;
}
/**
* Return the MDE description for this equation using the given description
* mode.
*
* @param equation the equation as a string.
* @param mode either "visual" or "math".
* @return the MDE description for this equation using the given description mode.
*/
public String getDescription(String equation, String mode) {
//If mode has changed, get appropriate Transformer
if (!currentDescriptionMode.equals(mode)) {
setCurrentDescriptionMode(mode);
}
return getDescription(equation);
}
/**
* Return MDE descriptions for all items in the Solver object's solution
* list. Use the given description mode.
*
* @param mode either "visual" or "math".
* @return MDE descriptions for all items in the Solver object's solution list.
*/
public String getDescriptions(String mode) {
//If mode has changed, get appropriate Transformer
if (!currentDescriptionMode.equals(mode)) {
setCurrentDescriptionMode(mode);
}
return getDescriptions();
}
/**
* Return MDE descriptions for all items in the Solver object's solution
* list. Use the current description mode.
*
* @return MDE descriptions for all items in the Solver object's solution list.
*/
public String getDescriptions() {
String xmlToTransform;
String result;
xmlToTransform = getFeatureXML(); // magic
// System.out.println(xmlToTransform);
result = transformXML(xmlToTransform);
return result;
}
/**
* Change the output format to the requested type for each description mode
* transformer.
*
* @param outputFormat
* the desired output format - text or html
*/
public void setOutputFormat(String outputFormat) {
//TODO: handle invalid outputFormat
boolean notXML = (outputFormat.equals(TEXT_OUTPUT) || outputFormat.equals(HTML_OUTPUT));
this.currentOutputFormat = outputFormat;
if (MdeSettings.DEBUG) {
System.out.println(getClass().getName() + ".setOutputFormat() " + outputFormat);
}
Transformer tf;
Enumeration<Transformer> en = transformers.elements();
while (en.hasMoreElements()) {
tf = en.nextElement();
if (notXML) {
omitXMLDeclaration(tf, true);
} else {
omitXMLDeclaration(tf, false);
}
tf.setOutputProperty(OutputKeys.METHOD, outputFormat);
}
}
/**
* Return the MDE XML String for all items in the Solver object's solution
* list which have a showGraph=true.
*
* @return The feature in XML form.
*/
private String getFeatureXML() {
//Build the XML stream:
StringBuffer b = new StringBuffer(128);
b.append("\n<MDE>");
// Get the XML block from the features.
Solution solution;
SolvedGraph features;
for (@SuppressWarnings("rawtypes")
Iterator iter = solver.getSolutionIterator(); iter.hasNext();) {
solution = (Solution) iter.next();
// Only describe the solutions that are graphed.
if (solution.isShowGraph()) {
features = solution.getFeatures();
if (features != null) {
b.append(features.getXMLString());
}
}
}
b.append("\n</MDE>");
// if (MdeConstants.DEBUG) {
// System.out.println(getClass().getName()+".getFeatureXML()"+b.toString());
// }
return b.toString();
}
/**
* Not yet supported. Lets you specify your own text description mode with
* corresponding XSLT description templates file.
*
* @param modeName either "visual" or "math".
* @param xslFilename the name of the XSL file.
*/
public void addDescriptionMode(String modeName, String xslFilename) {
//TODO: handle other than default path to xsl
//TODO: check for/handle duplicate mode names...
//Map transformer to description mode
Transformer tf = getTransformer(xslFilename);
transformers.put(modeName, tf);
}
/**
* Change the description mode to the specified value.
*
* @param mode either "visual" or "math" or "standards".
*/
public void setCurrentDescriptionMode(String mode) {
//TODO: We could handle an invalid mode condition better than we do.
Transformer tf = transformers.get(mode);
if (tf == null) {
if (MdeSettings.DEBUG) {
System.out.println("Invalid description mode. Previously set mode will be used.");
}
} else {
currentTransformer = tf;
currentDescriptionMode = mode;
}
}
/**
* Return the current description mode value.
*
* @return the current description mode value.
*/
public String getCurrentDescriptionMode() {
return currentDescriptionMode;
}
private void configureTranformerFactory() throws TransformerFactoryConfigurationError {
tFactory = TransformerFactory.newInstance();
// Use a resource resolver to find the resources in the resources
// "/resources/" path of the Jar file. DDexter 1/19/2004
tFactory.setURIResolver(new LocalResourceResolver(MdeSettings.RESOURCES_PATH));
}
private Transformer getTransformer(String xslFilename) {
Transformer tf = null;
try {
if (MdeSettings.DEBUG) {
System.out.println(getClass().getName() + ".getTransformer() Get resource \"" + xslFilename
+ "\"");
}
LocalResourceResolver resolver = (LocalResourceResolver) tFactory.getURIResolver();
tf = tFactory.newTransformer(resolver.resolve(xslFilename, null));
} catch (Exception e) {
if (MdeSettings.DEBUG) {
System.out.println("Failed to initialize Transformer, styleSheet is !" + xslFilename + "!");
}
System.out.println(e);
e.printStackTrace(System.out);
}
return tf;
}
/**
* Specify whether the (XSLT-transformed XML) text description output should
* include an XML declaration.
*
* @param tf - the Transformer of
* @param flag true to omit XML declaration, flase to include it.
*/
private void omitXMLDeclaration(Transformer tf, boolean flag) {
tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, flag ? "yes" : "no");
if (MdeSettings.DEBUG) {
System.out.println(getClass().getName() + ".omitXMLDeclaration() OMIT_XML_DEC: "
+ tf.getOutputProperty(OutputKeys.OMIT_XML_DECLARATION));
}
}
private String transformXML(String xmlData) {
String finalResult = "";
ByteArrayOutputStream result = new ByteArrayOutputStream();
try {
currentTransformer.transform(new StreamSource(new ByteArrayInputStream(xmlData.getBytes())),
new StreamResult(result));
String resultStr = result.toString();
if(MdeSettings.DEBUG)
{
System.out.println("transformed Str = "+resultStr);
}
if (currentOutputFormat.equals(TEXT_OUTPUT)) {
finalResult = cleanUpText(resultStr, 40);
} else {
finalResult = resultStr;
}
} catch (Exception e) {
System.out.println("Failed to transform XML string: !" + xmlData + "!");
System.out.println(e);
e.printStackTrace();
}
return finalResult;
}
private String cleanUpText(String result1, int textLineLength) {
int i = 0;
//If it's HTML, we don't want the tags escaped. HTML viewers *should*
// properly convert < and > (less than and greater than math
// symbols)
while ((i = result1.indexOf("<")) > -1) {
result1 = result1.substring(0, i) + "<" + result1.substring(i + 4);
}
while ((i = result1.indexOf(">")) > -1) {
result1 = result1.substring(0, i) + ">" + result1.substring(i + 4);
}
StringTokenizer bt = new StringTokenizer(result1, "\n");
StringBuffer bb = new StringBuffer(result1.length());
while (bt.hasMoreTokens()) {
//bb.append(" ").append(bt.nextToken().trim());
bb.append(bt.nextToken().trim()).append(' ');
}
String result2 = bb.toString();
//This doesn't seem to work like it should! It's breaking a line after
// every word.
BreakIterator lines = BreakIterator.getLineInstance(Locale.US);
lines.setText(result2);
StringBuffer result3 = new StringBuffer(result2.length());
//So let's kludge it:
//int wordCount = 0;
int start = lines.first();
for (int end = lines.next(); end != BreakIterator.DONE; start = end, end = lines.next()) {
//wordCount++;
result3.append(result2.substring(start, end));
/* if (wordCount == wordsPerLine) {
result3.append("\n");
wordCount = 0;
}*/
}
return result3.toString();
}
/**
* Return the number of words per line for TEXT descriptions. The default
* value is 15 words per line.
*
* @return the words per line in text description outputs.
*/
public int getWordsPerLine() {
return wordsPerLine;
}
/**
* Set the number of words per line for TEXT descriptions. The default value
* is 15 words per line.
*
* @param wordsPerLine
* the wordsPerLine to set for text output
*/
public void setWordsPerLine(int wordsPerLine) {
this.wordsPerLine = wordsPerLine;
}
} // end class Describer