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;
55 public HtmlEmitter (TextWriter writer, XslOutput output)
58 indent = output.Indent == "yes" || output.Indent == null;
59 elementNameStack = new Stack ();
61 outputEncoding = writer.Encoding == null ? output.Encoding : writer.Encoding;
62 mediaType = output.MediaType;
63 if (mediaType == null || mediaType.Length == 0)
64 mediaType = "text/html";
67 public override void WriteStartDocument (Encoding encoding, StandaloneType standalone)
72 public override void WriteEndDocument ()
77 public override void WriteDocType (string name, string publicId, string systemId)
79 writer.Write ("<!DOCTYPE html ");
80 if (publicId != null) {
81 writer.Write ("PUBLIC \"");
82 writer.Write (publicId);
84 if (systemId != null) {
86 writer.Write (systemId);
89 } else if (systemId != null) {
90 writer.Write ("SYSTEM \"");
91 writer.Write (systemId);
99 private void CloseAttribute ()
102 openAttribute = false;
105 private void CloseStartElement ()
107 //FIXME: consider sanity check if (openElement) return;
113 if (outputEncoding != null && elementNameStack.Count > 0) {
114 string name = ((string) elementNameStack.Peek ()).ToUpper (CultureInfo.InvariantCulture);
117 WriteStartElement (String.Empty, "META", String.Empty);
118 WriteAttributeString (String.Empty, "http-equiv", String.Empty, "Content-Type");
119 WriteAttributeString (String.Empty, "content", String.Empty, String.Concat (mediaType, "; charset=", outputEncoding.WebName));
125 for (int i = 0; i <= elementNameStack.Count; i++)
132 // FIXME: check all HTML elements' indentation.
133 private void Indent (string elementName, bool endIndent)
137 switch (elementName.ToUpper (CultureInfo.InvariantCulture)) {
169 writer.Write (writer.NewLine);
170 int count = elementNameStack.Count;
171 for (int i = 0; i < count; i++)
175 if (elementName.Length > 0 && nonHtmlDepth > 0)
181 public override void WriteStartElement (string prefix, string localName, string nsURI)
184 CloseStartElement ();
185 Indent (elementNameStack.Count > 0 ? elementNameStack.Peek () as String : String.Empty, false);
186 string formatName = localName;
189 if (nsURI != String.Empty) {// && !IsHtmlElement (localName)) {
191 if (prefix != String.Empty) {
192 formatName = String.Concat (prefix, ":", localName);
195 if (nonHtmlDepth < 0)
196 nonHtmlDepth = elementNameStack.Count + 1;
198 writer.Write (formatName);
199 elementNameStack.Push (formatName);
203 private bool IsHtmlElement (string localName)
205 // see http://www.w3.org/TR/html401/index/elements.html
206 switch (localName.ToUpper (CultureInfo.InvariantCulture)) {
207 case "A": case "ABBR": case "ACRONYM":
208 case "ADDRESS": case "APPLET": case "AREA":
209 case "B": case "BASE": case "BASEFONT": case "BDO": case "BIG":
210 case "BLOCKQUOTE": case "BODY": case "BR": case "BUTTON":
211 case "CAPTION": case "CENTER": case "CITE":
212 case "CODE": case "COL": case "COLGROUP":
213 case "DD": case "DEL": case "DFN": case "DIR":
214 case "DIV": case "DL": case "DT":
216 case "FIELDSET": case "FONT": case "FORM": case "FRAME": case "FRAMESET":
217 case "H1": case "H2": case "H3": case "H4": case "H5": case "H6":
218 case "HEAD": case "HR": case "HTML":
219 case "I": case "IFRAME": case "IMG":
220 case "INPUT": case "INS": case "ISINDEX":
222 case "LABEL": case "LEGEND": case "LI": case "LINK":
223 case "MAP": case "MENU": case "META":
224 case "NOFRAMES": case "NOSCRIPT":
225 case "OBJECT": case "OL": case "OPTGROUP": case "OPTION":
226 case "P": case "PARAM": case "PRE":
228 case "S": case "SAMP": case "SCRIPT": case "SELECT":
229 case "SMALL": case "SPAN": case "STRIKE": case "STRONG":
230 case "STYLE": case "SUB": case "SUP":
231 case "TABLE": case "TBODY": case "TD": case "TEXTAREA":
232 case "TFOOT": case "TH": case "THEAD": case "TITLE":
233 case "TR": case "TT": case "U": case "UL": case "VAR":
239 public override void WriteEndElement ()
241 WriteFullEndElement ();
244 public override void WriteFullEndElement ()
246 string element = elementNameStack.Peek () as string;
247 switch (element.ToUpper (CultureInfo.InvariantCulture)) {
264 writer.Write ('>'); //FIXME: consider using CloseStartElement() to write '>'
265 elementNameStack.Pop ();
269 CloseStartElement ();
270 elementNameStack.Pop ();
271 if (IsHtmlElement (element))
272 Indent (element as string, true);
274 writer.Write (element);
278 if (nonHtmlDepth > elementNameStack.Count)
283 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
286 if (prefix != null && prefix.Length!=0)
288 writer.Write (prefix);
291 writer.Write (localName);
293 if (nonHtmlDepth >= 0)
295 writer.Write ("=\"");
296 openAttribute = true;
297 WriteFormattedString (value);
298 openAttribute = false;
304 string attribute = localName.ToUpper (CultureInfo.InvariantCulture);
305 string element = ((string) elementNameStack.Peek ()).ToLower (CultureInfo.InvariantCulture);
307 if (attribute == "SELECTED" && element == "option"
308 || attribute == "CHECKED" && element == "input")
311 writer.Write ("=\"");
312 openAttribute = true;
314 // URI attribute should be escaped.
315 string attrName = null;
316 string [] attrNames = null;
334 attrName = "profile";
337 attrNames = new string [] {"src", "usemap"};
340 attrNames = new string [] {"src", "usemap", "longdesc"};
343 attrNames = new string [] {"classid", "codebase", "data", "archive", "usemap"};
346 attrNames = new string [] {"src", "for"};
349 if (attrNames != null) {
350 string attr = localName.ToLower (CultureInfo.InvariantCulture);
351 foreach (string a in attrNames) {
353 value = HtmlUriEscape.EscapeUri (value);
358 else if (attrName != null && attrName == localName.ToLower (CultureInfo.InvariantCulture))
359 value = HtmlUriEscape.EscapeUri (value);
360 WriteFormattedString (value);
361 openAttribute = false;
365 class HtmlUriEscape : Uri
367 private HtmlUriEscape () : base ("urn:foo") {}
369 public static string EscapeUri (string input)
371 StringBuilder sb = new StringBuilder ();
373 for (int i = 0; i < input.Length; i++) {
375 if (c < 32 || c > 127)
377 bool preserve = false;
387 preserve = HtmlUriEscape.IsExcludedCharacter (c);
391 sb.Append (EscapeString (input.Substring (start, i - start)));
396 if (start < input.Length)
397 sb.Append (EscapeString (input.Substring (start)));
398 return sb.ToString ();
402 public override void WriteComment (string text) {
404 CloseStartElement ();
405 writer.Write ("<!--");
407 writer.Write ("-->");
410 public override void WriteProcessingInstruction (string name, string text)
412 if ((text.IndexOf("?>") > 0))
413 throw new ArgumentException ("Processing instruction cannot contain \"?>\" as its value.");
415 CloseStartElement ();
417 if (elementNameStack.Count > 0)
418 Indent (elementNameStack.Peek () as string, false);
422 if (text != null && text != String.Empty) {
427 if (nonHtmlDepth >= 0)
430 writer.Write (">"); // HTML PI ends with '>'
433 public override void WriteString (string text)
436 CloseStartElement ();
437 WriteFormattedString (text);
440 private void WriteFormattedString (string text)
442 // style and script should not be escaped.
443 if (!openAttribute && elementNameStack.Count > 0) {
444 string element = ((string) elementNameStack.Peek ()).ToUpper (CultureInfo.InvariantCulture);
454 for (int i = 0; i < text.Length; i++) {
457 // '&' '{' should be "&{", not "&{"
458 if (nonHtmlDepth < 0 && i + 1 < text.Length && text [i + 1] == '{')
460 writer.Write (text.ToCharArray (), start, i - start);
461 writer.Write ("&");
467 writer.Write (text.ToCharArray (), start, i - start);
468 writer.Write ("<");
474 writer.Write (text.ToCharArray (), start, i - start);
475 writer.Write (">");
481 writer.Write (text.ToCharArray (), start, i - start);
482 writer.Write (""");
487 if (text.Length > start)
488 writer.Write (text.ToCharArray (), start, text.Length - start);
491 public override void WriteRaw (string data)
494 CloseStartElement ();
498 public override void WriteCDataSection (string text) {
500 CloseStartElement ();
501 // writer.Write ("<![CDATA[");
503 // writer.Write ("]]>");
506 public override void WriteWhitespace (string value)
509 CloseStartElement ();
510 writer.Write (value);
513 public override void Done ()