1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\r
5 * This software is subject to the Microsoft Public License (Ms-PL).
\r
6 * A copy of the license can be found in the license.htm file included
\r
7 * in this distribution.
\r
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc {
\r
15 using System.Collections.Generic;
\r
16 using System.Globalization;
\r
19 using System.Web.Mvc.Resources;
\r
21 public class TagBuilder {
\r
22 private string _idAttributeDotReplacement;
\r
24 private const string _attributeFormat = @" {0}=""{1}""";
\r
25 private const string _elementFormatEndTag = "</{0}>";
\r
26 private const string _elementFormatNormal = "<{0}{1}>{2}</{0}>";
\r
27 private const string _elementFormatSelfClosing = "<{0}{1} />";
\r
28 private const string _elementFormatStartTag = "<{0}{1}>";
\r
30 private string _innerHtml;
\r
32 public TagBuilder(string tagName) {
\r
33 if (String.IsNullOrEmpty(tagName)) {
\r
34 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "tagName");
\r
38 Attributes = new SortedDictionary<string, string>(StringComparer.Ordinal);
\r
41 public IDictionary<string, string> Attributes {
\r
46 public string IdAttributeDotReplacement {
\r
48 if (String.IsNullOrEmpty(_idAttributeDotReplacement)) {
\r
49 _idAttributeDotReplacement = HtmlHelper.IdAttributeDotReplacement;
\r
51 return _idAttributeDotReplacement;
\r
54 _idAttributeDotReplacement = value;
\r
58 public string InnerHtml {
\r
60 return _innerHtml ?? String.Empty;
\r
67 public string TagName {
\r
72 public void AddCssClass(string value) {
\r
73 string currentValue;
\r
75 if (Attributes.TryGetValue("class", out currentValue)) {
\r
76 Attributes["class"] = value + " " + currentValue;
\r
79 Attributes["class"] = value;
\r
83 internal static string CreateSanitizedId(string originalId, string dotReplacement) {
\r
84 if (String.IsNullOrEmpty(originalId)) {
\r
88 char firstChar = originalId[0];
\r
89 if (!Html401IdUtil.IsLetter(firstChar)) {
\r
90 // the first character must be a letter
\r
94 StringBuilder sb = new StringBuilder(originalId.Length);
\r
95 sb.Append(firstChar);
\r
97 for (int i = 1; i < originalId.Length; i++) {
\r
98 char thisChar = originalId[i];
\r
99 if (Html401IdUtil.IsValidIdCharacter(thisChar)) {
\r
100 sb.Append(thisChar);
\r
103 sb.Append(dotReplacement);
\r
107 return sb.ToString();
\r
110 public void GenerateId(string name) {
\r
111 if (!Attributes.ContainsKey("id")) {
\r
112 string sanitizedId = CreateSanitizedId(name, IdAttributeDotReplacement);
\r
113 if (!String.IsNullOrEmpty(sanitizedId)) {
\r
114 Attributes["id"] = sanitizedId;
\r
119 private string GetAttributesString() {
\r
120 StringBuilder sb = new StringBuilder();
\r
121 foreach (var attribute in Attributes) {
\r
122 string key = attribute.Key;
\r
123 if (String.Equals(key, "id", StringComparison.Ordinal /* case-sensitive */) && String.IsNullOrEmpty(attribute.Value)) {
\r
124 continue; // DevDiv Bugs #227595: don't output empty IDs
\r
126 string value = HttpUtility.HtmlAttributeEncode(attribute.Value);
\r
127 sb.AppendFormat(CultureInfo.InvariantCulture, _attributeFormat, key, value);
\r
129 return sb.ToString();
\r
132 public void MergeAttribute(string key, string value) {
\r
133 MergeAttribute(key, value, false /* replaceExisting */);
\r
136 public void MergeAttribute(string key, string value, bool replaceExisting) {
\r
137 if (String.IsNullOrEmpty(key)) {
\r
138 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "key");
\r
141 if (replaceExisting || !Attributes.ContainsKey(key)) {
\r
142 Attributes[key] = value;
\r
146 public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes) {
\r
147 MergeAttributes(attributes, false /* replaceExisting */);
\r
150 public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes, bool replaceExisting) {
\r
151 if (attributes != null) {
\r
152 foreach (var entry in attributes) {
\r
153 string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
\r
154 string value = Convert.ToString(entry.Value, CultureInfo.InvariantCulture);
\r
155 MergeAttribute(key, value, replaceExisting);
\r
160 public void SetInnerText(string innerText) {
\r
161 InnerHtml = HttpUtility.HtmlEncode(innerText);
\r
164 internal MvcHtmlString ToMvcHtmlString(TagRenderMode renderMode) {
\r
165 return MvcHtmlString.Create(ToString(renderMode));
\r
168 public override string ToString() {
\r
169 return ToString(TagRenderMode.Normal);
\r
172 public string ToString(TagRenderMode renderMode) {
\r
173 switch (renderMode) {
\r
174 case TagRenderMode.StartTag:
\r
175 return String.Format(CultureInfo.InvariantCulture, _elementFormatStartTag, TagName, GetAttributesString());
\r
176 case TagRenderMode.EndTag:
\r
177 return String.Format(CultureInfo.InvariantCulture, _elementFormatEndTag, TagName);
\r
178 case TagRenderMode.SelfClosing:
\r
179 return String.Format(CultureInfo.InvariantCulture, _elementFormatSelfClosing, TagName, GetAttributesString());
\r
181 return String.Format(CultureInfo.InvariantCulture, _elementFormatNormal, TagName, GetAttributesString(), InnerHtml);
\r
185 // Valid IDs are defined in http://www.w3.org/TR/html401/types.html#type-id
\r
186 private static class Html401IdUtil {
\r
187 private static bool IsAllowableSpecialCharacter(char c) {
\r
192 // note that we're specifically excluding the '.' character
\r
200 private static bool IsDigit(char c) {
\r
201 return ('0' <= c && c <= '9');
\r
204 public static bool IsLetter(char c) {
\r
205 return (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'));
\r
208 public static bool IsValidIdCharacter(char c) {
\r
209 return (IsLetter(c) || IsDigit(c) || IsAllowableSpecialCharacter(c));
\r