Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638)
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / TagBuilder.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\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
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc {\r
14     using System;\r
15     using System.Collections.Generic;\r
16     using System.Globalization;\r
17     using System.Text;\r
18     using System.Web;\r
19     using System.Web.Mvc.Resources;\r
20 \r
21     public class TagBuilder {\r
22         private string _idAttributeDotReplacement;\r
23 \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
29 \r
30         private string _innerHtml;\r
31 \r
32         public TagBuilder(string tagName) {\r
33             if (String.IsNullOrEmpty(tagName)) {\r
34                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "tagName");\r
35             }\r
36 \r
37             TagName = tagName;\r
38             Attributes = new SortedDictionary<string, string>(StringComparer.Ordinal);\r
39         }\r
40 \r
41         public IDictionary<string, string> Attributes {\r
42             get;\r
43             private set;\r
44         }\r
45 \r
46         public string IdAttributeDotReplacement {\r
47             get {\r
48                 if (String.IsNullOrEmpty(_idAttributeDotReplacement)) {\r
49                     _idAttributeDotReplacement = HtmlHelper.IdAttributeDotReplacement;\r
50                 }\r
51                 return _idAttributeDotReplacement;\r
52             }\r
53             set {\r
54                 _idAttributeDotReplacement = value;\r
55             }\r
56         }\r
57 \r
58         public string InnerHtml {\r
59             get {\r
60                 return _innerHtml ?? String.Empty;\r
61             }\r
62             set {\r
63                 _innerHtml = value;\r
64             }\r
65         }\r
66 \r
67         public string TagName {\r
68             get;\r
69             private set;\r
70         }\r
71 \r
72         public void AddCssClass(string value) {\r
73             string currentValue;\r
74 \r
75             if (Attributes.TryGetValue("class", out currentValue)) {\r
76                 Attributes["class"] = value + " " + currentValue;\r
77             }\r
78             else {\r
79                 Attributes["class"] = value;\r
80             }\r
81         }\r
82 \r
83         internal static string CreateSanitizedId(string originalId, string dotReplacement) {\r
84             if (String.IsNullOrEmpty(originalId)) {\r
85                 return null;\r
86             }\r
87 \r
88             char firstChar = originalId[0];\r
89             if (!Html401IdUtil.IsLetter(firstChar)) {\r
90                 // the first character must be a letter\r
91                 return null;\r
92             }\r
93 \r
94             StringBuilder sb = new StringBuilder(originalId.Length);\r
95             sb.Append(firstChar);\r
96 \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
101                 }\r
102                 else {\r
103                     sb.Append(dotReplacement);\r
104                 }\r
105             }\r
106 \r
107             return sb.ToString();\r
108         }\r
109 \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
115                 }\r
116             }\r
117         }\r
118 \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
125                 }\r
126                 string value = HttpUtility.HtmlAttributeEncode(attribute.Value);\r
127                 sb.AppendFormat(CultureInfo.InvariantCulture, _attributeFormat, key, value);\r
128             }\r
129             return sb.ToString();\r
130         }\r
131 \r
132         public void MergeAttribute(string key, string value) {\r
133             MergeAttribute(key, value, false /* replaceExisting */);\r
134         }\r
135 \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
139             }\r
140 \r
141             if (replaceExisting || !Attributes.ContainsKey(key)) {\r
142                 Attributes[key] = value;\r
143             }\r
144         }\r
145 \r
146         public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes) {\r
147             MergeAttributes(attributes, false /* replaceExisting */);\r
148         }\r
149 \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
156                 }\r
157             }\r
158         }\r
159 \r
160         public void SetInnerText(string innerText) {\r
161             InnerHtml = HttpUtility.HtmlEncode(innerText);\r
162         }\r
163 \r
164         internal MvcHtmlString ToMvcHtmlString(TagRenderMode renderMode) {\r
165             return MvcHtmlString.Create(ToString(renderMode));\r
166         }\r
167 \r
168         public override string ToString() {\r
169             return ToString(TagRenderMode.Normal);\r
170         }\r
171 \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
180                 default:\r
181                     return String.Format(CultureInfo.InvariantCulture, _elementFormatNormal, TagName, GetAttributesString(), InnerHtml);\r
182             }\r
183         }\r
184 \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
188                 switch (c) {\r
189                     case '-':\r
190                     case '_':\r
191                     case ':':\r
192                         // note that we're specifically excluding the '.' character\r
193                         return true;\r
194 \r
195                     default:\r
196                         return false;\r
197                 }\r
198             }\r
199 \r
200             private static bool IsDigit(char c) {\r
201                 return ('0' <= c && c <= '9');\r
202             }\r
203 \r
204             public static bool IsLetter(char c) {\r
205                 return (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'));\r
206             }\r
207 \r
208             public static bool IsValidIdCharacter(char c) {\r
209                 return (IsLetter(c) || IsDigit(c) || IsAllowableSpecialCharacter(c));\r
210             }\r
211         }\r
212 \r
213     }\r
214 }\r