Click here to Skip to main content
15,892,298 members
Articles / Web Development / ASP.NET

ASP.NET DaST to wrestle with MVC and WebForms

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
26 Mar 2013CPOL82 min read 23.2K   152   11  
DaST is a new architectural pattern for building highly dynamic Web 2.0 applications. A web page is rendered as a set of randomly nested rectangles where each rectangle is controlled individually and every combination of rectangles can be partially updated via AJAX.
// ******************************************************************************
// ******************************************************************************
// ASP.NET DaST Rendering Engine
// Copyright (C) 2011 Roman Gubarenko
// Project Home: http://aspnetdast.sourceforge.net/
// ******************************************************************************
// ******************************************************************************
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// ******************************************************************************
// Primary Contact: rgubarenko@gmail.com
// Learn More: http://aspnetdast.sourceforge.net/
// Project Repository: http://sourceforge.net/projects/aspnetdast/
// ******************************************************************************




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using AspNetDaST.HtmlParsing;

namespace AspNetDaST.Web.Private
{
  internal class TemplateTreeParser
  {
    private readonly string[] ValidScopeTags = new string[] { "div", "span", "td" };

    private Stack<KeyValuePair<string, string>> m_stackTags;
    private TreeNodeScope m_currNode;
    private HtmlParser m_parser;


    public TemplateTreeParser()
    {
      m_stackTags = new Stack<KeyValuePair<string, string>>();
      m_currNode = null;
    }


    public void ExtendTreeModelFromNode(TreeNodeScope startNode, string template)
    {
      try
      {
        m_currNode = startNode;

        m_parser = new HtmlParser(template);

        while (m_parser.ReadNextNode())
        {
          // opening element allowed to be scope container (i.e. DIV, SPAN, etc.)
          if (m_parser.NodeType == NodeTypes.Element && AllowedScopeTag(m_parser.Name))
          {
            string scopeID = m_parser.Attributes["dast:scope"];

            if (scopeID != null)
            {
              PushStack(m_parser.Name, scopeID);
              m_currNode = CreateChildScopeNode(scopeID);

              if (m_parser.IsEmptyElement)
              {
                PopStack();
                CommitScopeNode(m_currNode);
                m_currNode = m_currNode.ParentNode;
              }

              continue;
            }
            else
            {
              PushStack(m_parser.Name, null);
              if (m_parser.IsEmptyElement) PopStack();
            }
          }
          // closing element allowed to be scope container (i.e. DIV, SPAN, etc.)
          else if (m_parser.NodeType == NodeTypes.EndElement && AllowedScopeTag(m_parser.Name))
          {
            // pop recent allowed tag from stack
            var tag = PopStack();
            string stackTagName = tag.Key;
            string stackScopeID = tag.Value;

            // ensure well formness of allowed tags
            if (!string.Equals(m_parser.Name, stackTagName, StringComparison.OrdinalIgnoreCase))
              throw new DaSTException("Tags in the template are not well formed");

            // if current tag closes scope container, then
            // we finalize current scope and jump to next one
            if (stackScopeID != null)
            {
              // also make sure closing tag name matches tag name of current scope
              if (!string.Equals(m_currNode.TagOriginalName, stackTagName, StringComparison.OrdinalIgnoreCase))
                throw new DaSTException("Scopes in the template are not well formed");

              CommitScopeNode(m_currNode);
              m_currNode = m_currNode.ParentNode;
              continue;
            }
          }
          // comment element
          else if (m_parser.NodeType == NodeTypes.Comment)
          {
            if (m_parser.Value == "scope-from")
            {
              MarkScopeFromNode();
              continue;
            }
            else if (m_parser.Value == "scope-stop")
            {
              MarkScopeStopNode();
              continue;
            }
          }

          CreateChildHtmlNode();
        }

        // if everything is OK, currScope must be an initial scope
        // and stack must be empty to ensure well formed divs structure
        if (!object.Equals(m_currNode, startNode) || m_stackTags.Count != 0)
          throw new DaSTException("Scope tags in the template are not closed properly");

        // finalize initial node
        CommitScopeNode(m_currNode);
      }
      catch (DaSTException ex) { throw new DaSTException(ex, "Failed to parse template"); }
    }

    private TreeNodeScope CreateChildScopeNode(string scopeID)
    {
      // TODO: ensure there is no ID attribute

      TreeNodeScope newNode = new TreeNodeScope(m_currNode.ScopeTree);
      newNode.ScopeID = scopeID;
      newNode.NodePath = m_currNode.NodePath.Append(scopeID);
      newNode.ParentNode = m_currNode;
      newNode.TagOriginalName = m_parser.Name;
      newNode.TagAttributes.Add(m_parser.Attributes);

      m_currNode.Nodes.Add(newNode);

      return newNode;
    }

    private TreeNodeHtml CreateChildHtmlNode()
    {
      TreeNodeHtml newNode = m_currNode.Nodes.LastOrDefault() as TreeNodeHtml;

      if (newNode == null ||
        m_currNode.Nodes.Count == m_currNode.FromNodeIdx ||
        m_currNode.Nodes.Count - 1 == m_currNode.StopNodeIdx)
      {
        newNode = new TreeNodeHtml();
        newNode.ParentNode = m_currNode;
        m_currNode.Nodes.Add(newNode);
      }

      newNode.HtmlText.Append(m_parser.NodeHtml);

      return newNode;
    }

    private void MarkScopeFromNode()
    {
      m_currNode.FromNodeIdx = m_currNode.Nodes.Count;
    }

    private void MarkScopeStopNode()
    {
      m_currNode.StopNodeIdx = m_currNode.Nodes.Count - 1;
    }

    private void CommitScopeNode(TreeNodeScope scopeNode)
    {
      if (scopeNode.Nodes.Count > 0)
      {
        if (scopeNode.FromNodeIdx < 0 && scopeNode.StopNodeIdx < 0)
        {
          scopeNode.FromNodeIdx = 0;
          scopeNode.StopNodeIdx = Math.Max(scopeNode.Nodes.Count - 1, 0);
        }

        // validate scope fragments from/stop configuration
        if (scopeNode.FromNodeIdx < 0 ||
          scopeNode.FromNodeIdx > scopeNode.StopNodeIdx ||
          scopeNode.StopNodeIdx > scopeNode.Nodes.Count - 1)
        {
          throw new DaSTException("Invalid scope from/stop: {0}", scopeNode.NodePath);
        }
      }
      else
      {
        scopeNode.FromNodeIdx = -1;
        scopeNode.StopNodeIdx = -1;
      }
    }

  
    private bool AllowedScopeTag(string tagName)
    {
      return ValidScopeTags.Contains(tagName);
    }

    private void PushStack(string tagName, string scopeID)
    {
      m_stackTags.Push(new KeyValuePair<string, string>(tagName, scopeID));
    }

    private KeyValuePair<string, string> PopStack()
    {
      return m_stackTags.Pop();
    }
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
Software Architect with over 15 years in IT field. Started with deep math and C++ Computer Vision software. Currently in .NET and PHP web development. Creator of DaST pattern, open-source frameworks, and plugins. Interested in cutting Edge IT, open-source, Web 2.0, .NET, MVC, C++, Java, jQuery, Mobile tech, and extreme sports.

Comments and Discussions