forked from AngleSharp/AngleSharp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
XhtmlMarkupFormatter.cs
205 lines (169 loc) · 6.99 KB
/
XhtmlMarkupFormatter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
namespace AngleSharp.Xhtml
{
using AngleSharp.Dom;
using AngleSharp.Text;
using System;
/// <summary>
/// Represents the standard XHTML markup formatter.
/// </summary>
public class XhtmlMarkupFormatter : IMarkupFormatter
{
#region Instance
/// <summary>
/// An instance of the XhtmlMarkupFormatter.
/// </summary>
public static readonly IMarkupFormatter Instance = new XhtmlMarkupFormatter();
#endregion
#region Constructors
/// <summary>
/// Default constructor for <see cref="XhtmlMarkupFormatter"/>
/// </summary>
public XhtmlMarkupFormatter() : this(true)
{
}
/// <summary>
/// Constructor for <see cref="XhtmlMarkupFormatter"/>
/// </summary>
/// <param name="emptyTagsToSelfClosing">Specify if empty elements like <div></div>
/// should be converted to self-closing ones like <div/></param>
public XhtmlMarkupFormatter(Boolean emptyTagsToSelfClosing)
{
_emptyTagsToSelfClosing = emptyTagsToSelfClosing;
}
#endregion
#region Private fields
private readonly Boolean _emptyTagsToSelfClosing;
#endregion
#region Methods
/// <inheritdoc />
public virtual String CloseTag(IElement element, Boolean selfClosing)
{
var prefix = element.Prefix;
var name = element.LocalName;
var tag = !String.IsNullOrEmpty(prefix) ? prefix + ":" + name : name;
return (selfClosing || _emptyTagsToSelfClosing && !element.HasChildNodes) ? String.Empty : String.Concat("</", tag, ">");
}
/// <inheritdoc />
public virtual String Comment(IComment comment) =>
String.Concat("<!--", comment.Data, "-->");
/// <inheritdoc />
public virtual String Doctype(IDocumentType doctype)
{
var publicId = doctype.PublicIdentifier;
var systemId = doctype.SystemIdentifier;
var noExternalId = String.IsNullOrEmpty(publicId) && String.IsNullOrEmpty(systemId);
var externalId = noExternalId ? String.Empty : " " + (String.IsNullOrEmpty(publicId) ?
String.Concat("SYSTEM \"", systemId, "\"") :
String.Concat("PUBLIC \"", publicId, "\" \"", systemId, "\""));
return String.Concat("<!DOCTYPE ", doctype.Name, externalId, ">");
}
/// <inheritdoc />
public virtual String OpenTag(IElement element, Boolean selfClosing)
{
var prefix = element.Prefix;
var temp = StringBuilderPool.Obtain();
temp.Append(Symbols.LessThan);
if (!String.IsNullOrEmpty(prefix))
{
temp.Append(prefix).Append(Symbols.Colon);
}
temp.Append(element.LocalName);
foreach (var attribute in element.Attributes)
{
temp.Append(' ').Append(Attribute(attribute));
}
if (selfClosing || _emptyTagsToSelfClosing && !element.HasChildNodes)
{
temp.Append(" /");
}
temp.Append(Symbols.GreaterThan);
return temp.ToPool();
}
/// <inheritdoc />
public virtual String Processing(IProcessingInstruction processing)
{
var value = String.Concat(processing.Target, " ", processing.Data);
return String.Concat("<?", value, "?>");
}
/// <inheritdoc />
public virtual String LiteralText(ICharacterData text) => text.Data;
/// <inheritdoc />
public virtual String Text(ICharacterData text) => EscapeText(text.Data);
/// <summary>
/// Creates the string representation of the attribute.
/// </summary>
/// <param name="attribute">The attribute to serialize.</param>
/// <returns>The string representation.</returns>
protected virtual String Attribute(IAttr attribute)
{
var namespaceUri = attribute.NamespaceUri;
var localName = attribute.LocalName;
var value = attribute.Value;
var temp = StringBuilderPool.Obtain();
if (String.IsNullOrEmpty(namespaceUri))
{
temp.Append(localName);
}
else if (namespaceUri.Is(NamespaceNames.XmlUri))
{
temp.Append(NamespaceNames.XmlPrefix).Append(Symbols.Colon).Append(localName);
}
else if (namespaceUri.Is(NamespaceNames.XLinkUri))
{
temp.Append(NamespaceNames.XLinkPrefix).Append(Symbols.Colon).Append(localName);
}
else if (namespaceUri.Is(NamespaceNames.XmlNsUri))
{
temp.Append(XmlNamespaceLocalName(localName));
}
else
{
temp.Append(attribute.Name);
}
temp.Append(Symbols.Equality).Append(Symbols.DoubleQuote);
for (var i = 0; i < value.Length; i++)
{
switch (value[i])
{
case Symbols.Ampersand: temp.Append("&"); break;
case Symbols.NoBreakSpace: temp.Append(" "); break;
case Symbols.LessThan: temp.Append("<"); break;
case Symbols.DoubleQuote: temp.Append("""); break;
default: temp.Append(value[i]); break;
}
}
return temp.Append(Symbols.DoubleQuote).ToPool();
}
#endregion
#region Helpers
/// <summary>
/// Escapes the given text by replacing special characters with their
/// XHTML entity (amp, nbsp as numeric value, lt, and gt).
/// </summary>
/// <param name="content">The string to alter.</param>
/// <returns>The altered string.</returns>
public static String EscapeText(String content)
{
var temp = StringBuilderPool.Obtain();
for (var i = 0; i < content.Length; i++)
{
switch (content[i])
{
case Symbols.Ampersand: temp.Append("&"); break;
case Symbols.NoBreakSpace: temp.Append(" "); break;
case Symbols.GreaterThan: temp.Append(">"); break;
case Symbols.LessThan: temp.Append("<"); break;
default: temp.Append(content[i]); break;
}
}
return temp.ToPool();
}
/// <summary>
/// Gets the local name using the XML namespace prefix if required.
/// </summary>
/// <param name="name">The name to be properly represented.</param>
/// <returns>The string representation.</returns>
public static String XmlNamespaceLocalName(String name) => !name.Is(NamespaceNames.XmlNsPrefix) ? String.Concat(NamespaceNames.XmlNsPrefix, ":") : name;
#endregion
}
}