Package org.cruxframework.crux.core.declarativeui

Source Code of org.cruxframework.crux.core.declarativeui.ViewParser

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

Related Classes of org.cruxframework.crux.core.declarativeui.ViewParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.