//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ #if WMLSUPPORT namespace System.Web.UI { using System.Collections; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Security.Permissions; using System.Web.UI.Adapters; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.Adapters; using System.Web.Util; /// /// [To be supplied.] /// public class WmlTextWriter : HtmlTextWriter { private static readonly char[] _attributeCharacters = new char[] {'"', '&', '<', '>', '$'}; private static Layout _defaultLayout = new Layout(HorizontalAlign.NotSet, true); public const String PostBackWithVarsCardID = "__pbc1"; private bool _alwaysScrambleClientIDs = false; private EmptyTextWriter _analyzeWriter; private bool _analyzeMode = false; private const String _boldTag = "b"; private bool _boldTagOpen = false; // UNDONE: This HttpBrowserCapabilities instance couples the writer with the HttpContext and can be removed. private HttpBrowserCapabilities _browser = null; private IDictionary _controlShortNames = null; private HtmlForm _currentForm = null; private static Style _defaultStyle = new Style(); private bool _paragraphOpen = false; private const String _italicTag = "i"; private bool _italicTagOpen = false; private const String _largeTag = "big"; private bool _largeTagOpen = false; private Stack _layoutStack = new Stack(); private const int _maxShortNameLength = 16; private int _numberOfSoftkeys; private bool _openingPWritten = true; // True if the current control was immediately preceded by an opening p. Valid between BeginRender and EndRender. private bool _pendingP = false; private Stack _panelStyleStack = new Stack(); private const String _postBackEventArgumentVarName = "mcsva"; private const String _postBackEventTargetVarName = "mcsvt"; private IDictionary _radioButtonGroups = new ListDictionary(); private static Random _random = new Random(); private TextWriter _realInnerWriter; private const String _shortNamePrefix = "mcsv"; private const String _smallTag = "small"; private bool _smallTagOpen = false; private Stack _styleStack = new Stack(); private bool _topOfFormOrPanel = false; // True if at top of form or panel, before opening p. public WmlTextWriter(TextWriter writer) : this(writer, DefaultTabString) { } public WmlTextWriter(TextWriter writer, string tabString) : base(writer, tabString) { _realInnerWriter = writer; _numberOfSoftkeys = Convert.ToInt32(Browser["NumberOfSoftkeys"], CultureInfo.InvariantCulture); if (_numberOfSoftkeys > 2) { _numberOfSoftkeys = 2; } } /// /// [To be supplied.] /// // AnalyzeMode is set to true during first analysis pass of rendering. public bool AnalyzeMode { get { return _analyzeMode; } } // UNDONE: This property couples the writer to the HttpContext and can be removed. private HttpBrowserCapabilities Browser { get { if (_browser == null && HttpContext.Current != null) { _browser = HttpContext.Current.Request.Browser; } return _browser; } } /// /// [To be supplied.] /// public virtual HtmlForm CurrentForm { set { _currentForm = value; } get { return _currentForm; } } /// /// [To be supplied.] /// private Layout CurrentLayout { get { if (_layoutStack.Count > 0) { return(Layout) _layoutStack.Peek(); } else { return _defaultLayout; } } } /// /// [To be supplied.] /// public Style CurrentStyle { get { if (_styleStack.Count > 0) { return(Style)_styleStack.Peek(); } else { return DefaultStyle; } } } /// /// [To be supplied.] /// protected virtual Style DefaultStyle { get { return _defaultStyle; } } /// /// [To be supplied.] /// protected int NumberOfSoftkeys { get { return _numberOfSoftkeys; } } // See Whidbey 33012. internal override bool SkipRenderDelegates { get { return AnalyzeMode; } } /// /// True if at top of form or panel, before opening p. /// public bool TopOfForm { get { return _topOfFormOrPanel; } } /// /// [To be supplied.] /// // UNDONE: See whether this method can be replaced by calls to RenderBeginTag. public override void BeginRender() { if (AnalyzeMode) { return; } _openingPWritten = WritePendingP(); } /// /// [To be supplied.] /// internal void BeginFormOrPanel() { if (AnalyzeMode) { return; } _topOfFormOrPanel = true; } /// /// [To be supplied.] /// private void BeginBlockLevelControl() { if (AnalyzeMode) { return; } if (_openingPWritten || _topOfFormOrPanel) { return; } CloseParagraph(); OpenParagraph(); } // Helper to reset all the internal state whenever Analyze mode changes. private void ClearFlags() { _paragraphOpen = false; _italicTagOpen = false; _largeTagOpen = false; _layoutStack = new Stack(); _openingPWritten = true; // True if the current control was immediately preceded by an opening p. Valid between BeginRender and EndRender. _pendingP = false; _panelStyleStack = new Stack(); _smallTagOpen = false; _styleStack = new Stack(); _topOfFormOrPanel = false; // True if at top of form or panel, before opening p. } /// /// Close any open paragraph. /// protected internal virtual void CloseParagraph() { if (!_paragraphOpen) { return; } CloseCurrentStyleTags(); Indent--; WriteLine(); WriteEndTag("p"); WriteLine(); _paragraphOpen = false; } /// /// Close any open character formatting tags. Public for TextBox adapter. /// internal void CloseCurrentStyleTags() { if (_largeTagOpen) { WriteEndTag(_largeTag); _largeTagOpen = false; } if (_smallTagOpen) { WriteEndTag(_smallTag); _smallTagOpen = false; } if (_italicTagOpen) { WriteEndTag(_italicTag); _italicTagOpen = false; } if (_boldTagOpen) { WriteEndTag(_boldTag); _boldTagOpen = false; } } /// /// [To be supplied.] /// private void EndBlockLevelControl() { if (AnalyzeMode) { return; } _pendingP = true; } /// /// [To be supplied.] /// public override void EnterStyle(Style style, HtmlTextWriterTag tag) { // Ignore tag for wml. if (AnalyzeMode) { return; } // All "block level controls" (controls that render using block level elements in HTML) call enterStyle // using a div. Here we ensure that a new p is open for these controls to ensure line breaking behavior. if (tag == HtmlTextWriterTag.Div) { BeginBlockLevelControl(); } Style stackStyle = new Style(); stackStyle.CopyFrom(style); stackStyle.MergeWith(CurrentStyle); if (_panelStyleStack.Count > 0) { stackStyle.MergeWith((Style)_panelStyleStack.Peek()); } _styleStack.Push(stackStyle); // updates CurrentStyle if (_paragraphOpen) { OpenCurrentStyleTags(); } } /// /// [To be supplied.] /// public override void ExitStyle(Style style, HtmlTextWriterTag tag) { // Ignore tag for wml. if (AnalyzeMode) { return; } // No need to call CloseCurrentStyleTags() here, because OpenCurrentStyleTags() closes anything // that is not current and already open. Call to CurrentStyleTags() results in correct but // unnecessary extra tags. VSWhidbey 156207. if (_styleStack.Count > 0) { _styleStack.Pop(); } OpenCurrentStyleTags(); // All "block level controls" (controls that render using block level elements in HTML) call exitStyle // using a div. Here we ensure that a new p is open for these controls to ensure line breaking behavior. if (tag == HtmlTextWriterTag.Div) { EndBlockLevelControl(); } } /// /// Escape '&' in XML if it hasn't been. /// internal String EscapeAmpersand(String url) { if (url == null) { return null; } char ampersand = '&'; string ampEscaped = "amp;"; int ampPos = url.IndexOf(ampersand); while (ampPos != -1) { if (url.Length - ampPos <= ampEscaped.Length || url.Substring(ampPos + 1, ampEscaped.Length) != ampEscaped) { url = url.Insert(ampPos + 1, ampEscaped); } ampPos = url.IndexOf(ampersand, ampPos + ampEscaped.Length + 1); } return url; } /// /// [To be supplied.] /// public override void Flush() { if (AnalyzeMode) { return; } base.Flush(); } /// /// Makes sure the writer has rendered character formatting tags corresponding to the current format. /// private String GetRandomID(int length) { Byte[] randomBytes = new Byte[length]; _random.NextBytes(randomBytes); char[] randomChars = new char[length]; for (int i = 0; i < length; i++) { randomChars[i] = (char)((((int)randomBytes[i]) % 26) + 'a'); } return new String(randomChars); } /// /// /// MapClientIDToShortName provides a unique map of control ClientID properties /// to shorter names. In cases where a control has a very long ClientID, a /// shorter unique name is used. All references to the client ID on the page /// are mapped, resulting in the same postback regardless of mapping. /// MapClientIDToShortName also scrambles client IDs that need to be /// scrambled for security reasons. /// /// protected internal String MapClientIDToShortName(String clientID, bool generateRandomID) { if (_alwaysScrambleClientIDs) { generateRandomID = true; } if (_controlShortNames != null) { String lookup = (String)_controlShortNames[clientID]; if (lookup != null) { return lookup; } } if (!generateRandomID) { bool shortID = clientID.Length < _maxShortNameLength; // Map names with underscores, colons, and conflicting names regardless of length. bool goodID = (clientID.IndexOf(':') == -1) && (clientID.IndexOf('_') == -1) && !NameConflicts(clientID); if (shortID && goodID) { return clientID; } } if (_controlShortNames == null) { _controlShortNames = new ListDictionary(); } String shortName; if (generateRandomID) { shortName = GetRandomID(5); } else { shortName = String.Empty; } shortName = String.Concat(_shortNamePrefix, shortName, _controlShortNames.Count.ToString(CultureInfo.InvariantCulture)); _controlShortNames[clientID] = shortName; return shortName; } /// /// [To be supplied.] /// private bool NameConflicts(String name) { if (name == null) { return false; } Debug.Assert(_postBackEventTargetVarName.ToLower(CultureInfo.InvariantCulture) == _postBackEventTargetVarName && _postBackEventArgumentVarName.ToLower(CultureInfo.InvariantCulture) == _postBackEventArgumentVarName && _shortNamePrefix.ToLower(CultureInfo.InvariantCulture) == _shortNamePrefix); name = name.ToLower(CultureInfo.InvariantCulture); return name == _postBackEventTargetVarName || name == _postBackEventArgumentVarName || StringUtil.StringStartsWith(name, _shortNamePrefix); } /// /// [To be supplied.] /// public virtual void OpenParagraph() { Layout layout = CurrentLayout; OpenParagraph(layout, (layout != null) && (layout.Align != HorizontalAlign.NotSet), (layout != null) && !layout.Wrap); } /// /// [To be supplied.] /// private void OpenParagraph(Layout layout, bool writeHorizontalAlign, bool writeWrapping) { CloseParagraph(); // does nothing if paragraph is not open. WriteBeginTag("p"); if (writeHorizontalAlign) { String alignment; switch (layout.Align) { case HorizontalAlign.Right: alignment = "right"; break; case HorizontalAlign.Center: alignment = "center"; break; default: alignment = "left"; break; } WriteAttribute("align", alignment); } if (writeWrapping) { WriteAttribute("mode", layout.Wrap == true ? "wrap" : "nowrap"); } Write(">"); _paragraphOpen = true; Indent++; WriteLine(); } /// /// [To be supplied.] /// internal void OpenCurrentStyleTags() { // Review: This results in extra tags in some situations. May want to get rid of the extra tags and make // this logic more object oriented. // Opening font tags are always kept in order in order , , . If anything is open out of order, // for example is open and we want to open , we close the "out of order" tag, then reopen it later, // if necessary. Style format = CurrentStyle; // First close all open elements we no longer want. if (_smallTagOpen) { if (format.Font.Size == FontUnit.Empty || format.Font.Size == FontUnit.Larger || format.Font.Size == FontUnit.Large || format.Font.Size == FontUnit.XLarge || format.Font.Size == FontUnit.XXLarge) { WriteEndTag(_smallTag); _smallTagOpen = false; } } if (_largeTagOpen) { if (format.Font.Size == FontUnit.Empty || format.Font.Size == FontUnit.Smaller || format.Font.Size == FontUnit.Small || format.Font.Size == FontUnit.XSmall || format.Font.Size == FontUnit.XXSmall) { WriteEndTag(_largeTag); _largeTagOpen = false; } } if (!format.Font.Italic && _italicTagOpen) { if (_smallTagOpen) { WriteEndTag(_smallTag); _smallTagOpen = false; } if (_largeTagOpen) { WriteEndTag(_largeTag); _largeTagOpen = false; } WriteEndTag(_italicTag); _italicTagOpen = false; } if (!format.Font.Bold && _boldTagOpen) { if (_smallTagOpen) { WriteEndTag(_smallTag); _smallTagOpen = false; } if (_largeTagOpen) { WriteEndTag(_largeTag); _largeTagOpen = false; } if (_italicTagOpen) { WriteEndTag(_italicTag); _italicTagOpen = false; } WriteEndTag(_boldTag); _boldTagOpen = false; } // Now open any elements we need which are not yet open. if (format.Font.Bold && !_boldTagOpen) { if (_smallTagOpen) { WriteEndTag(_smallTag); _smallTagOpen = false; } if (_largeTagOpen) { WriteEndTag(_largeTag); _largeTagOpen = false; } if (_italicTagOpen) { WriteEndTag(_italicTag); _italicTagOpen = false; } WriteFullBeginTag(_boldTag); _boldTagOpen = true; } if (format.Font.Italic && !_italicTagOpen) { if (_smallTagOpen) { WriteEndTag(_smallTag); _smallTagOpen = false; } if (_largeTagOpen) { WriteEndTag(_largeTag); _largeTagOpen = false; } WriteFullBeginTag(_italicTag); _italicTagOpen = true; } if (format.Font.Size != FontUnit.Empty) { if (format.Font.Size == FontUnit.Larger || format.Font.Size == FontUnit.Large || format.Font.Size == FontUnit.XLarge || format.Font.Size == FontUnit.XXLarge) { if (!_largeTagOpen) { WriteFullBeginTag(_largeTag); _largeTagOpen = true; } } if (format.Font.Size == FontUnit.Smaller || format.Font.Size == FontUnit.Small || format.Font.Size == FontUnit.XSmall || format.Font.Size == FontUnit.XXSmall) { if (!_smallTagOpen) { WriteFullBeginTag(_smallTag); _smallTagOpen = true; } } } } /// /// [To be supplied.] /// public void PopLayout() { if (_layoutStack.Count == 0) { Debug.Fail("Layout stack is empty."); return; } _layoutStack.Pop(); } /// /// [To be supplied.] /// internal void PopPanelStyle() { if (_panelStyleStack.Count == 0) { Debug.Fail("Stack is empty."); return; } _panelStyleStack.Pop(); } /// /// [To be supplied.] /// public void PushLayout(HorizontalAlign align, bool wrap) { Layout newLayout = new Layout(align, wrap); newLayout.MergeWith(CurrentLayout); _layoutStack.Push(newLayout); } /// /// Push style on stack, but do not open it. Used for pending styles, as in the panel and page adapters. /// internal void PushPanelStyle(Style style) { Style stackStyle = new Style(); if (_panelStyleStack.Count == 0) { stackStyle.CopyFrom(style); _panelStyleStack.Push(stackStyle); return; } stackStyle.CopyFrom((Style)_panelStyleStack.Peek()); stackStyle.MergeWith(style); _panelStyleStack.Push(stackStyle); } /// /// [To be supplied.] /// public virtual void RenderImage(String source, String localSource, String alternateText) { if (AnalyzeMode) { return; } WriteBeginTag("img"); WriteAttribute("src", source, true); if (localSource != null) { WriteAttribute("localsrc", localSource, true); } WriteTextEncodedAttribute("alt", alternateText != null ? alternateText : String.Empty); Write(" />"); } /// /// [To be supplied.] /// internal String ReplaceFormsCookieWithVariable(String queryString) { // UNDONE: MMIT FormsAuthentication not integrated yet return queryString; } public void SetAnalyzeMode(bool analyzeMode) { _analyzeMode = analyzeMode; if (analyzeMode) { _analyzeWriter = new EmptyTextWriter(); InnerWriter = _analyzeWriter; } else { InnerWriter = _realInnerWriter; } ClearFlags(); } /// /// [To be supplied.] /// public void SetPendingP() { if (AnalyzeMode) { return; } _pendingP = true; } /// /// [To be supplied.] /// public override void WriteAttribute(String attribute, String value) { // Must double $'s for valid WML, so call WriteAttribute with encode == true. WriteAttribute(attribute, value, true /* encode */); } /// /// [To be supplied.] /// public override void WriteAttribute(String attribute, String value, bool encode) { // If in analyze mode, we don't actually have to perform the conversion, because // it's not getting written anyway. // 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 Wml. if (value == null) { return; } if (AnalyzeMode || !encode) { base.WriteAttribute(attribute, value, false /* encode */); return; } WriteTextEncodedAttribute(attribute, value); } /// /// [To be supplied.] /// public virtual void WriteBeginSelect(String name, String value, String iname, String ivalue, String title, bool multiSelect) { if (AnalyzeMode) { return; } // Select tags cannot appear inside character formatting tags, // so close any character formatting. CloseCurrentStyleTags(); // Certain devices always render a break before a