5 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
7 // (C)2003 Atsushi Enomoto
10 // indent, uri escape, allowed entity char such as ,
11 // encoding to meta tag, doctype-public/doctype-system.
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System
.Collections
;
37 using System
.Globalization
;
42 namespace Mono
.Xml
.Xsl
44 internal class HtmlEmitter
: Emitter
47 Stack elementNameStack
;
52 Encoding outputEncoding
;
54 public HtmlEmitter (TextWriter writer
, XslOutput output
)
57 indent
= !(output
.Indent
== "no");
58 elementNameStack
= new Stack ();
60 outputEncoding
= writer
.Encoding
== null ? output
.Encoding
: writer
.Encoding
;
63 public override void WriteStartDocument (Encoding encoding
, StandaloneType standalone
)
68 public override void WriteEndDocument ()
73 public override void WriteDocType (string name
, string publicId
, string systemId
)
75 writer
.Write ("<!DOCTYPE html ");
76 if (publicId
!= null && publicId
!= String
.Empty
) {
77 writer
.Write ("PUBLIC \"");
78 writer
.Write (publicId
);
80 if (systemId
!= null && systemId
!= String
.Empty
) {
82 writer
.Write (systemId
);
85 } else if (systemId
!= null && systemId
!= String
.Empty
) {
86 writer
.Write ("SYSTEM \"");
87 writer
.Write (systemId
);
95 private void CloseAttribute ()
98 openAttribute
= false;
101 private void CloseStartElement ()
108 if (outputEncoding
!= null && elementNameStack
.Count
> 0) {
109 string name
= ((string) elementNameStack
.Peek ()).ToUpper (CultureInfo
.InvariantCulture
);
112 WriteStartElement (String
.Empty
, "META", String
.Empty
);
113 WriteAttributeString (String
.Empty
, "http-equiv", String
.Empty
, "Content-Type");
114 WriteAttributeString (String
.Empty
, "content", String
.Empty
, "text/html; charset=" + outputEncoding
.WebName
);
120 for (int i
= 0; i
<= elementNameStack
.Count
; i
++)
127 // FIXME: check all HTML elements' indentation.
128 private void Indent (string elementName
, bool endIndent
)
132 switch (elementName
.ToUpper (CultureInfo
.InvariantCulture
)) {
164 writer
.Write (writer
.NewLine
);
165 int count
= elementNameStack
.Count
;
166 for (int i
= 0; i
< count
; i
++)
170 if (elementName
.Length
> 0 && nonHtmlDepth
> 0)
176 public override void WriteStartElement (string prefix
, string localName
, string nsURI
)
179 CloseStartElement ();
180 Indent (elementNameStack
.Count
> 0 ? elementNameStack
.Peek () as String
: String
.Empty
, false);
181 string formatName
= localName
;
184 if (nsURI
!= String
.Empty
|| !IsHtmlElement (localName
)) {
186 if (prefix
!= String
.Empty
) {
187 writer
.Write (prefix
);
189 formatName
= String
.Concat (prefix
, ":", localName
);
192 if (nonHtmlDepth
< 0)
193 nonHtmlDepth
= elementNameStack
.Count
+ 1;
195 writer
.Write (formatName
);
196 elementNameStack
.Push (formatName
);
200 private bool IsHtmlElement (string localName
)
202 // see http://www.w3.org/TR/html401/index/elements.html
203 switch (localName
.ToUpper (CultureInfo
.InvariantCulture
)) {
204 case "A": case "ABBR": case "ACRONYM":
205 case "ADDRESS": case "APPLET": case "AREA":
206 case "B": case "BASE": case "BASEFONT": case "BDO": case "BIG":
207 case "BLOCKQUOTE": case "BODY": case "BR": case "BUTTON":
208 case "CAPTION": case "CENTER": case "CITE":
209 case "CODE": case "COL": case "COLGROUP":
210 case "DD": case "DEL": case "DFN": case "DIR":
211 case "DIV": case "DL": case "DT":
213 case "FIELDSET": case "FONT": case "FORM": case "FRAME": case "FRAMESET":
214 case "H1": case "H2": case "H3": case "H4": case "H5": case "H6":
215 case "HEAD": case "HR": case "HTML":
216 case "I": case "IFRAME": case "IMG":
217 case "INPUT": case "INS": case "ISINDEX":
219 case "LABEL": case "LEGEND": case "LI": case "LINK":
220 case "MAP": case "MENU": case "META":
221 case "NOFRAMES": case "NOSCRIPT":
222 case "OBJECT": case "OL": case "OPTGROUP": case "OPTION":
223 case "P": case "PARAM": case "PRE":
225 case "S": case "SAMP": case "SCRIPT": case "SELECT":
226 case "SMALL": case "SPAN": case "STRIKE": case "STRONG":
227 case "STYLE": case "SUB": case "SUP":
228 case "TABLE": case "TBODY": case "TD": case "TEXTAREA":
229 case "TFOOT": case "TH": case "THEAD": case "TITLE":
230 case "TR": case "TT": case "U": case "UL": case "VAR":
236 public override void WriteEndElement ()
238 WriteFullEndElement ();
241 public override void WriteFullEndElement ()
243 string element
= elementNameStack
.Peek () as string;
244 switch (element
.ToUpper (CultureInfo
.InvariantCulture
)) {
261 elementNameStack
.Pop ();
265 CloseStartElement ();
266 elementNameStack
.Pop ();
267 if (IsHtmlElement (element
))
268 Indent (element
as string, true);
270 writer
.Write (element
);
274 if (nonHtmlDepth
> elementNameStack
.Count
)
279 public override void WriteAttributeString (string prefix
, string localName
, string nsURI
, string value)
281 if (nonHtmlDepth
>= 0) {
283 writer
.Write (localName
);
284 writer
.Write ("=\"");
285 openAttribute
= true;
286 WriteFormattedString (value);
287 openAttribute
= false;
293 string attribute
= localName
.ToUpper (CultureInfo
.InvariantCulture
);
295 writer
.Write (localName
);
304 writer
.Write ("=\"");
305 openAttribute
= true;
307 // URI attribute should be escaped.
308 string element
= ((string) elementNameStack
.Peek ()).ToLower (CultureInfo
.InvariantCulture
);
309 string attrName
= null;
310 string [] attrNames
= null;
328 attrName
= "profile";
331 attrNames
= new string [] {"src", "usemap"}
;
334 attrNames
= new string [] {"src", "usemap", "longdesc"}
;
337 attrNames
= new string [] {"classid", "codebase", "data", "archive", "usemap"}
;
340 attrNames
= new string [] {"src", "for"}
;
343 if (attrNames
!= null) {
344 string attr
= localName
.ToLower (CultureInfo
.InvariantCulture
);
345 foreach (string a
in attrNames
) {
347 value = HtmlUriEscape
.EscapeUri (value);
352 else if (attrName
!= null && attrName
== localName
.ToLower (CultureInfo
.InvariantCulture
))
353 value = HtmlUriEscape
.EscapeUri (value);
354 WriteFormattedString (value);
355 openAttribute
= false;
359 class HtmlUriEscape
: Uri
361 private HtmlUriEscape () : base ("urn:foo") {}
363 public static string EscapeUri (string input
)
365 StringBuilder sb
= new StringBuilder ();
367 for (int i
= 0; i
< input
.Length
; i
++) {
369 if (c
< 32 || c
> 127)
371 bool preserve
= false;
381 preserve
= HtmlUriEscape
.IsExcludedCharacter (c
);
385 sb
.Append (EscapeString (input
.Substring (start
, i
- start
)));
390 if (start
< input
.Length
)
391 sb
.Append (EscapeString (input
.Substring (start
)));
392 return sb
.ToString ();
396 public override void WriteComment (string text
) {
398 CloseStartElement ();
399 writer
.Write ("<!--");
401 writer
.Write ("-->");
404 public override void WriteProcessingInstruction (string name
, string text
)
406 if ((text
.IndexOf("?>") > 0))
407 throw new ArgumentException ("Processing instruction cannot contain \"?>\" as its value.");
409 CloseStartElement ();
411 if (elementNameStack
.Count
> 0)
412 Indent (elementNameStack
.Peek () as string, false);
416 if (text
!= null && text
!= String
.Empty
) {
421 if (nonHtmlDepth
>= 0)
424 writer
.Write (">"); // HTML PI ends with '>'
427 public override void WriteString (string text
)
430 CloseStartElement ();
431 WriteFormattedString (text
);
434 private void WriteFormattedString (string text
)
436 // style and script should not be escaped.
437 if (!openAttribute
) {
438 string element
= ((string) elementNameStack
.Peek ()).ToUpper (CultureInfo
.InvariantCulture
);
448 for (int i
= 0; i
< text
.Length
; i
++) {
451 // '&' '{' should be "&{", not "&{"
452 if (nonHtmlDepth
< 0 && i
+ 1 < text
.Length
&& text
[i
+ 1] == '{')
454 writer
.Write (text
.ToCharArray (), start
, i
- start
);
455 writer
.Write ("&");
461 writer
.Write (text
.ToCharArray (), start
, i
- start
);
462 writer
.Write ("<");
468 writer
.Write (text
.ToCharArray (), start
, i
- start
);
469 writer
.Write (">");
475 writer
.Write (text
.ToCharArray (), start
, i
- start
);
476 writer
.Write (""");
481 if (text
.Length
> start
)
482 writer
.Write (text
.ToCharArray (), start
, text
.Length
- start
);
485 public override void WriteRaw (string data
)
488 CloseStartElement ();
492 public override void WriteCDataSection (string text
) {
494 CloseStartElement ();
495 writer
.Write ("<![CDATA[");
497 writer
.Write ("]]>");
500 public override void WriteWhitespace (string value)
503 CloseStartElement ();
504 writer
.Write (value);
507 public override void Done ()