/// <summary> /// Determines whether the given control is a template (should have the sys-template class) /// </summary> /// <param name="control"></param> /// <returns></returns> private static bool IsTemplate(Control control) { bool result = false; // DataView and Template create a new template context if (control is DataView || control is Template) result = true; // Toggle creates a new template context if using render/dispose mode else if (control is Toggle) result = ((Toggle)control).IsTemplate(); return result; }
/// <summary> /// Parses the children of the specified element into a list of template blocks. /// </summary> /// <param name="source">The original string source of the template.</param> /// <param name="element">The element to parse</param> /// <param name="withinTemplate">Indicates that the children of the given element are within a template control.</param> /// <param name="lastNestedTemplateIndex">Tracks the index of the last template element that was encountered. Passed through to recursive calls excluding templates.</param> /// <param name="nestedTemplates">Out parameter that returns the number of template controls represented by the children of the given element.</param> /// <returns>A list of template blocks.</returns> static List<Block> ParseChildren(string source, XmlElement element, bool withinTemplate, int lastNestedTemplateIndex, out int nestedTemplates) { // Track the blocks represented by the current element var blocks = new List<Block>(); nestedTemplates = 0; // Process the child nodes of the current element foreach (XmlNode node in element.ChildNodes) { switch (node.NodeType) { // XML Element, which could be a control, bound element start tag, or just literal content case XmlNodeType.Element: var child = (XmlElement)node; // Control if (child.HasAttribute("sys:attach") || child.HasAttribute("sys:if") || child.HasAttribute("sys:content-template") || child.HasAttribute("sys:id") || child.HasAttribute("id")) { bool parseChildren = true; Control control; if (child.HasAttribute("sys:attach")) { switch (child.GetAttribute("sys:attach")) { // Template case "template": control = new Template() { Attributes = GetAttributes(child, "class", "sys:attach", "sys:if", "sys:content-template", "template:name", "template:kind", "template:datatype", "template:islist", "template:isreference"), Name = GetLiteralTokens(child, "template:name"), Source = source + " [" + child.GetAttribute("template:name") + "]" + (child.HasAttribute("template:datatype") ? " - " + child.GetAttribute("template:datatype") : ""), IsList = GetBoolean(child, "template:islist"), IsReference = GetBoolean(child, "template:isreference"), Kind = GetStringLiteral(child, "template:kind"), DataType = GetStringLiteral(child, "template:datatype"), Class = GetLiteralTokens(child, "class").Where(c => c.ToLower() != "sys-template").ToArray(), ContentTemplateNames = GetLiteralTokens(child, "sys:content-template") }; break; // DataView case "dataview": control = new DataView() { Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template", "dataview:data"), Data = GetBinding(child, "dataview:data"), Template = GetTemplate(child), ContentTemplate = GetBinding(child, "sys:content-template") }; break; // Content case "content": control = new Content() { Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template", "content:data", "content:template"), Data = GetBinding(child, "content:data"), Template = GetBinding(child, "content:template"), DataType = GetBinding(child, "content:datatype"), ContentTemplate = GetBinding(child, "sys:content-template") }; break; // Toggle case "toggle": control = new Toggle() { Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template", "toggle:on", "toggle:action", "toggle:class", "toggle:groupname", "toggle:strictmode", "toggle:when"), On = GetBinding(child, "toggle:on"), Class = GetBinding(child, "toggle:class"), ClassName = GetBinding(child, "toggle:classname"), Action = GetBinding(child, "toggle:action"), GroupName = GetBinding(child, "toggle:groupname"), StrictMode = GetBinding(child, "toggle:strictmode"), When = GetBinding(child, "toggle:when"), ContentTemplate = GetBinding(child, "sys:content-template") }; break; // ToggleGroup case "togglegroup": control = new ToggleGroup() { Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template"), ContentTemplate = GetBinding(child, "sys:content-template") }; break; // Behavior case "behavior": control = new Behavior() { Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template"), ContentTemplate = GetBinding(child, "sys:content-template") }; break; // Html case "html": control = new Html() { Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template"), ContentTemplate = GetBinding(child, "sys:content-template") }; break; default: throw new ArgumentException("Controls of type '" + child.GetAttribute("sys:attach") + "' are not supported for server rendering."); } // Add data-sys-attach to indicate the control type when performing linking on the client control.Attributes.Add(new Attribute() { Name = "data-sys-attach", Value = child.GetAttribute("sys:attach") }); } else { control = new Control() { Attributes = GetAttributes(child, "sys:if", "sys:content-template", "sys:innerhtml", "sys:innertext"), ContentTemplate = GetBinding(child, "sys:content-template"), IsEmpty = child.ChildNodes.Count == 0 }; if (child.InnerXml.Trim().StartsWith("{") && child.InnerXml.Trim().EndsWith("}")) { parseChildren = false; control.Attributes.Add(new Attribute() { Name = "innerhtml", Binding = Binding.Parse(GetDefaultProperty(child), child.InnerXml.Trim()), Value = child.InnerXml.Trim() }); control.IsEmpty = true; } } var isTemplate = IsTemplate(control); // If the sys:content-template attribute is found, then ensure it is within or on a templated control if (child.HasAttribute("sys:content-template") && !withinTemplate && !isTemplate) throw new ApplicationException("The sys:content-template attribute must be used on or within an control that implements Sys.UI.IContentTemplateConsumer."); // Process the controls child blocks if (parseChildren) { // Determine the number of top-level templates represented by this node int numTopLevelTemplates; if (isTemplate) { // A templated control represents only 1 top-level nested template numTopLevelTemplates = 1; // Set nested template index control.NestedTemplateIndex = lastNestedTemplateIndex + 1; // Parse child blocks as a new template region. This means that lastNestedTemplateIndex // starts fresh (-1) and the number of child templates are not relevant here. control.Blocks = ParseChildren(source, child, true); } else // Parse children and capture the number of top-level templates contained within control.Blocks = ParseChildren(source, child, withinTemplate, lastNestedTemplateIndex, out numTopLevelTemplates); // Increment the number of nested templates and last index by // the number of top-level templates that this node represents nestedTemplates += numTopLevelTemplates; lastNestedTemplateIndex += numTopLevelTemplates; } else // A non-control with a binding expression as its inner-html contains no blocks control.Blocks = new List<Block>(); control.Tag = child.Name; control.Markup = GetMarkup(child); control.If = GetBinding(child, "sys:if"); // Add the control blocks.Add(control); } // Element else if (child.Attributes.Cast<XmlAttribute>().Any(a => a.Value.StartsWith("{") && a.Value.EndsWith("}")) || (child.ChildNodes.Cast<XmlNode>().All(n => n.NodeType != XmlNodeType.Element) && child.InnerXml.Trim().StartsWith("{") && child.InnerXml.Trim().EndsWith("}"))) { var isBinding = child.InnerXml.Trim().StartsWith("{") && child.InnerXml.Trim().EndsWith("}"); // Add the bound element var e = new Element() { Markup = isBinding || child.ChildNodes.Count == 0 ? GetMarkup(child) : GetElementMarkup(child), Attributes = GetAttributes(child), Tag = child.Name, IsEmpty = isBinding || child.ChildNodes.Count == 0 }; if (isBinding) e.Attributes.Add(new Attribute() { Name = "innerhtml", Binding = Binding.Parse(GetDefaultProperty(child), child.InnerXml.Trim()) }); blocks.Add(e); // Process child nodes, if the element content is not bound if (!e.IsEmpty) { int numTopLevelTemplates; var children = ParseChildren(source, child, withinTemplate, lastNestedTemplateIndex, out numTopLevelTemplates); lastNestedTemplateIndex += numTopLevelTemplates; nestedTemplates += numTopLevelTemplates; blocks.AddRange(children); blocks.Add(new Block() { Markup = "</" + child.Name + ">" }); } } // Literal else { // Get the blocks contained by the literal element int numTopLevelTemplates; var children = ParseChildren(source, child, withinTemplate, lastNestedTemplateIndex, out numTopLevelTemplates); lastNestedTemplateIndex += numTopLevelTemplates; nestedTemplates += numTopLevelTemplates; // Add the entire element as a block if it only contains literal content if (children.Count == 0 || (children.Count == 1 && children.First().GetType() == typeof(Block))) blocks.Add(new Block() { Markup = GetMarkup(child) }); // Otherwise, process the child blocks else { blocks.Add(new Block() { Markup = GetElementMarkup(child) }); blocks.AddRange(children); blocks.Add(new Block() { Markup = "</" + child.Name + ">" }); } } break; // Literal content case XmlNodeType.Text: blocks.Add(new Block() { Markup = GetMarkup(node) }); break; } } // Condense adjacent literal blocks Block literal = null; for (int i = blocks.Count - 1; i >= 0; i--) { if (blocks[i].GetType() == typeof(Block)) { if (literal == null) literal = blocks[i]; else { literal.Markup = blocks[i].Markup + literal.Markup; blocks.RemoveAt(i); } } else literal = null; } return blocks; }