/*
* Copyright 2011 cruxframework.org.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cruxframework.crux.core.declarativeui;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cruxframework.crux.core.client.screen.views.ViewFactoryUtils;
import org.cruxframework.crux.core.client.utils.EscapeUtils;
import org.cruxframework.crux.core.client.utils.StringUtils;
import org.cruxframework.crux.core.config.ConfigurationFactory;
import org.cruxframework.crux.core.rebind.dataprovider.DataProviderType;
import org.cruxframework.crux.core.rebind.screen.ScreenFactory;
import org.cruxframework.crux.core.rebind.screen.widget.WidgetCreator;
import org.cruxframework.crux.core.rebind.screen.widget.WidgetLibraries;
import org.cruxframework.crux.core.rebind.screen.widget.creator.children.WidgetChildProcessor;
import org.cruxframework.crux.core.rebind.screen.widget.creator.children.WidgetChildProcessor.HTMLTag;
import org.cruxframework.crux.core.rebind.screen.widget.declarative.DeclarativeFactory;
import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagChild;
import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagChildren;
import org.cruxframework.crux.core.rebind.screen.widget.declarative.TagConstraints;
import org.cruxframework.crux.core.utils.HTMLUtils;
import org.cruxframework.crux.core.utils.ViewUtils;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import com.google.gwt.user.client.ui.HTMLPanel;
/**
* Parses Crux view pages to extract metadata and generate the equivalent html for host pages.
*
* @author Thiago da Rosa de Bustamante
*
*/
public class ViewParser
{
public static final String CRUX_VIEW_PREFIX = "_crux_view_prefix_";
public static final String SCREEN_TYPE = "screen";
private static Set<String> attachableWidgets;
private static final String CRUX_CORE_NAMESPACE= "http://www.cruxframework.org/crux";
private static final String CRUX_CORE_SPLASH_SCREEN = "splashScreen";
private static DocumentBuilder documentBuilder;
private static XPathExpression findCruxPagesBodyExpression;
private static XPathExpression findCruxSplashScreenExpression;
private static XPathExpression findHTMLHeadExpression;
private static Set<String> hasInnerHTMLWidgetTags;
private static Set<String> htmlPanelContainers;
private static final Log log = LogFactory.getLog(ViewParser.class);
private static Map<String, String> referenceWidgetsList;
private static final String WIDGETS_NAMESPACE_PREFIX= "http://www.cruxframework.org/crux/";
private static Set<String> widgetsSubTags;
private static final String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
static
{
try
{
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(false);
builderFactory.setIgnoringComments(true);
documentBuilder = builderFactory.newDocumentBuilder();
generateReferenceWidgetsList();
generateSpecialWidgetsList();
XPath htmlPath = XPathUtils.getHtmlXPath();
findCruxPagesBodyExpression = htmlPath.compile("//h:body");
findHTMLHeadExpression = htmlPath.compile("//h:head");
findCruxSplashScreenExpression = XPathUtils.getCruxPagesXPath().compile("//c:splashScreen");
}
catch (Exception e)
{
log.error("Error creating viewParser.", e);
}
}
private Document cruxPageDocument;
private String cruxTagName;
private final boolean escapeXML;
private Document htmlDocument;
private final boolean indentOutput;
private int jsIndentationLvl;
private final String viewId;
private final String device;
private final boolean xhtmlInput;
/**
* Constructor.
*
* @param viewId The view identifier
* @param device the compilation device
* @param device The target device being compiled
* @param escapeXML If true will escape all inner text nodes to ensure that the generated outputs will be parsed correctly by a XML parser.
* @param indentOutput True makes the generated outputs be indented.
* @param xhtmlInput True if the given document represents a valid XHTML page. If false, parser will assume that the input is
* composed by a root tag representing a the view.
* @throws ViewParserException
*/
public ViewParser(String viewId, String device, boolean escapeXML, boolean indentOutput, boolean xhtmlInput) throws ViewParserException
{
this.viewId = viewId;
this.device = device;
this.escapeXML = escapeXML;
this.indentOutput = indentOutput;
this.xhtmlInput = xhtmlInput;
}
/**Extract the view metadata form the current document
* @param element
*/
public String extractCruxMetaData(Document view) throws ViewParserException
{
try
{
this.htmlDocument = createHTMLDocument(view);
Element htmlElement = view.getDocumentElement();
StringBuilder elementsMetadata = new StringBuilder();
StringBuilder nativeControllers = new StringBuilder();
nativeControllers.append("[");
elementsMetadata.append("[");
indent();
generateCruxMetadataForView(htmlElement, elementsMetadata, nativeControllers);
outdent();
elementsMetadata.append("]");
StringBuilder metadata = new StringBuilder();
metadata.append("{");
indent();
metadata.append("\"elements\":"+elementsMetadata.toString());
metadata.append(",\"lazyDeps\":"+new LazyWidgets(escapeXML).generateScreenLazyDeps(elementsMetadata.toString()));
Element viewHtmlElement = htmlDocument.createElementNS(XHTML_NAMESPACE,"body");
Element rootElement = (xhtmlInput?getPageBodyElement(view):view.getDocumentElement());
translateDocument(rootElement, viewHtmlElement, true);
generateCruxInnerHTMLMetadata(nativeControllers, metadata, viewHtmlElement);
nativeControllers.append("]");
metadata.append(",\"nativeControllers\":"+nativeControllers.toString());
outdent();
metadata.append("}");
return metadata.toString();
}
catch (Exception e)
{
throw new ViewParserException("Error extracting Crux Metadata from view.", e);
}
}
/**
* Generates the HTML page from the given .crux.xml page.
*
* @param viewId The id of the screen associated with the .crux.xml page.
* @param cruxPageDocument a XML Document representing the .crux.xml page.
* @param out Where the generated HTML will be written.
* @throws ViewParserException
*/
public void generateHTMLHostPage(Document cruxPageDocument, Writer out) throws ViewParserException
{
try
{
if (!xhtmlInput)
{
throw new ViewParserException("Can not generate an HTML host page for a non XHTML document.");
}
this.cruxPageDocument = cruxPageDocument;
this.htmlDocument = createHTMLDocument(cruxPageDocument);
translateHTMLHostDocument();
write(htmlDocument, out);
}
catch (IOException e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
* @param tagName
* @return
*/
private boolean allowInnerHTML(String tagName)
{
return hasInnerHTMLWidgetTags.contains(tagName);
}
/**
*
*/
private void clearCurrentWidget()
{
cruxTagName = "";
}
/**
* @param cruxPageDocument
* @return
*/
private Document createHTMLDocument(Document cruxPageDocument)
{
Document htmlDocument;
DocumentType doctype = cruxPageDocument.getDoctype();
if (doctype != null || Boolean.parseBoolean(ConfigurationFactory.getConfigurations().enableGenerateHTMLDoctype()))
{
String name = doctype != null ? doctype.getName() : "HTML";
String publicId = doctype != null ? doctype.getPublicId() : null;
String systemId = doctype != null ? doctype.getSystemId() : null;
DocumentType newDoctype = documentBuilder.getDOMImplementation().createDocumentType(name, publicId, systemId);
htmlDocument = documentBuilder.getDOMImplementation().createDocument(XHTML_NAMESPACE, "html", newDoctype);
}
else
{
htmlDocument = documentBuilder.newDocument();
Element cruxPageElement = cruxPageDocument.getDocumentElement();
Node htmlElement = htmlDocument.importNode(cruxPageElement, false);
htmlDocument.appendChild(htmlElement);
}
String manifest = cruxPageDocument.getDocumentElement().getAttribute("manifest");
if (!StringUtils.isEmpty(manifest))
{
htmlDocument.getDocumentElement().setAttribute("manifest", manifest);
}
return htmlDocument;
}
/**
* @param nativeControllers
* @param cruxArrayMetaData
* @param htmlElement
* @throws ViewParserException
*/
private void generateCruxInnerHTMLMetadata(StringBuilder nativeControllers,
StringBuilder cruxArrayMetaData, Element htmlElement) throws ViewParserException
{
StringBuilder nativeBindings = new StringBuilder();
nativeBindings.append("[");
String innerHTML = getHTMLFromNode(nativeControllers, nativeBindings, htmlElement);
nativeBindings.append("]");
cruxArrayMetaData.append(",\"_html\":\""+innerHTML+"\"");
cruxArrayMetaData.append(",\"nativeBindings\":"+nativeBindings.toString());
}
/**
* @param cruxPageInnerTag
* @param cruxArrayMetaData
* @param nativeControllers
* @throws ViewParserException
*/
private void generateCruxInnerMetaData(Element cruxPageInnerTag, StringBuilder cruxArrayMetaData, StringBuilder nativeControllers) throws ViewParserException
{
writeIndentationSpaces(cruxArrayMetaData);
cruxArrayMetaData.append("{");
String currentWidgetTag = getCurrentWidgetTag() ;
if (isWidget(currentWidgetTag))
{
cruxArrayMetaData.append("\"_type\":\""+currentWidgetTag+"\"");
}
else
{
cruxArrayMetaData.append("\"_childTag\":\""+cruxPageInnerTag.getLocalName()+"\"");
}
if (isHtmlContainerWidget(cruxPageInnerTag))
{
Element htmlElement = htmlDocument.createElementNS(XHTML_NAMESPACE,"body");
translateDocument(cruxPageInnerTag, htmlElement, true);
generateCruxInnerHTMLMetadata(nativeControllers, cruxArrayMetaData, htmlElement);
}
else if (allowInnerHTML(currentWidgetTag))
{
generateCruxInnerHTMLMetadata(nativeControllers, cruxArrayMetaData, cruxPageInnerTag);
}
else
{
String innerText = getTextFromNode(cruxPageInnerTag);
if (innerText.length() > 0)
{
cruxArrayMetaData.append(",\"_text\":\""+innerText+"\"");
}
}
generateCruxMetaDataAttributes(cruxPageInnerTag, cruxArrayMetaData);
NodeList childNodes = cruxPageInnerTag.getChildNodes();
if (childNodes != null && childNodes.getLength() > 0)
{
cruxArrayMetaData.append(",\"_children\":[");
indent();
generateCruxMetaData(cruxPageInnerTag, cruxArrayMetaData, nativeControllers);
outdent();
cruxArrayMetaData.append("]");
}
cruxArrayMetaData.append("}");
}
/**
* @param cruxPageBodyElement
* @param cruxArrayMetaData
* @param nativeControllers
* @throws ViewParserException
*/
private void generateCruxMetaData(Node cruxPageBodyElement, StringBuilder cruxArrayMetaData,
StringBuilder nativeControllers) throws ViewParserException
{
NodeList childNodes = cruxPageBodyElement.getChildNodes();
if (childNodes != null)
{
boolean needsComma = false;
for (int i=0; i<childNodes.getLength(); i++)
{
Node child = childNodes.item(i);
String namespaceURI = child.getNamespaceURI();
String nodeName = child.getLocalName();
if (namespaceURI != null && namespaceURI.equals(CRUX_CORE_NAMESPACE) && !nodeName.equals(CRUX_CORE_SPLASH_SCREEN))
{
if (needsComma)
{
cruxArrayMetaData.append(",");
}
if (nodeName.equals(SCREEN_TYPE))
{
generateCruxScreenMetaData((Element)child, cruxArrayMetaData, nativeControllers);
}
else if (isDataProviderElement(nodeName))
{
generateCruxDataProviderMetaData((Element)child, cruxArrayMetaData);
}
needsComma = true;
}
else if (namespaceURI != null && namespaceURI.startsWith(WIDGETS_NAMESPACE_PREFIX))
{
if (needsComma)
{
cruxArrayMetaData.append(",");
}
String widgetType = getCurrentWidgetTag();
updateCurrentWidgetTag((Element)child);
generateCruxInnerMetaData((Element)child, cruxArrayMetaData, nativeControllers);
setCurrentWidgetTag(widgetType);
needsComma = true;
}
else
{
StringBuilder childrenMetaData = new StringBuilder();
generateCruxMetaData(child, childrenMetaData, nativeControllers);
if (childrenMetaData.length() > 0)
{
if (needsComma)
{
cruxArrayMetaData.append(",");
}
cruxArrayMetaData.append(childrenMetaData);
needsComma = true;
}
}
}
}
}
/**
* Verify if the given nodeName refers to a dataProvider element
* @param nodeName node name
* @return true if it is a dataProvider element
*/
private boolean isDataProviderElement(String nodeName)
{
for (DataProviderType dataProviderType : DataProviderType.values())
{
if (dataProviderType.name().equals(nodeName))
{
return true;
}
}
return false;
}
/**
* @param cruxPageMetaData
* @param cruxArrayMetaData
*/
private void generateCruxMetaDataAttributes(Element cruxPageMetaData, StringBuilder cruxArrayMetaData)
{
NamedNodeMap attributes = cruxPageMetaData.getAttributes();
if (attributes != null)
{
for (int i=0; i<attributes.getLength(); i++)
{
Node attribute = attributes.item(i);
String attrName = attribute.getLocalName();
if (attrName == null)
{
attrName = attribute.getNodeName();
}
String attrValue = attribute.getNodeValue();
String namespaceURI = attribute.getNamespaceURI();
if (!StringUtils.isEmpty(attrValue) && (namespaceURI == null || !namespaceURI.endsWith("/xmlns/")))
{
cruxArrayMetaData.append(",");
cruxArrayMetaData.append("\""+attrName+"\":");
cruxArrayMetaData.append("\""+HTMLUtils.escapeJavascriptString(attrValue, escapeXML)+"\"");
}
}
}
}
/**
* @param htmlHeadElement
* @throws ViewParserException
*/
private void generateCruxMetaDataElement(Element htmlHeadElement) throws ViewParserException
{
String screenModule = null;
try
{
screenModule = ScreenFactory.getScreenModule(cruxPageDocument);
}
catch (Exception e)
{
throw new ViewParserException(e.getMessage(), e);
}
if (screenModule == null)
{
throw new ViewParserException("No module declared on view ["+viewId+"].");
}
try
{
String screenId = this.viewId;
Element cruxMetaData = htmlDocument.createElement("script");
cruxMetaData.setAttribute("id", "__CruxMetaDataTag_");
htmlHeadElement.appendChild(cruxMetaData);
Text textNode = htmlDocument.createTextNode("var __CruxScreen_ = \""+HTMLUtils.escapeJavascriptString(screenId, escapeXML)+"\"");
cruxMetaData.appendChild(textNode);
}
catch (Exception e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
*
* @param htmlElement
* @param elementsMetadata
* @param nativeControllers
* @throws ViewParserException
*/
private void generateCruxMetadataForView(Element htmlElement, StringBuilder elementsMetadata,
StringBuilder nativeControllers) throws ViewParserException
{
generateCruxMetadataForView(htmlElement, elementsMetadata, nativeControllers, !xhtmlInput);
}
private void generateCruxDataProviderMetaData(Element dataProviderElement, StringBuilder cruxArrayMetaData) throws ViewParserException
{
DataProviderType providerType = DataProviderType.valueOf(dataProviderElement.getLocalName());
writeIndentationSpaces(cruxArrayMetaData);
cruxArrayMetaData.append("{");
cruxArrayMetaData.append("\"_type\":"+EscapeUtils.quote(providerType.getType()));
generateCruxMetaDataAttributes(dataProviderElement, cruxArrayMetaData);
cruxArrayMetaData.append("}");
}
/**
*
* @param cruxPageScreen
* @param cruxArrayMetaData
* @param nativeControllers
* @param generateViewTag
* @throws ViewParserException
*/
private void generateCruxMetadataForView(Element cruxPageScreen, StringBuilder cruxArrayMetaData,
StringBuilder nativeControllers,
boolean generateViewTag) throws ViewParserException
{
writeIndentationSpaces(cruxArrayMetaData);
if (generateViewTag)
{
cruxArrayMetaData.append("{");
cruxArrayMetaData.append("\"_type\":\"screen\"");
generateCruxMetaDataAttributes(cruxPageScreen, cruxArrayMetaData);
cruxArrayMetaData.append("}");
}
StringBuilder childrenMetaData = new StringBuilder();
generateCruxMetaData(cruxPageScreen, childrenMetaData, nativeControllers);
if (childrenMetaData.length() > 0)
{
if (generateViewTag)
{
cruxArrayMetaData.append(",");
}
cruxArrayMetaData.append(childrenMetaData);
}
}
/**
*
* @param htmlBodyElement
* @throws ViewParserException
*/
private void generateCruxModuleElement(Element htmlBodyElement) throws ViewParserException
{
Element child = getScreenModule(cruxPageDocument);
if (child == null)
{
throw new ViewParserException("No module declared on screen ["+viewId+"].");
}
Node htmlChild = htmlDocument.importNode(child, false);
htmlBodyElement.appendChild(htmlChild);
}
/**
* @param cruxPageScreen
* @param cruxArrayMetaData
* @param nativeControllers
* @throws ViewParserException
*/
private void generateCruxScreenMetaData(Element cruxPageScreen, StringBuilder cruxArrayMetaData,
StringBuilder nativeControllers) throws ViewParserException
{
generateCruxMetadataForView(cruxPageScreen, cruxArrayMetaData, nativeControllers, xhtmlInput);
}
/**
* @return
*/
private String getCurrentWidgetTag()
{
return cruxTagName;
}
/**
* @param nativeControllers
* @param nativeBindings
* @param node
* @return
* @throws ViewParserException
*/
private String getHTMLFromNode(StringBuilder nativeControllers,StringBuilder nativeBindings, Element elem) throws ViewParserException
{
try
{
StringWriter innerHTML = new StringWriter();
NodeList children = elem.getChildNodes();
if (children != null)
{
for (int i=0; i<children.getLength(); i++)
{
Node child = children.item(i);
if (!isCruxModuleImportTag(child))
{
HTMLUtils.write(viewId, device, nativeControllers, nativeBindings, child, innerHTML);
}
}
}
return HTMLUtils.escapeJavascriptString(innerHTML.toString(), false);
}
catch (IOException e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
* @param node
* @return
*/
private String getLibraryName(Node node)
{
String namespaceURI = node.getNamespaceURI();
if (namespaceURI != null && namespaceURI.startsWith(WIDGETS_NAMESPACE_PREFIX))
{
return namespaceURI.substring(WIDGETS_NAMESPACE_PREFIX.length());
}
return null;
}
/**
* @param cruxPageDocument
* @return
* @throws ViewParserException
*/
private Element getPageBodyElement(Document cruxPageDocument) throws ViewParserException
{
try
{
NodeList bodyNodes = (NodeList)findCruxPagesBodyExpression.evaluate(cruxPageDocument, XPathConstants.NODESET);
if (bodyNodes.getLength() > 0)
{
return (Element)bodyNodes.item(0);
}
Element bodyElement = cruxPageDocument.createElementNS(XHTML_NAMESPACE, "body");
cruxPageDocument.getDocumentElement().appendChild(bodyElement);
return bodyElement;
}
catch (XPathExpressionException e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
* @param htmlDocument
* @return
* @throws ViewParserException
*/
private Element getPageHeadElement(Document htmlDocument) throws ViewParserException
{
try
{
NodeList headNodes = (NodeList)findHTMLHeadExpression.evaluate(htmlDocument, XPathConstants.NODESET);
if (headNodes.getLength() > 0)
{
return (Element)headNodes.item(0);
}
Element headElement = htmlDocument.createElementNS(XHTML_NAMESPACE,"head");
Element bodyElement = getPageBodyElement(htmlDocument);
if (bodyElement != null)
{
htmlDocument.getDocumentElement().insertBefore(headElement, bodyElement);
return headElement;
}
htmlDocument.getDocumentElement().appendChild(headElement);
return headElement;
}
catch (XPathExpressionException e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
* @param node
* @return
*/
private String getReferencedWidget(String tagName)
{
return referenceWidgetsList.get(tagName);
}
/**
*
* @param source
* @return
* @throws ViewParserException
*/
private Element getScreenModule(Document source) throws ViewParserException
{
Element result = null;
NodeList nodeList = source.getElementsByTagName("script");
int length = nodeList.getLength();
for (int i = 0; i < length; i++)
{
Element item = (Element) nodeList.item(i);
String src = item.getAttribute("src");
if (src != null && src.endsWith(".nocache.js"))
{
if (result != null)
{
throw new ViewParserException("Multiple modules in the same html page is not allowed in CRUX.");
}
result = item;
}
}
return result;
}
/**
* @param node
* @return
*/
private String getTextFromNode(Node node)
{
StringBuilder text = new StringBuilder();
NodeList children = node.getChildNodes();
if (children != null)
{
for (int i=0; i<children.getLength(); i++)
{
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE)
{
text.append(child.getNodeValue());
}
}
}
return HTMLUtils.escapeJavascriptString(text.toString().trim(), escapeXML);
}
/**
*
* @throws ViewParserException
*/
private void handleCruxSplashScreen() throws ViewParserException
{
try
{
NodeList splashScreenNodes = (NodeList)findCruxSplashScreenExpression.evaluate(cruxPageDocument, XPathConstants.NODESET);
if (splashScreenNodes.getLength() > 0)
{
if (splashScreenNodes.getLength() > 1)
{
throw new ViewParserException("The view ["+this.viewId+"] declares more than one splashScreen. Only one is allowed.");
}
Element splashScreen = (Element)splashScreenNodes.item(0);
translateSplashScreen(splashScreen, getPageBodyElement(htmlDocument));
}
}
catch (XPathExpressionException e)
{
throw new ViewParserException("Error inspecting the view ["+this.viewId+"]. Error while searching for splashScreen elements.", e);
}
}
/**
*
*/
private void indent()
{
jsIndentationLvl++;
}
/**
* @param node
* @return
*/
private boolean isAttachableWidget(Node node)
{
if (node instanceof Element)
{
return isAttachableWidget(node.getLocalName(), getLibraryName(node));
}
return false;
}
/**
* @param localName
* @param libraryName
* @return
*/
private boolean isAttachableWidget(String localName, String libraryName)
{
return attachableWidgets.contains(libraryName+"_"+localName);
}
/**
*
* @param node
* @return
*/
private boolean isCruxModuleImportTag(Node node)
{
if (node instanceof Element)
{
Element elem = (Element)node;
String tagName = elem.getTagName();
String namespaceURI = elem.getNamespaceURI();
String src = elem.getAttribute("src");
return (namespaceURI == null || namespaceURI.equals(XHTML_NAMESPACE)) && tagName.equalsIgnoreCase("script") && (src != null && src.endsWith(".nocache.js"));
}
return false;
}
/**
* Check if the target node is child from a rootDocument element or from a native XHTML element.
*
* @param node
* @return
*/
private boolean isHTMLChild(Node node)
{
Node parentNode = node.getParentNode();
String namespaceURI = parentNode.getNamespaceURI();
if (namespaceURI == null)
{
log.warn("The view ["+this.viewId+"] contains elements that is not bound to any namespace. It can cause errors while translating to an HTML page.");
}
if (node.getOwnerDocument().getDocumentElement().equals(parentNode))
{
return true;
}
if (namespaceURI != null && namespaceURI.equals(XHTML_NAMESPACE) || isHtmlContainerWidget(parentNode))
{
return true;
}
if (parentNode instanceof Element && namespaceURI != null && namespaceURI.equals(CRUX_CORE_NAMESPACE) && parentNode.getLocalName().equals(SCREEN_TYPE))
{
return isHTMLChild(parentNode);
}
return false;
}
/**
* @param node
* @return
*/
private boolean isHtmlContainerWidget(Node node)
{
if (node instanceof Element)
{
return isHtmlContainerWidget(node.getLocalName(), getLibraryName(node));
}
return false;
}
/**
* @param localName
* @param libraryName
* @return
*/
private boolean isHtmlContainerWidget(String localName, String libraryName)
{
return htmlPanelContainers.contains(libraryName+"_"+localName);
}
/**
* @param tagName
* @return
*/
private boolean isReferencedWidget(String tagName)
{
return referenceWidgetsList.containsKey(tagName);
}
/**
* @param localName
* @param libraryName
* @return
*/
private boolean isWidget(String tagName)
{
return (WidgetLibraries.getInstance().getFactoryClass(tagName) != null);
}
/**
* @param cruxTagName
* @return
*/
private boolean isWidgetSubTag(String cruxTagName)
{
if (cruxTagName.indexOf('_') == cruxTagName.lastIndexOf('_'))
{
return false;
}
return widgetsSubTags.contains(cruxTagName);
}
/**
*
*/
private void outdent()
{
jsIndentationLvl--;
}
/**
* @param cruxTagName
*/
private void setCurrentWidgetTag(String cruxTagName)
{
this.cruxTagName = cruxTagName;
}
/**
* @param cruxPageElement
* @param htmlElement
*/
private void translateCruxCoreElements(Element cruxPageElement, Element htmlElement, Document htmlDocument)
{
String nodeName = cruxPageElement.getLocalName();
if (nodeName.equals(SCREEN_TYPE))
{
translateDocument(cruxPageElement, htmlElement, true);
}
// else IGNORE
}
/**
* @param cruxPageElement
* @param htmlElement
* @param htmlDocument
*/
private void translateCruxInnerTags(Element cruxPageElement, Element htmlElement, Document htmlDocument)
{
String currentWidgetTag = getCurrentWidgetTag();
boolean attachableWidget = isAttachableWidget(cruxPageElement);
if ((isWidget(currentWidgetTag)) && attachableWidget && isHTMLChild(cruxPageElement))
{
Element widgetHolder = htmlDocument.createElement("div");
htmlElement.appendChild(widgetHolder);
widgetHolder.setAttribute("id", ViewFactoryUtils.ENCLOSING_PANEL_PREFIX + CRUX_VIEW_PREFIX+cruxPageElement.getAttribute("id"));
}
}
/**
* @param cruxPageElement
* @param htmlElement
*/
private void translateDocument(Node cruxPageElement, Node htmlElement, boolean copyHtmlNodes)
{
NodeList childNodes = cruxPageElement.getChildNodes();
if (childNodes != null)
{
for (int i=0; i<childNodes.getLength(); i++)
{
Node child = childNodes.item(i);
String namespaceURI = child.getNamespaceURI();
if (namespaceURI != null && namespaceURI.equals(CRUX_CORE_NAMESPACE))
{
translateCruxCoreElements((Element)child, (Element)htmlElement, htmlDocument);
}
else if (namespaceURI != null && namespaceURI.startsWith(WIDGETS_NAMESPACE_PREFIX))
{
String widgetType = getCurrentWidgetTag();
updateCurrentWidgetTag((Element)child);
translateCruxInnerTags((Element)child, (Element)htmlElement, htmlDocument);
setCurrentWidgetTag(widgetType);
}
else
{
Node htmlChild;
if (copyHtmlNodes)
{
htmlChild = htmlDocument.importNode(child, false);
htmlElement.appendChild(htmlChild);
}
else
{
htmlChild = htmlElement;
}
translateDocument(child, htmlChild, copyHtmlNodes);
}
}
}
}
/**
* @param viewId
* @throws ViewParserException
*/
private void translateHTMLHostDocument() throws ViewParserException
{
Element htmlHeadElement = getPageHeadElement(htmlDocument);
Element htmlBodyElement = getPageBodyElement(htmlDocument);
Element cruxHeadElement = getPageHeadElement(cruxPageDocument);
clearCurrentWidget();
translateDocument(cruxHeadElement, htmlHeadElement, true);
clearCurrentWidget();
generateCruxMetaDataElement(htmlBodyElement);
generateCruxModuleElement(htmlBodyElement);
handleCruxSplashScreen();
}
/**
* @param cruxPageNode
* @param htmlNode
* @param htmlDocument
*/
private void translateSplashScreen(Element cruxPageNode, Element htmlNode)
{
Element splashScreen = htmlDocument.createElement("div");
splashScreen.setAttribute("id", "cruxSplashScreen");
String style = cruxPageNode.getAttribute("style");
if (!StringUtils.isEmpty(style))
{
splashScreen.setAttribute("style", style);
}
String transactionDelay = cruxPageNode.getAttribute("transactionDelay");
if (!StringUtils.isEmpty(transactionDelay))
{
splashScreen.setAttribute("transactionDelay", transactionDelay);
}
NodeList childNodes = cruxPageNode.getChildNodes();
if (childNodes != null)
{
for (int i=0; i<childNodes.getLength(); i++)
{
Node child = childNodes.item(i);
Node htmlChild = htmlDocument.importNode(child, false);
splashScreen.appendChild(htmlChild);
}
}
htmlNode.appendChild(splashScreen);
}
/**
* @param cruxPageElement
* @return
*/
private String updateCurrentWidgetTag(Element cruxPageElement)
{
String canditateWidgetType = getLibraryName(cruxPageElement)+"_"+cruxPageElement.getLocalName();
if (StringUtils.isEmpty(cruxTagName))
{
cruxTagName = canditateWidgetType;
}
else
{
cruxTagName += "_"+cruxPageElement.getLocalName();
}
if (!isWidgetSubTag(cruxTagName) && (isWidget(canditateWidgetType)))
{
cruxTagName = canditateWidgetType;
}
if (isReferencedWidget(cruxTagName))
{
cruxTagName = getReferencedWidget(cruxTagName);
}
return cruxTagName;
}
/**
* @param out
* @throws IOException
*/
private void write(Document htmlDocument, Writer out) throws IOException
{
DocumentType doctype = htmlDocument.getDoctype();
if (doctype != null)
{
out.write("<!DOCTYPE " + doctype.getName() + ">\n");
}
HTMLUtils.write(viewId, device, null, null, htmlDocument.getDocumentElement(), out, indentOutput);
}
/**
* @param cruxArrayMetaData
*/
private void writeIndentationSpaces(StringBuilder cruxArrayMetaData)
{
if (indentOutput)
{
cruxArrayMetaData.append("\n");
for (int i=0; i< jsIndentationLvl; i++)
{
cruxArrayMetaData.append(" ");
}
}
}
/**
* Some widgets can define tags with custom names that has its type as other widget. It can be handled properly
* and those situations are mapped by this method.
*
* @return
* @throws ViewParserException
*/
private static void generateReferenceWidgetsList() throws ViewParserException
{
referenceWidgetsList = new HashMap<String, String>();
widgetsSubTags = new HashSet<String>();
hasInnerHTMLWidgetTags = new HashSet<String>();
Iterator<String> registeredLibraries = WidgetLibraries.getInstance().iterateRegisteredLibraries();
while (registeredLibraries.hasNext())
{
String library = registeredLibraries.next();
Iterator<String> factories = WidgetLibraries.getInstance().iterateRegisteredLibraryWidgetCreators(library);
while (factories.hasNext())
{
String widget = factories.next();
try
{
Class<?> factoryClass = Class.forName(WidgetLibraries.getInstance().getFactoryClass(library, widget));
generateReferenceWidgetsListFromTagChildren(factoryClass.getAnnotation(TagChildren.class),
library, widget, new HashSet<String>());
}
catch (Exception e)
{
throw new ViewParserException("Error initialising view parser: Error generating widgets reference list.", e);
}
}
}
}
/**
*
* @param widgetList
* @param tagChildren
* @param parentLibrary
* @throws ViewParserException
*/
private static void generateReferenceWidgetsListFromTagChildren(TagChildren tagChildren,
String parentLibrary, String parentWidget, Set<String> added) throws ViewParserException
{
if (tagChildren != null)
{
String parentPath;
for (TagChild child : tagChildren.value())
{
Class<? extends WidgetChildProcessor<?>> processorClass = child.value();
if (!added.contains(processorClass.getCanonicalName()))
{
parentPath = parentWidget;
added.add(processorClass.getCanonicalName());
TagConstraints childAttributes = ViewUtils.getChildTagConstraintsAnnotation(processorClass);
if (childAttributes!= null)
{
if (!StringUtils.isEmpty(childAttributes.tagName()))
{
parentPath = parentWidget+"_"+childAttributes.tagName();
if (WidgetCreator.class.isAssignableFrom(childAttributes.type()))
{
DeclarativeFactory declarativeFactory = childAttributes.type().getAnnotation(DeclarativeFactory.class);
if (declarativeFactory != null)
{
referenceWidgetsList.put(parentLibrary+"_"+parentPath,
declarativeFactory.library()+"_"+declarativeFactory.id());
}
}
else
{
widgetsSubTags.add(parentLibrary+"_"+parentPath);
}
}
if (HTMLTag.class.isAssignableFrom(childAttributes.type()))
{
hasInnerHTMLWidgetTags.add(parentLibrary+"_"+parentPath);
}
}
try
{
generateReferenceWidgetsListFromTagChildren(processorClass.getAnnotation(TagChildren.class),
parentLibrary, parentPath, added);
}
catch (Exception e)
{
throw new ViewParserException("Error initialising view parser: Error generating widgets list.", e);
}
}
}
}
}
/**
* That method maps all widgets that needs special handling from Crux.
* Panels that can contain HTML mixed with widgets as contents (like {@link HTMLPanel}) and widgets
* that must not be attached to DOM must be handled differentially.
*
* @return
* @throws ViewParserException
*/
private static void generateSpecialWidgetsList() throws ViewParserException
{
htmlPanelContainers = new HashSet<String>();
attachableWidgets = new HashSet<String>();
Iterator<String> registeredLibraries = WidgetLibraries.getInstance().iterateRegisteredLibraries();
while (registeredLibraries.hasNext())
{
String library = registeredLibraries.next();
Iterator<String> factories = WidgetLibraries.getInstance().iterateRegisteredLibraryWidgetCreators(library);
while (factories.hasNext())
{
String widget = factories.next();
try
{
Class<?> factoryClass = Class.forName(WidgetLibraries.getInstance().getFactoryClass(library, widget));
DeclarativeFactory factory = factoryClass.getAnnotation(DeclarativeFactory.class);
if (factory.htmlContainer())
{
htmlPanelContainers.add(library+"_"+widget);
}
if (factory.attachToDOM())
{
attachableWidgets.add(library+"_"+widget);
}
}
catch (Exception e)
{
throw new ViewParserException("Error initialising view parser: Error generating widgets reference list.", e);
}
}
}
}
}