//------------------------------------------------------------------------------
//
pairs. if (!_isBreakingReset) { _isBreakingReset = true; goto case LiteralElementType.Break; } break; case LiteralElementType.Break: // If a break is already pending, insert an empty one. if (_beginNewParagraph) { ParseText(String.Empty); } if (_lastQueuedElement != null && _lastQueuedElement.Text.Length == 0) { _lastQueuedElement.ForceBreakTag = true; } _beginNewParagraph = true; break; case LiteralElementType.Bold: if (isClosingTag) { _formatStack.Pop(FormatStack.Bold); } else { _formatStack.Push(FormatStack.Bold); } break; case LiteralElementType.Italic: if (isClosingTag) { _formatStack.Pop(FormatStack.Italic); } else { _formatStack.Push(FormatStack.Italic); } break; default: { if (!isClosingTag) { IDictionary attribs = ParseTagAttributes(literalText, tagNameFinish, tagFinish, tagName); _currentTag = new LiteralElement(tagType, attribs); } break; } } if (_isBreakingReset && tagType != LiteralElementType.Paragraph) { _isBreakingReset = false; } } protected bool IsInTag { get { return _currentTag != null; } } protected LiteralFormat CurrentFormat { get { return _formatStack.CurrentFormat; } } // Parse attributes of a tag. private enum AttributeParseState { StartingAttributeName, ReadingAttributeName, ReadingEqualSign, StartingAttributeValue, ReadingAttributeValue, Error, } private IDictionary ParseTagAttributes(String literalText, int attrStart, int attrFinish, String tagName) { if (attrFinish > attrStart && literalText[attrFinish - 1] == '/') { attrFinish--; } IDictionary dictionary = null; int attrPos = attrStart; bool skipWhiteSpaces = true; int attrNameStart = 0; int attrNameFinish = 0; int attrValueStart = 0; char quoteChar = '\0'; AttributeParseState state = AttributeParseState.StartingAttributeName; while (attrPos <= attrFinish && state != AttributeParseState.Error) { char c = attrPos == attrFinish ? '\0' : literalText[attrPos]; if (skipWhiteSpaces) { if (Char.IsWhiteSpace(c)) { attrPos++; continue; } else { skipWhiteSpaces = false; } } switch (state) { case AttributeParseState.StartingAttributeName: if (c == '\0') { attrPos = attrFinish + 1; } else { attrNameStart = attrPos; state = AttributeParseState.ReadingAttributeName; } break; case AttributeParseState.ReadingAttributeName: if (c == '=' || Char.IsWhiteSpace(c)) { attrNameFinish = attrPos; skipWhiteSpaces = true; state = AttributeParseState.ReadingEqualSign; } else if (c == '\0') { state = AttributeParseState.Error; } else { attrPos++; } break; case AttributeParseState.ReadingEqualSign: if (c == '=') { skipWhiteSpaces = true; state = AttributeParseState.StartingAttributeValue; attrPos++; } else { state = AttributeParseState.Error; } break; case AttributeParseState.StartingAttributeValue: attrValueStart = attrPos; if (c == '\0') { state = AttributeParseState.Error; break; } else if (c == '\"' || c == '\'') { quoteChar = c; attrValueStart++; attrPos++; } else { quoteChar = '\0'; } state = AttributeParseState.ReadingAttributeValue; break; case AttributeParseState.ReadingAttributeValue: if (c == quoteChar || ((Char.IsWhiteSpace(c) || c == '\0') && quoteChar == '\0')) { if (attrNameFinish == attrNameStart) { state = AttributeParseState.Error; break; } if (dictionary == null) { dictionary = new HybridDictionary(true); } dictionary.Add( literalText.Substring(attrNameStart, attrNameFinish - attrNameStart), literalText.Substring(attrValueStart, attrPos - attrValueStart)); skipWhiteSpaces = true; state = AttributeParseState.StartingAttributeName; if (c == quoteChar) { attrPos++; } } else { attrPos++; } break; } } if (state == AttributeParseState.Error) { throw new Exception(SR.GetString(SR.LiteralTextParser_InvalidTagFormat)); } return dictionary; } // Parse a plain text literal. private void ParseText(String text) { if (_currentTag != null) { // Add to inner text of tag. _currentTag.Text += text; } else { if (_isBreakingReset && IsValidText(text)) { _isBreakingReset = false; } ProcessElementInternal(new LiteralElement(text)); } } private void ProcessElementInternal(LiteralElement element) { // This method needs to fill in an element with formatting and // breaking information, and calls ProcessElement. However, // each element needs to know whether there will be a break // AFTER the element, so elements are processed lazily, keeping // the last one in a single-element queue. LiteralFormat currentFormat = _formatStack.CurrentFormat; if (_lastQueuedElement != null) { // If both the last and current element are text elements, and // the formatting hasn't changed, then just combine the two into // a single element. if (_lastQueuedElement.IsText && element.IsText && (_lastQueuedElement.Format == currentFormat) && !_beginNewParagraph) { _lastQueuedElement.Text += element.Text; return; } else if (_lastQueuedElement.IsEmptyText && !_beginNewParagraph && IgnoreWhiteSpaceElement(_lastQueuedElement)) { // Empty text element with no breaks - so just ignore. } else { _lastQueuedElement.BreakAfter = _beginNewParagraph; ProcessElement(_lastQueuedElement); ElementsProcessed = true; } } _lastQueuedElement = element; _lastQueuedElement.Format = currentFormat; _beginNewParagraph = false; } private void Flush() { if (_currentTag != null) { // In the middle of a tag. There may be multiple inner text elements inside // a tag, e.g. // some text <%# a databinding %> some more text // and we're being flushed just at the start of the databinding. if (!_currentTag.IsEmptyText) { ProcessTagInnerText(_currentTag.Text); } _currentTag.Text = String.Empty; return; } if (_lastQueuedElement == null) { return; } // Ignore orphaned whitespace. if (!_lastQueuedElement.ForceBreakTag && _lastQueuedElement.IsEmptyText) { if (!ElementsProcessed) { return; } if (_lastQueuedElement.Text.Length == 0 || _lastQueuedElement.Text[0] != ' ') { return; } _lastQueuedElement.Text = " "; } _lastQueuedElement.BreakAfter = _beginNewParagraph; ProcessElement(_lastQueuedElement); _lastQueuedElement = null; } protected virtual bool IgnoreWhiteSpaceElement(LiteralElement element) { return true; } /* * FormatStack private class * * This class maintains a simple stack of formatting directives. As tags and * closing tags are processed, they are pushed on and popped off this stack. * The CurrentFormat property returns the current state. */ private class FormatStack { internal const char Bold = 'b'; internal const char Italic = 'i'; private StringBuilder _stringBuilder = new StringBuilder(16); public void Push(char option) { _stringBuilder.Append(option); } public void Pop(char option) { // Only pop a matching directive - non-matching directives are ignored! int length = _stringBuilder.Length; if (length > 0 && _stringBuilder[length - 1] == option) { _stringBuilder.Remove(length - 1, 1); } } public LiteralFormat CurrentFormat { get { LiteralFormat format = LiteralFormat.None; for (int i = _stringBuilder.Length - 1; i >= 0; i--) { switch (_stringBuilder[i]) { case Bold: format |= LiteralFormat.Bold; break; case Italic: format |= LiteralFormat.Italic; break; } } return format; } } } } }