/* * 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); } } } } }