//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ using System; using System.Security.Permissions; using System.Web; using System.Web.Caching; using System.Web.Mobile; using System.Web.UI.MobileControls; using System.Web.UI.MobileControls.Adapters; using System.IO; using System.Text; using System.Drawing; using System.Collections; using System.Diagnostics; using System.Collections.Specialized; using System.Globalization; using System.Web.SessionState; #if COMPILING_FOR_SHIPPED_SOURCE namespace System.Web.UI.MobileControls.ShippedAdapterSource.XhtmlAdapters #else namespace System.Web.UI.MobileControls.Adapters.XhtmlAdapters #endif { /// [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)] [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)] [Obsolete("The System.Web.Mobile.dll assembly has been deprecated and should no longer be used. For information about how to develop ASP.NET mobile applications, see http://go.microsoft.com/fwlink/?LinkId=157231.")] public class XhtmlMobileTextWriter : MobileTextWriter { private int _styleCount = 0; private static readonly Style _defaultStyle = new Style (); private static readonly XhtmlStyleClass _defaultStyleClass = new XhtmlStyleClass(_defaultStyle, XhtmlConstants.All); // For certain WML 2.0 devices, we have to render a WML onevent element. // Therefore we need two markup builders -a preWmlOnEventLocation and a postWmlOnEventLocation. // Calling MarkWmlOnEventLocation switches the markup string builder from the pre to the post // builder. Everything is concatenated in order in EndCachedRendering / WriteCachedMarkup. private XhtmlStyleClass _bodyStyle = null; private TextWriter _cachedInnerWriter = null; private String _cacheKey; private String _cachedMarkup; private string _cachedEndTag = null; private bool _cacheKeyValid = false; private bool _cachingRendering; private string _customBodyStyles = null; private IDictionary _doctypeDeclarations = new Hashtable (); private bool _isStyleSheetEmpty = true; private ArrayList _orderedStyleClassKeys = new ArrayList (); private bool _pendingBreak = false; private Stack _physicalCssClasses = new Stack(); private StringBuilder _preWmlOnEventMarkupBuilder = new StringBuilder(); private StringBuilder _postWmlOnEventMarkupBuilder = new StringBuilder(); private string _sessionKey; private bool _sessionKeyValid = false; private IDictionary _styleHash = new Hashtable (); private bool _supportsNoWrapStyle = true; private bool _suppressNewLines = false; private ArrayList _wmlOnEnterForwardVarNames = new ArrayList(); private bool _useDivsForBreaks = false; /// public XhtmlMobileTextWriter (TextWriter writer, MobileCapabilities device) : base(writer, device) { _doctypeDeclarations[Doctype.XhtmlBasic] = ""; _doctypeDeclarations[Doctype.XhtmlMobileProfile] = ""; _doctypeDeclarations[Doctype.Wml20] = ""; } internal String CachedEndTag { get { return _cachedEndTag; } } /// public virtual String CacheKey { get { // Exception is for extensibility scenarios. if (!_cacheKeyValid) { throw new Exception (SR.GetString( SR.XhtmlMobileTextWriter_CacheKeyNotSet)); } return _cacheKey; } } #if UNUSED_CODE internal bool CssClassOnStack { get { return _physicalCssClasses.Count > 0; } } #endif // Saves a couple of lines of code in most cases. internal XhtmlStyleClass CurrentStyleClass { get { if (_styleStack.Count > 0) { StylePair pair = (StylePair) _styleStack.Peek (); return pair.Class; } else { // If the style stack is empty, the current style is default. return _defaultStyleClass; } } } /// public String CustomBodyStyles { get { return _customBodyStyles; } set { _isStyleSheetEmpty = false; _customBodyStyles = value; } } /// public virtual String SessionKey { // Exception is for extensibility scenarios. get { if (!_sessionKeyValid) { throw new Exception (SR.GetString( SR.XhtmlMobileTextWriter_SessionKeyNotSet)); } return _sessionKey; } } public virtual bool SupportsNoWrapStyle { get { return _supportsNoWrapStyle; } set { _supportsNoWrapStyle = value; } } /// public bool SuppressNewLine { get { return _suppressNewLines; } set { _suppressNewLines = value; } } /// public bool UseDivsForBreaks { get { return _useDivsForBreaks; } set { _useDivsForBreaks = value; } } // Add a variable name to clear. /// public virtual void AddOnEnterForwardSetVar(String var) { AddOnEnterForwardSetVar(var, String.Empty); } /// public virtual void AddOnEnterForwardSetVar(String var, String value) { _wmlOnEnterForwardVarNames.Add(new String[2]{var, value}); } /// public virtual void BeginCachedRendering () { _cachedInnerWriter = InnerWriter; InnerWriter = new StringWriter (_preWmlOnEventMarkupBuilder, CultureInfo.InvariantCulture); _cachingRendering = true; } internal void ClearCachedEndTag() { _cachedEndTag = null; } /// public virtual void ClearPendingBreak() { _pendingBreak = false; } //////////////////////////////////////////////////////////////////////////////////////////////////// // Optimization for physical stylesheets. We only write the cssClass attribute if it differs // (case-sensitive) from the current cssClass. The current cssClass is kept in a stack. //////////////////////////////////////////////////////////////////////////////////////////////////// internal bool DiffersFromCurrentPhysicalCssClass(String cssClass) { // Case sensitive comparison. return _physicalCssClasses.Count == 0 || String.Compare(cssClass, (String)_physicalCssClasses.Peek(), StringComparison.Ordinal) != 0; } private void EncodeAttributeValue(String value, StringBuilder encodedValue) { StringWriter writer = new StringWriter(encodedValue, CultureInfo.InvariantCulture); HttpUtility.HtmlEncode(value, writer); } // Expose base method to xhtml control adapter. internal string EncodeUrlInternal(String url) { return base.EncodeUrl(url); } /// public virtual void EndCachedRendering () { StringBuilder cachedMarkupBuilder = new StringBuilder(); cachedMarkupBuilder.Append(_preWmlOnEventMarkupBuilder.ToString()); cachedMarkupBuilder.Append(GetWmlOnEventSubtree()); cachedMarkupBuilder.Append(_postWmlOnEventMarkupBuilder.ToString()); _cachedMarkup = cachedMarkupBuilder.ToString (); InnerWriter = _cachedInnerWriter; _cachingRendering = false; } private XhtmlStyleClass EnsureXhtmlStyleClassInHashtable (XhtmlStyleClass styleClass) { if (styleClass.Filter == StyleFilter.None) { return CurrentStyleClass; } // We hash the style classes by the class definition. String classKey = styleClass.GetClassDefinition (); XhtmlStyleClass existingClass = (XhtmlStyleClass) _styleHash [classKey]; string className = existingClass == null ? "s" + _styleCount++ : existingClass.StyleClassName; if (existingClass == null) { // Used to retrieve style classes in order from the hash table. _orderedStyleClassKeys.Add (classKey); styleClass.StyleClassName = className; _styleHash [classKey] = styleClass; _isStyleSheetEmpty = false; } return existingClass == null ? styleClass : existingClass; } /// public override void EnterFormat(Style style) { StyleFilter filter = CurrentStyleClass.GetFilter(style); EnterStyleInternal(new XhtmlFormatStyleClass(style, filter), "span", null /* no additional attributes */); } /// public override void EnterLayout (Style style) { StyleFilter filter = CurrentStyleClass.GetFilter(style); if (!SupportsNoWrapStyle) { filter &= ~StyleFilter.Wrapping; } EnterStyleInternal (new XhtmlLayoutStyleClass(style, filter), "div", null /* no additional attributes */, true /* force tag to be written */); } // Hiding inherited member works because dynamic type is same as static type. /// public new void EnterStyle (Style style) { StyleFilter filter = CurrentStyleClass.GetFilter(style); if (!SupportsNoWrapStyle) { filter &= ~StyleFilter.Wrapping; } // We prefer span to div because span is inline. if ((filter & XhtmlConstants.Layout) == 0) { EnterStyleInternal (style, "span", filter); } else { EnterStyleInternal (new XhtmlStyleClass(style, filter), "div", null /*additional attributes */, true /* force tag to be written */); } } // This internal overload can be used to enter style by setting the class element // on a caller-specified tag, such as or . internal void EnterStyle(Style style, String styleTag) { StyleFilter filter = CurrentStyleClass.GetFilter(style); if (filter == StyleFilter.None) { WriteFullBeginTag(styleTag); _styleStack.Push(new StylePair(styleTag, style, StyleFilter.None)); return; } EnterStyleInternal (style, styleTag, filter); } // This internal overload can be used to enter style by setting the class element // on a caller-specified tag, such as or
. internal void EnterStyle(XhtmlStyleClass styleClass, String styleTag) { if (styleClass.Filter == StyleFilter.None) { WriteFullBeginTag(styleTag); _styleStack.Push(new StylePair(styleTag, styleClass)); return; } EnterStyleInternal (styleClass, styleTag, null); } private Stack _styleStack = new Stack (); // StyleTag is the tag used for the class attribute. Possible values are div, span, // p, or any elt. Div and span are preferred over p, since p can only contain inline elements. internal void EnterStyleInternal (Style style, String styleTag, StyleFilter filter) { EnterStyleInternal(style, styleTag, filter, null); } internal void EnterStyleInternal (XhtmlStyleClass styleClass, String styleTag, NameValueCollection additionalAttributes) { EnterStyleInternal(styleClass, styleTag, additionalAttributes, false /* force tag to be written */); } internal void EnterStyleInternal (XhtmlStyleClass styleClass, String styleTag, NameValueCollection additionalAttributes, bool forceTag) { // EnterStyle is only expected to be called when _cachingRendering is true. // Specifically, the active form is rendered to the markup cache, then the cached // markup is later rendered to the page. This allows efficient use of CSS. // The if clause exits gracefully if the expected precondition is not met. if (!_cachingRendering) { Debug.Fail ("Only call EnterStyleInternal when caching rendering"); _styleStack.Push (new StylePair (String.Empty, styleClass)); return; } if (styleClass.Filter == StyleFilter.None && !forceTag) { // value comparison WritePendingBreak(); // Push a placeholder for this style with Tag == "", indicating no // tag was written. _styleStack.Push (new StylePair (String.Empty, styleClass)); return; } // Swap styleClass for an equivalent style class already in the hashtable, if there is one. styleClass = EnsureXhtmlStyleClassInHashtable (styleClass); WriteBeginTag (styleTag); if (styleClass != null && styleClass != CurrentStyleClass && styleClass.Filter != StyleFilter.None) { WriteAttribute ("class", styleClass.StyleClassName); } if (additionalAttributes != null) { foreach (String key in additionalAttributes.Keys) { WriteAttribute (key, additionalAttributes[key], true /* encode */); } } Write (">"); _styleStack.Push (new StylePair (styleTag, styleClass)); } internal void EnterStyleInternal (Style style, String styleTag, StyleFilter filter, NameValueCollection additionalAttributes) { EnterStyleInternal(new XhtmlStyleClass(style, filter), styleTag, additionalAttributes, false /* force tag to be written */); } /// public override void ExitFormat (Style style) { ExitStyle (style); } /// public override void ExitFormat (Style style, bool breakafter) { ExitStyle (style); if (breakafter) { SetPendingBreak(); } } /// public override void ExitLayout (Style style) { ExitStyle (style); } /// public override void ExitLayout (Style style, bool breakafter) { ExitStyle (style); if (breakafter) { SetPendingBreak(); } } /// public new void ExitStyle (Style style) { StylePair pair = (StylePair) _styleStack.Pop (); if (pair.Tag != null && pair.Tag.Length > 0) WriteEndTag (pair.Tag); if (IsBlockElement(pair.Tag)) { ClearPendingBreak(); } WriteLine (); } // Please see ASURT 144034 for this device-specific issue. // Internal-only utility to return name of an XhtmlFormatStyleClass that represents the (format) diff of the // parameter from the current style. This does not push the style stack -it is used only for tags with // small content models such as . internal String GetCssFormatClassName(Style style) { if (!_cachingRendering) { Debug.Fail ("Only call when caching rendering"); return null; } if (style == null) { return null; // caller should check for this return value. } // We need to write all non-default properties, so get the filter from the _defaultStyleClass. StyleFilter filter = _defaultStyleClass.GetFilter(style); filter &= XhtmlConstants.Format; if (filter == StyleFilter.None) { return null; } XhtmlFormatStyleClass styleClass = new XhtmlFormatStyleClass(style, filter); // Value returned is a valid styleClass which can be added to the attributes of, e.g., an element // to cause character formatting. Please see 144034 for the device specific issue. XhtmlStyleClass hashStyleClass = EnsureXhtmlStyleClassInHashtable(styleClass); if (hashStyleClass == null) { return null; } return hashStyleClass.StyleClassName; } internal string GetStyles () { StringBuilder styleBuilder = new StringBuilder (); string bodyStyleClassDefinition = _bodyStyle == null ? null : _bodyStyle.GetClassDefinition(); if (bodyStyleClassDefinition != null && bodyStyleClassDefinition.Trim().Length > 0 || _customBodyStyles != null) { styleBuilder.Append("body{\r\n"); styleBuilder.Append(bodyStyleClassDefinition); // null ok. styleBuilder.Append(_customBodyStyles); // null ok. styleBuilder.Append("}\r\n"); } foreach (String key in _orderedStyleClassKeys) { styleBuilder.Append (((XhtmlStyleClass) _styleHash [key]).ToString()); } return styleBuilder.ToString (); } private String GetWmlOnEventSubtree() { if (_wmlOnEnterForwardVarNames.Count == 0) { return String.Empty; } StringBuilder wmlOnEventBuilder = new StringBuilder (); wmlOnEventBuilder.Append("\r\n"); wmlOnEventBuilder.Append("\r\n"); foreach (String[] varInfo in _wmlOnEnterForwardVarNames) { String varName = varInfo[0]; String varValue = varInfo[1]; // Clear each client variable by rendering a setvar. wmlOnEventBuilder.Append("\r\n"); } wmlOnEventBuilder.Append("\r\n"); wmlOnEventBuilder.Append("\r\n"); return wmlOnEventBuilder.ToString(); } private void HandleBreakForTag(String tag) { if (IsBlockElement(tag)) { ClearPendingBreak(); } else { WritePendingBreak(); } } private bool IsBlockElement(String tag){ tag = tag.ToLower(CultureInfo.InvariantCulture); return // From xhtml 1.0 transitional dtd, definition of %block; entity. tag == "p" || // %heading; tag == "h1" || tag == "h2" || tag == "h3" || tag == "h4" || tag == "h5" || tag == "h6" || tag == "div" || // %lists; tag == "ul" || tag == "ol" || tag == "dl" || tag == "menu" || tag == "dir" || // %blocktext; tag == "pre" || tag == "hr" || tag == "blockquote" || tag == "center" || tag == "noframes" || tag == "isindex" || tag == "fieldset" || tag == "table"; } /// public virtual bool IsStyleSheetEmpty () { return _isStyleSheetEmpty; } // Call to switch from markup above the wml:onevent (if any) to below. /// public virtual void MarkWmlOnEventLocation() { InnerWriter = new StringWriter(_postWmlOnEventMarkupBuilder, CultureInfo.InvariantCulture); } internal String PopPhysicalCssClass() { return(String)_physicalCssClasses.Pop(); } internal void PushPhysicalCssClass(String cssClass) { _physicalCssClasses.Push(cssClass); } /// public virtual void SetBodyStyle (Style style) { // Filter is not strictly needed here, since default property values are not written anyway // but it is a good practice to provide a meaningful filter. StyleFilter filter = _defaultStyleClass.GetFilter(style); _bodyStyle = new XhtmlStyleClass (style, filter); _isStyleSheetEmpty = filter == 0; } /// public virtual void SetCacheKey (Cache cache) { String styleText = GetStyles(); _cacheKey = styleText.GetHashCode ().ToString (CultureInfo.InvariantCulture); // Avoid hash collisions and app developer values in cache by finding a new string. while (cache [_cacheKey] != null && (cache [_cacheKey].GetType () != typeof (String) || styleText != (String) cache [_cacheKey])) { _cacheKey += "x"; } _cacheKeyValid = true; } /// public virtual void SetPendingBreak() { _pendingBreak = true; } /// public virtual void SetSessionKey (HttpSessionState session) { String styleText = GetStyles(); _sessionKey = XhtmlConstants.SessionKeyPrefix + styleText.GetHashCode ().ToString (CultureInfo.InvariantCulture); // Avoid hash collisions and app developer values in session by finding a new string. while (session [_sessionKey] != null && (session [_sessionKey].GetType () != typeof (String) || styleText != (String) session [_sessionKey])) { _sessionKey += "x"; } _sessionKeyValid = true; } /// public override void WriteAttribute(String attribute, String value, bool encode) { // If the value is null, we return without writing anything. This is different // from HtmlTextWriter, which writes the name of the attribute, but no value at all. // A name with no value is illegal in Xml. if (value == null) { return; } if (encode) { // Unlike HTML encoding, we need to replace <> with < and >. // For performance reasons we duplicate some code, // rather than piggybacking on the inherited method. Write(' '); Write(attribute); Write("=\""); WriteEncodedAttributeValue(value); Write('\"'); } else { base.WriteAttribute(attribute, value, encode); } } /// public override void WriteBeginTag(String tag) { HandleBreakForTag(tag); base.WriteBeginTag(tag); } /// public new virtual void WriteBreak() { if (UseDivsForBreaks) { if((string)Device["usePOverDiv"] == "true") WriteLine("
"); else WriteLine("
"); } else { WriteLine ("
"); } } // CachedEndTag used for devices that cannot render
/// public virtual void WriteCachedMarkup () { Write (_cachedMarkup); } /// public virtual void WriteDoctypeDeclaration (Doctype type){ WriteLine((String)_doctypeDeclarations[type]); } /// public virtual void WriteEncodedAttributeValue(String value) { StringBuilder builder = new StringBuilder(); EncodeAttributeValue(value, builder); Write(builder.ToString()); } /// public override void WriteEndTag(String tag) { if (IsBlockElement(tag)) { ClearPendingBreak(); } base.WriteEndTag(tag); _cachedEndTag = tag; } /// public override void WriteFullBeginTag(String tag) { HandleBreakForTag(tag); base.WriteFullBeginTag(tag); } /// public virtual void WriteHiddenField(String name, String value) { WriteBeginTag ("input"); WriteAttribute ("type", "hidden"); WriteAttribute ("name", name); WriteAttribute ("value", value, true); WriteLine ("/>"); } // Write a hidden field with no value attribute (useful for __ET hidden field). /// public virtual void WriteHiddenField(String name) { WriteBeginTag ("input"); WriteAttribute ("type", "hidden"); WriteAttribute ("name", name); WriteLine ("/>"); } /// public override void WriteLine() { if (!_suppressNewLines) base.WriteLine(); } /// public override void WriteLine(String format, Object[] arg) { if (_suppressNewLines) { Write(format, arg); } else { base.WriteLine(format, arg); } } /// public override void WriteLine(String format, Object arg) { if (_suppressNewLines) { Write(format, arg); } else { base.WriteLine(format, arg); } } /// public override void WriteLine(String format, Object arg0, Object arg1) { if (_suppressNewLines) { Write(format, arg0, arg1); } else { base.WriteLine(format, arg0, arg1); } } /// public override void WriteLine(Object v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public override void WriteLine(String s) { if (_suppressNewLines) { Write(s); } else { base.WriteLine(s); } } /// public override void WriteLine(Double v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public override void WriteLine(Single v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public override void WriteLine(Int64 v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } // Note: type UInt32 is not CLS compliant, hence no override for UInt32 /// public override void WriteLine(Int32 v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public override void WriteLine(Boolean v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public override void WriteLine(Char[] buffer, Int32 index, Int32 count) { if (_suppressNewLines) { Write(buffer, index, count); } else { base.WriteLine(buffer, index, count); } } /// public override void WriteLine(Char[] v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public override void WriteLine(Char v) { if (_suppressNewLines) { Write(v); } else { base.WriteLine(v); } } /// public virtual void WritePendingBreak(){ if (_pendingBreak) { WriteBreak(); _pendingBreak = false; } } /// public virtual void WriteUrlParameter (String name, String value) { WriteEncodedUrlParameter (name); Write ("="); WriteEncodedUrlParameter (value); } /// public virtual void WriteXmlDeclaration (){ Write (""); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Style stack elements are of type StylePair. //////////////////////////////////////////////////////////////////////////////////////////////////// private struct StylePair { internal StylePair (String styleTag, XhtmlStyleClass styleClass) { Tag = styleTag; Class = styleClass; } internal StylePair (String styleTag, Style style, StyleFilter filter) { Tag = styleTag; Class = new XhtmlStyleClass (style, filter); } // Review: public fields internal String Tag; internal XhtmlStyleClass Class; } } }