/*
* 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.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.StringUtils;
import org.cruxframework.crux.core.config.ConfigurationFactory;
import org.cruxframework.crux.core.rebind.screen.ScreenFactory;
import org.cruxframework.crux.core.rebind.screen.widget.WidgetConfig;
import org.cruxframework.crux.core.rebind.screen.widget.WidgetCreator;
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_";
private static final String CRUX_CORE_SCREEN = "screen";
private static final String CRUX_CORE_SPLASH_SCREEN = "splashScreen";
private static final String CRUX_CORE_NAMESPACE= "http://www.cruxframework.org/crux";
private static DocumentBuilder documentBuilder;
private static XPathExpression findCruxPagesBodyExpression;
private static XPathExpression findHTMLHeadExpression;
private static XPathExpression findCruxSplashScreenExpression;
private static Set<String> htmlPanelContainers;
private static final Log log = LogFactory.getLog(ViewProcessor.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 Set<String> hasInnerHTMLWidgetTags;
private static Set<String> attachableWidgets;
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 final boolean escapeXML;
private final boolean indentOutput;
private String cruxTagName;
private int jsIndentationLvl;
private final String viewId;
private Document htmlDocument;
private Document cruxPageDocument;
private final boolean xhtmlInput;
/**
* Constructor.
*
* @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, boolean escapeXML, boolean indentOutput, boolean xhtmlInput) throws ViewParserException
{
this.viewId = viewId;
this.escapeXML = escapeXML;
this.indentOutput = indentOutput;
this.xhtmlInput = xhtmlInput;
}
/**
* 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>();
Set<String> registeredLibraries = WidgetConfig.getRegisteredLibraries();
for (String library : registeredLibraries)
{
Set<String> factories = WidgetConfig.getRegisteredLibraryFactories(library);
for (String widget : factories)
{
try
{
Class<?> clientClass = Class.forName(WidgetConfig.getClientClass(library, widget));
DeclarativeFactory factory = clientClass.getAnnotation(DeclarativeFactory.class);
if (factory.htmlContainer())
{
htmlPanelContainers.add(library+"_"+widget);
}
if (factory.attachToDOM())
{
attachableWidgets.add(library+"_"+widget);
}
}
catch (Exception e)
{
throw new ViewParserException("Error creating XSD File: Error generating widgets reference list.", e);
}
}
}
}
/**
* 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>();
Set<String> registeredLibraries = WidgetConfig.getRegisteredLibraries();
for (String library : registeredLibraries)
{
Set<String> factories = WidgetConfig.getRegisteredLibraryFactories(library);
for (String widget : factories)
{
try
{
Class<?> clientClass = Class.forName(WidgetConfig.getClientClass(library, widget));
generateReferenceWidgetsListFromTagChildren(clientClass.getAnnotation(TagChildren.class),
library, widget, new HashSet<String>());
}
catch (Exception e)
{
throw new ViewParserException("Error creating XSD File: 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 creating XSD File: Error generating widgets list.", e);
}
}
}
}
}
/**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();
elementsMetadata.append("[");
indent();
generateCruxMetadataForView(htmlElement, elementsMetadata);
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(metadata, viewHtmlElement);
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 cruxPageScreen
* @param cruxArrayMetaData
* @throws ViewParserException
*/
private void generateCruxScreenMetaData(Element cruxPageScreen, StringBuilder cruxArrayMetaData) throws ViewParserException
{
generateCruxMetadataForView(cruxPageScreen, cruxArrayMetaData, xhtmlInput);
}
/**
*
* @param htmlElement
* @param elementsMetadata
* @throws ViewParserException
*/
private void generateCruxMetadataForView(Element htmlElement, StringBuilder elementsMetadata) throws ViewParserException
{
generateCruxMetadataForView(htmlElement, elementsMetadata, !xhtmlInput);
}
/**
*
* @param cruxPageScreen
* @param cruxArrayMetaData
* @param generateViewTag
* @throws ViewParserException
*/
private void generateCruxMetadataForView(Element cruxPageScreen, StringBuilder cruxArrayMetaData, 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);
if (childrenMetaData.length() > 0)
{
if (generateViewTag)
{
cruxArrayMetaData.append(",");
}
cruxArrayMetaData.append(childrenMetaData);
}
}
/**
* @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 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 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;
}
/**
*
* @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);
}
}
/**
* @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);
}
htmlNode.appendChild(splashScreen);
}
/**
* @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 cruxPageInnerTag
* @param cruxArrayMetaData
* @throws ViewParserException
*/
private void generateCruxInnerMetaData(Element cruxPageInnerTag, StringBuilder cruxArrayMetaData) 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(cruxArrayMetaData, htmlElement);
}
else if (allowInnerHTML(currentWidgetTag))
{
generateCruxInnerHTMLMetadata(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);
outdent();
cruxArrayMetaData.append("]");
}
cruxArrayMetaData.append("}");
}
/**
*
* @param cruxArrayMetaData
* @param htmlElement
* @throws ViewParserException
*/
private void generateCruxInnerHTMLMetadata(StringBuilder cruxArrayMetaData, Element htmlElement) throws ViewParserException
{
String innerHTML = getHTMLFromNode(htmlElement);
cruxArrayMetaData.append(",\"_html\":\""+innerHTML+"\"");
}
/**
* @param tagName
* @return
*/
private boolean allowInnerHTML(String tagName)
{
return hasInnerHTMLWidgetTags.contains(tagName);
}
/**
* @param cruxPageBodyElement
* @param cruxArrayMetaData
* @throws ViewParserException
*/
private void generateCruxMetaData(Node cruxPageBodyElement, StringBuilder cruxArrayMetaData) 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(CRUX_CORE_SCREEN))
{
generateCruxScreenMetaData((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);
setCurrentWidgetTag(widgetType);
needsComma = true;
}
else
{
StringBuilder childrenMetaData = new StringBuilder();
generateCruxMetaData(child, childrenMetaData);
if (childrenMetaData.length() > 0)
{
if (needsComma)
{
cruxArrayMetaData.append(",");
}
cruxArrayMetaData.append(childrenMetaData);
needsComma = true;
}
}
}
}
}
/**
* @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
{
ScreenFactory factory = ScreenFactory.getInstance();
String screenModule = null;
try
{
screenModule = factory.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 = factory.getRelativeScreenId(this.viewId, screenModule);
Element cruxMetaData = htmlDocument.createElement("script");
cruxMetaData.setAttribute("id", "__CruxMetaDataTag_");
htmlHeadElement.appendChild(cruxMetaData);
Text textNode = htmlDocument.createTextNode("var __CruxScreen_ = \""+screenModule+"/"+HTMLUtils.escapeJavascriptString(screenId, escapeXML)+"\"");
cruxMetaData.appendChild(textNode);
}
catch (Exception e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
* @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);
}
}
/**
* @return
*/
private String getCurrentWidgetTag()
{
return cruxTagName;
}
/**
* @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 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 node
* @return
*/
private String getReferencedWidget(String tagName)
{
return referenceWidgetsList.get(tagName);
}
/**
* @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);
}
/**
* @param node
* @return
* @throws ViewParserException
*/
private String getHTMLFromNode(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(child, innerHTML);
}
}
}
return HTMLUtils.escapeJavascriptString(innerHTML.toString(), false);
}
catch (IOException e)
{
throw new ViewParserException(e.getMessage(), e);
}
}
/**
*
* @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;
}
/**
*
*/
private void indent()
{
jsIndentationLvl++;
}
/**
* 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(CRUX_CORE_SCREEN))
{
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 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 tagName
* @return
*/
private boolean isReferencedWidget(String tagName)
{
return referenceWidgetsList.containsKey(tagName);
}
/**
* @param localName
* @param libraryName
* @return
*/
private boolean isWidget(String tagName)
{
return (WidgetConfig.getClientClass(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;
}
/**
*
*/
private void clearCurrentWidget()
{
cruxTagName = "";
}
/**
* @param cruxPageElement
* @param htmlElement
*/
private void translateCruxCoreElements(Element cruxPageElement, Element htmlElement, Document htmlDocument)
{
String nodeName = cruxPageElement.getLocalName();
if (nodeName.equals(CRUX_CORE_SCREEN))
{
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 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(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(" ");
}
}
}
}