xidyn/src/xid/Presenter.java
2007-01-23 18:43:32 +01:00

1087 lines
23 KiB
Java

package xid;
import java.util.*;
import java.io.*;
import javax.xml.parsers.*;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import org.xml.sax.*;
import org.w3c.dom.*;
/**
*
*/
public class Presenter
{
static protected org.apache.log4j.Logger log;
static
{
log = org.apache.log4j.Logger.getLogger (Presenter.class.getName ());
}
protected String webappPath;
protected String sourceFileName;
protected long sourceFileTime;
protected Document doc;
/*
*
*/
public Presenter ()
{
this.webappPath = null;
this.sourceFileName = null;
this.sourceFileTime = 0;
this.doc = null;
}
/*
*
*/
public Presenter (String webappPath, String fileName)
{
this.webappPath = webappPath;
this.sourceFileName = fileName;
this.sourceFileTime = 0;
this.doc = null;
}
/*
*
*/
public String getWebappPath ()
{
String result;
result = this.webappPath;
//
return (result);
}
/*
*
*/
public void setSource (String fileName)
{
this.sourceFileName = fileName;
this.sourceFileTime = 0;
this.doc = null;
}
/*
*
*/
public String getSource ()
{
String result;
result = this.sourceFileName;
//
return (result);
}
/*
*
*/
public StringBuffer doXid (TagsDataById datas, StringBuffer errorOutput)
{
StringBuffer result;
String sourceFilePath = this.webappPath + File.separator + this.sourceFileName;
// Get the good tree.
File source = new File (sourceFilePath);
if (source == null)
{
String errorMessage = "source file not defined";
errorOutput.append (errorMessage);
log.error (errorMessage);
result = null;
}
else if (!source.exists ())
{
String errorMessage = "source file defined but not found (" + sourceFilePath + ")";
errorOutput.append (errorMessage);
log.error (errorMessage);
result = null;
}
else if ((this.doc == null) ||
(this.sourceFileTime != source.lastModified ()))
{
this.sourceFileTime = source.lastModified ();
this.doc = Presenter.fileToTree (sourceFilePath, errorOutput);
if (this.doc != null)
{
Presenter.addMetaTag (doc, "generator", "XID 0.0");
}
}
// Build the web page.
result = Presenter.doXid (doc, datas, this.webappPath, errorOutput);
//
return (result);
}
/*
* Xid a file without data.
*/
static public StringBuffer doXid (String fileName, String webappPath, StringBuffer errorOutput)
{
StringBuffer result;
Document doc = Presenter.fileToTree (fileName, errorOutput);
if (doc == null)
{
result = null;
}
else
{
Presenter.addMetaTag (doc, "generator", "XID 0.0");
result = Presenter.doXid (doc, null, webappPath, errorOutput);
}
//
return (result);
}
/*
* Xid a string with html in.
*/
static public StringBuffer doXid (String html, TagsDataById datas, String webappPath, StringBuffer errorOutput)
{
StringBuffer result;
String htmlSource;
if ((html.startsWith ("<!DOCTYPE")) ||
(html.startsWith ("<!doctype")) ||
(html.startsWith ("<html>")) ||
(html.startsWith ("<HTML>")))
{
htmlSource = html;
}
else
{
htmlSource = "<html><head></head><body>\n" + html + "</body></html>";
}
Document doc = null;
// StringBufferInputStream is deprecated so we use another solution.
// (see http://www.developpez.net/forums/archive/index.php/t-14101.html).
doc = buildTree (new ByteArrayInputStream (htmlSource.getBytes ()), errorOutput);
StringBuffer htmlTarget;
htmlTarget = Presenter.process (doc, datas, webappPath, errorOutput);
if (htmlTarget == null)
{
result = null;
}
else if ((html.startsWith ("<!DOCTYPE")) ||
(html.startsWith ("<!doctype")) ||
(html.startsWith ("<html>")) ||
(html.startsWith ("<HTML>")))
{
result = htmlTarget;
}
else
{
String bodyContent = extractBodyContent (htmlTarget);
if (bodyContent == null)
{
result = null;
}
else
{
result = new StringBuffer (bodyContent);
}
}
//
return (result);
}
/*
* Xid a file with data.
*/
static public StringBuffer doXid (Document doc, TagsDataById datas, String webappPath, StringBuffer errorOutput)
{
StringBuffer result;
result = Presenter.process (doc, datas, webappPath, errorOutput);
//
return (result);
}
/*
*
*/
static public String getClassAttributeValue (Node node)
{
String result;
NamedNodeMap attributes = node.getAttributes ();
if (attributes == null)
{
result = null;
}
else
{
Node nameAttribute = attributes.getNamedItem ("class");
if (nameAttribute == null)
{
result = null;
}
else
{
result = nameAttribute.getNodeValue ();
}
}
//
return (result);
}
/*
*
*/
static protected StringBuffer processChildren (Node node, TagsDataById datas, String webappPath, StringBuffer errorOutput)
{
StringBuffer result;
result = new StringBuffer ();
NodeList children = node.getChildNodes();
if (children == null)
{
result.append (" ");
}
else
{
int childrenCount = children.getLength ();
for (int i = 0; i < childrenCount; i++)
{
result.append (process (children.item(i), datas, webappPath, errorOutput));
}
}
//
return (result);
}
/**
* Includes another HSP file into the current page.
*
* @param node
* @param attrMap
* @param idAttr
*/
static protected StringBuffer processObjectTag (Node node, NamedNodeMap attrMap, Node idAttr, TagsDataById datas, String webappPath, StringBuffer errorOutput)
{
StringBuffer result;
result = new StringBuffer ();
// Find codetype.
String codetype;
if (attrMap == null)
{
codetype = null;
}
else if (attrMap.getNamedItem ("codetype") == null)
{
codetype = null;
}
else
{
codetype = attrMap.getNamedItem ("codetype").getNodeValue ();
}
// Check tag requirements.
if ((attrMap == null) ||
(codetype == null) ||
(!codetype.equals ("application/xid")) ||
(attrMap.getNamedItem ("data") == null))
{
// STU: do default action.
Presenter.processElementBasically (node, datas, webappPath, errorOutput);
}
else
{
log.debug ("object action");
result.append ("<!-- STARTING INCLUDE XID FILE " + attrMap.getNamedItem ("data") + " -->");
// Build the file name.
String htmlFileName = webappPath + attrMap.getNamedItem ("data").getNodeValue ();
// Load file in tree.
Document childDoc = null;
try
{
childDoc = fileToTree (htmlFileName, errorOutput);
}
catch (Exception ex)
{
result.append ("unable to build the file tree");
log.debug ("unable to build the file tree");
}
// Extract the 'body' section.
Node body = null;
try
{
NodeList nodes = childDoc.getElementsByTagName ("body");
if (nodes.getLength () == 0)
{
result.append ("no body tag in include html");
log.debug ("no body tag in include html");
}
else
{
body = nodes.item(0);
}
}
catch (Exception e)
{
result.append ("error getting child");
log.debug ("error getting child");
}
// Process the body child as part of the primary tree.
NodeList bodyChildren = body.getChildNodes ();
if (bodyChildren != null)
{
int childCount = bodyChildren.getLength ();
for (int childCounter = 0; childCounter < childCount; childCounter++)
{
result.append (process (bodyChildren.item (childCounter), datas, webappPath, errorOutput));
}
}
//
result.append ("<!-- ENDING INCLUDE XID FILE " + attrMap.getNamedItem ("data") + " -->");
}
log.debug ("end of object action");
//
return (result);
}
/**
* Processes a node that has dynamic content. Calls the appropriate code
* generator method, depending on the tag.
*
* @param node
* Current node.
* @param attrs
* The tag attributes.
* @param idAttr
* The ID.
*/
static protected StringBuffer processElementWithId (Node node,
NamedNodeMap attrs,
Node idAttr,
TagsDataById datas,
String webappPath,
StringBuffer errorOutput)
{
StringBuffer result;
result = new StringBuffer ();
String tag = node.getNodeName();
String idValue = idAttr.getNodeValue();
log.debug ("tag=" + tag);
// Get data of this id.
TagDataCore dataCore = datas.get (idAttr.getNodeValue ());
if (dataCore == null)
{
result.append (Presenter.processElementBasically (node, datas, webappPath, errorOutput));
}
else if (dataCore instanceof TagData)
{
TagData data = (TagData) dataCore;
String theClass;
if (data == null)
{
theClass = null;
}
else
{
theClass = data.getAttributes ().getAttribute ("class");
}
if ((theClass == null) ||
(!theClass.equals ("xid:nodisplay")))
{
// Open the tag.
result.append ("<");
result.append (node.getNodeName());
// Build attributes.
result.append (processAttributes (attrs, data));
if ((node.getChildNodes () == null) &&
((data == null) || data.display ().equals ("")))
{
// Close the tag.
result.append (" />");
}
else
{
result.append ('>');
// Insert data.
if ((data == null) ||
(data.display ().equals ("")))
{
result.append (processChildren (node, datas, webappPath, errorOutput));
}
else
{
result.append (data.display ());
}
// Close the tag.
result.append ("</");
result.append (node.getNodeName());
result.append ('>');
}
}
}
else if (dataCore instanceof TagsData)
{
TagsData data = (TagsData) dataCore;
}
else if (dataCore instanceof TagsDataById)
{
TagsDataById data = (TagsDataById) dataCore;
}
else
{
log.warn ("Unknow type of TagDataId");
}
//
log.debug ("Exit");
return (result);
}
/**
* Recursive method that processes a node and any child nodes.
*
*/
static protected StringBuffer process (Node node, TagsDataById datas, String webappPath, StringBuffer errorOutput)
{
log.debug ("Enter");
String TRANSITIONAL_DTD = "xhtml1-transitional.dtd";
String TRANSITIONAL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 "
+ "Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
StringBuffer result;
result = new StringBuffer ();
// Is there anything to do?
if (node != null)
{
log.debug ("nodeName=" + node.getNodeName ());
// Find the name attribute value.
String name;
name = getClassAttributeValue (node);
if ((name == null) ||
((name != null) &&
(!name.equals ("xid:nodisplay"))))
{
int type = node.getNodeType();
switch (type)
{
case Node.DOCUMENT_NODE:
{
log.debug ("case Node.DOCUMENT_NODE");
DocumentType dt = ((Document) node).getDoctype();
if (dt != null)
{
String publicId = dt.getPublicId();
String systemId = dt.getSystemId();
if (systemId.equals(TRANSITIONAL_DTD))
{
result.append(TRANSITIONAL_DOCTYPE);
}
// Log.write(Log.TRACE,"publicId = " + publicId);
// Log.write(Log.TRACE,"systemId = " + systemId);
}
result.append (Presenter.process (((Document) node).getDocumentElement(), datas, webappPath, errorOutput));
break;
}
case Node.ELEMENT_NODE:
{
log.debug ("case Node.ELEMENT_NODE");
NamedNodeMap attrs = node.getAttributes ();
Node idAttr = attrs.getNamedItem ("id");
if (idAttr != null)
{
result.append (Presenter.processElementWithId (node, attrs, idAttr, datas, webappPath, errorOutput));
}
else
{
result.append (Presenter.processElementBasically (node, datas, webappPath, errorOutput));
}
break;
}
// handle entity reference nodes
case Node.ENTITY_REFERENCE_NODE:
{
log.debug ("case Node.ENTITY_REFERENCE_NODE");
result.append ('&');
result.append (node.getNodeName());
result.append (';');
break;
}
// print cdata sections
case Node.CDATA_SECTION_NODE:
{
log.debug ("case Node.CDATA_SECTION_NODE");
result.append ("<![CDATA[");
result.append (node.getNodeValue());
result.append ("]]>");
break;
}
// print text
case Node.TEXT_NODE:
{
log.debug ("case Node.TEXTE_NODE");
result.append (restoreEntities (new StringBuffer(node.getNodeValue())));
break;
}
// print processing instruction
case Node.PROCESSING_INSTRUCTION_NODE:
{
log.debug ("Node.PROCESSING_INSTRUCTION_NODE");
result.append ("<?");
result.append (node.getNodeName());
String data = node.getNodeValue();
if ((data != null) && (data.length () > 0))
{
result.append (' ');
result.append (data);
}
result.append ("?>");
break;
}
}
}
}
//
//log.info ("result=" + result);
log.debug ("Exit");
return (result);
}
/*
*/
static StringBuffer processElementBasically (Node node, TagsDataById datas, String webappPath, StringBuffer errorOutput)
{
StringBuffer result;
result = new StringBuffer ();
// Open the tag.
result.append ('<');
result.append (node.getNodeName());
// Build the tag attributes.
NamedNodeMap attrs = node.getAttributes ();
if (attrs != null)
{
for (int i = 0; i < attrs.getLength(); i++)
{
Attr attr = (Attr) attrs.item(i);
result.append (' ');
result.append (attr.getNodeName());
result.append ("=\"");
result.append (restoreEntities(new StringBuffer(attr.getNodeValue())));
result.append ("\"");
}
//
NodeList children = node.getChildNodes();
if (children == null)
{
result.append(" />");
}
else
{
int childrenCount = children.getLength ();
if (childrenCount == 0)
{
result.append(" />");
}
else
{
result.append('>');
for (int i = 0; i < childrenCount; i++)
{
result.append (process (children.item(i), datas, webappPath, errorOutput));
}
result.append("</");
result.append(node.getNodeName());
result.append('>');
}
}
}
//
return (result);
}
/*
*
*/
static protected Document buildTree (InputStream source, StringBuffer errorOutput)
{
Document result;
try
{
// Create a DocumentBuilderFactory and configure it.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
// Set various configuration options.
dbf.setValidating (true);
dbf.setIgnoringComments (true);
dbf.setIgnoringElementContentWhitespace (false);
dbf.setCoalescing (false);
// Keep entity references as they are.
dbf.setExpandEntityReferences(false);
// Create a DocumentBuilder that satisfies the constraints
// specified by the DocumentBuilderFactory.
DocumentBuilder db = dbf.newDocumentBuilder ();
ParserErrorHandler errorHandler;
errorHandler = new ParserErrorHandler();
// Set the error handler.
db.setErrorHandler (errorHandler);
Schema schema = db.getSchema ();
log.debug ("schema=" + schema);
// Parse the input file.
result = db.parse (source);
if (errorHandler.hasError ())
{
errorOutput.append (errorHandler.toString ());
}
}
catch (ParserConfigurationException exception)
{
String errorMessage = "Parser configuration exception: " + exception.getMessage ();
errorOutput.append (errorMessage);
log.error (errorMessage);
result = null;
}
catch (SAXException exception)
{
String errorMessage = "Error during SAX parsing: " + exception.getMessage ();
errorOutput.append (errorMessage);
log.error (errorMessage);
result = null;
}
catch (IOException exception)
{
String errorMessage = "IOError during parsing." + exception.getMessage ();
errorOutput.append (errorMessage);
log.error (errorMessage);
result = null;
}
//
return (result);
}
/*
*
*/
static protected Document fileToTree (String fileName, StringBuffer errorOutput)
{
Document result;
try
{
result = buildTree (new FileInputStream (new File (fileName)), errorOutput);
}
catch (IOException exception)
{
String errorMessage = "IOError during parsing." + exception.getMessage ();
errorOutput.append (errorMessage);
log.error (errorMessage);
result = null;
}
//
return (result);
}
/*
*
*/
static protected void addMetaTag (Document doc, String name, String content)
{
// Find head tag.
Node headNode = Presenter.findHeadNode (doc);
Node metaNode = doc.createElement ("meta");
NamedNodeMap attrMap = metaNode.getAttributes();
Node attrNode = doc.createAttribute("name");
attrMap.setNamedItem(attrNode);
attrNode.setNodeValue(name);
attrNode = doc.createAttribute("content");
attrMap.setNamedItem(attrNode);
attrNode.setNodeValue(content);
headNode.insertBefore(metaNode, headNode.getFirstChild());
}
/**
* Finds the node containing the &lt;head&gt; tag.
*
* @param node
* Document node.
* @return The head tag node
*/
static protected Node findHeadNode (Node node)
{
Node headNode = null;
int type = node.getNodeType();
switch (type)
{
// print document
case Node.DOCUMENT_NODE:
{
headNode = findHeadNode(((Document) node).getDocumentElement());
break;
}
case Node.ELEMENT_NODE:
{
String tag = node.getNodeName();
if ("head".equals(tag))
{
headNode = node;
break;
}
NodeList children = node.getChildNodes();
int numChildren = 0;
if (children != null)
numChildren = children.getLength();
for (int i = 0; i < numChildren; i++)
{
headNode = findHeadNode(children.item(i));
if (headNode != null)
break;
}
break;
}
}
return headNode;
}
/**
* Any ampersand lt;, ampersand gt; and ampersand amp; sequences in text
* nodes get read in as symbols. This method converts them back to entities.
*
* @param s String that is to have the entities restored..
* @return The processed string.
*/
static public String restoreEntities (StringBuffer s)
{
String result;
if (s == null)
{
result = null;
}
else
{
StringBuffer str = new StringBuffer();
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++)
{
char ch = s.charAt(i);
switch (ch)
{
case '<':
{
str.append("&lt;");
break;
}
case '>':
{
str.append("&gt;");
break;
}
case '&':
{
str.append("&amp;");
break;
}
default:
{
str.append(ch);
}
}
}
result = str.toString ();
}
//
return (result);
}
/**
* Get the text for an element. Converts new lines to spaces.
*
* @param node
*/
static protected String getElementText (Node node)
{
String result;
result = ""; // Grrrr, Java ...
NodeList children = node.getChildNodes();
if (children == null)
{
result = "";
}
else
{
boolean ended = false;
int childCounter = 0;
int childCount = children.getLength ();
while (!ended)
{
if (childCounter >= childCount)
{
ended = true;
result = "";
}
else
{
Node child = children.item (childCounter);
if (child.getNodeType () == Node.TEXT_NODE)
{
result = newLinesToSpaces (child.getNodeValue ()); // STU (+=, newLines...)
ended = true;
}
else
{
childCounter += 1;
}
}
}
}
//
return (result);
}
/**
* Converts New Line characters to spaces. This is used when for example
* the text in a div tag goes over serveral lines.
*
* @param text String
* @return String
*/
static protected String newLinesToSpaces (String text)
{
StringBuffer result = new StringBuffer (text);
for (int i = 0; i < result.length(); i++)
{
if (result.charAt (i) == '\n')
{
result.setCharAt (i,' ');
}
}
//
return (result.toString());
}
/*
*
*/
static protected StringBuffer processAttributes (NamedNodeMap attrs, TagData model)
{
StringBuffer result;
result = new StringBuffer ();
// Build the original attributes list.
HashMap<String, String> mergedAttributes;
mergedAttributes = new HashMap<String, String> ();
for (int attributeCounter = 0; attributeCounter < attrs.getLength(); attributeCounter++)
{
Attr attr = (Attr) attrs.item (attributeCounter);
mergedAttributes.put (attr.getNodeName(), attr.getNodeValue ());
}
// Put model attributes in the merged attributes list.
if (model != null)
{
Attributes modelAttributes = model.getAttributes();
if (modelAttributes != null)
{
Iterator iterator = modelAttributes.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, String> attribute = (Map.Entry<String, String>) iterator.next();
if (mergedAttributes.containsKey (attribute.getKey ()))
{
if (attribute.getKey ().equalsIgnoreCase ("style"))
{
mergedAttributes.put (attribute.getKey (), mergedAttributes.get (attribute.getKey ()) + attribute.getValue ());
}
else
{
mergedAttributes.put (attribute.getKey (), attribute.getValue ());
}
}
else
{
mergedAttributes.put (attribute.getKey (), attribute.getValue ());
}
}
}
}
// Display the attributes
Iterator iterator = mergedAttributes.entrySet().iterator();
while (iterator.hasNext ())
{
Map.Entry<String, String> attribute = (Map.Entry<String, String>) iterator.next();
result.append(" " + attribute.getKey () + "=\"" + attribute.getValue ()+ "\"");
}
//
return (result);
}
/*
*
*/
static public String extractBodyContent (StringBuffer data)
{
String result = null;
// Extract the body content.
String dataLowerCase = data.toString ().toLowerCase ();
int startBody = dataLowerCase.indexOf ("<body>");
int endBody = dataLowerCase.indexOf ("</body>");
// Note: as failed search is improbable, no care about complexity
// in failed search case.
if ((startBody == -1) || (endBody == -1))
{
result = null;
}
else
{
result = data.substring (startBody + 6, endBody).trim ();
}
//
return (result);
}
}