public CharacterProcessorResult Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } var currentCharacter = stringNavigator.CurrentCharacter; if (currentCharacter == '{') { return(new CharacterProcessorResult( CharacterCategorisationOptions.OpenBrace, _characterProcessorToReturnTo )); } else if ((currentCharacter != null) && char.IsWhiteSpace(currentCharacter.Value)) { return(new CharacterProcessorResult( CharacterCategorisationOptions.Whitespace, this )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.SelectorOrStyleProperty, this )); }
public CharacterProcessorResult Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } // For single line comments, the line return should be considered part of the comment content (in the same way that the "/*" and "*/" sequences are // considered part of the content for multi-line comments) var currentCharacter = stringNavigator.CurrentCharacter; var nextCharacter = stringNavigator.Next.CurrentCharacter; if ((currentCharacter == '\r') && (nextCharacter == '\n')) { return(new CharacterProcessorResult( CharacterCategorisationOptions.Comment, _processorFactory.Get <SkipCharactersSegment>( CharacterCategorisationOptions.Comment, 1, _characterProcessorToReturnTo ) )); } else if ((currentCharacter == '\r') || (currentCharacter == '\n')) { return(new CharacterProcessorResult(CharacterCategorisationOptions.Comment, _characterProcessorToReturnTo)); } return(new CharacterProcessorResult(CharacterCategorisationOptions.Comment, this)); }
/// <summary> /// This is a convenience method signature onto DoesCurrentContentMatch where a null optionalComparer is passed (meaning precise matching is required) /// </summary> public static bool DoesCurrentContentMatch(this IWalkThroughStrings source, string value) { if (source == null) { throw new ArgumentNullException("source"); } return(source.DoesCurrentContentMatch(value, null)); }
/// <summary> /// This will never return null nor a set containing any null references. It will throw an exception for a null content reference. /// CSS does not support single line comments, unlike LESS CSS. The content parsing is deferred so that the work to parse the content /// is only performed as the returned data is enumerated over. All runs of characters that are of the same CharacterCategorisationOptions /// will be combined into one string (note: this means that runs of opening braces that aren't separated by whitespace will be combined /// into one string containing those multiple braces). /// </summary> public static IEnumerable <CategorisedCharacterString> ParseCSS(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } return(Parse(stringNavigator, false)); }
/// <summary> /// This will never return null nor a set containing any null references. It will throw an exception for null contentWalker or contentProcessor /// references or it the processing failed. /// </summary> public IEnumerable <CategorisedCharacterString> GetStrings(IWalkThroughStrings contentWalker, IProcessCharacters contentProcessor) { if (contentWalker == null) { throw new ArgumentNullException("contentWalker"); } if (contentProcessor == null) { throw new ArgumentNullException("contentProcessor"); } // It doesn'actually matter what the initial value of currentCharacterType is since it won't be used until there is string content // to record, and by that point it will have been assigned a value var currentCharacterType = CharacterCategorisationOptions.SelectorOrStyleProperty; var stringBuilder = new StringBuilder(); var currentCharacterIndex = 0; while (true) { var character = contentWalker.CurrentCharacter; if (character == null) { break; } // CloseBrace, OpenBrace and SemiColons constitute "CharacterTypesToNotGroup" var processResult = contentProcessor.Process(contentWalker); if ((processResult.CharacterCategorisation != currentCharacterType) || (processResult.CharacterCategorisation == CharacterCategorisationOptions.CloseBrace) || (processResult.CharacterCategorisation == CharacterCategorisationOptions.OpenBrace) || (processResult.CharacterCategorisation == CharacterCategorisationOptions.SemiColon)) { if (stringBuilder.Length > 0) { var value = stringBuilder.ToString(); yield return(new CategorisedCharacterString(value, currentCharacterIndex - value.Length, currentCharacterType)); stringBuilder.Clear(); } currentCharacterType = processResult.CharacterCategorisation; } stringBuilder.Append(character); contentProcessor = processResult.NextProcessor; contentWalker = contentWalker.Next; currentCharacterIndex++; } if (stringBuilder.Length > 0) { var value = stringBuilder.ToString(); yield return(new CategorisedCharacterString(value, currentCharacterIndex - value.Length, currentCharacterType)); } }
public ProcessedQuerySegment(IWalkThroughStrings stringNavigator, IQuerySegment querySegment) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } if (querySegment == null) { throw new ArgumentNullException("querySegment"); } StringNavigator = stringNavigator; QuerySegment = querySegment; }
public ProcessedQuerySegment Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } var contentBuilder = new StringBuilder(); var processNextCharacterStrictlyAsContent = false; while (true) { if (stringNavigator.CurrentCharacter == null) { var content = contentBuilder.ToString(); return(new ProcessedQuerySegment( stringNavigator, (content == "") ? (IQuerySegment) new NoMatchContentQuerySegment() : _contentToQuerySegmentTranslator(content) )); } if (processNextCharacterStrictlyAsContent) { processNextCharacterStrictlyAsContent = false; } else { if (stringNavigator.CurrentCharacter == '\\') { processNextCharacterStrictlyAsContent = true; stringNavigator = stringNavigator.Next; continue; } else if (_terminationCharacters.Contains(stringNavigator.CurrentCharacter.Value)) { var content = contentBuilder.ToString(); return(new ProcessedQuerySegment( stringNavigator, (content == "") ? (IQuerySegment) new NoMatchContentQuerySegment() : _contentToQuerySegmentTranslator(content) )); } } contentBuilder.Append(stringNavigator.CurrentCharacter.Value); stringNavigator = stringNavigator.Next; } }
public CharacterProcessorResult Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } if (NumberOfCharactersToSkip == 1) { return(new CharacterProcessorResult( CharacterCategorisation, _characterProcessorToReturnTo )); } return(new CharacterProcessorResult( CharacterCategorisation, new SkipCharactersSegment(CharacterCategorisation, NumberOfCharactersToSkip - 1, _characterProcessorToReturnTo) )); }
private static IEnumerable <CategorisedCharacterString> Parse(IWalkThroughStrings stringNavigator, bool supportSingleLineComments) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } var processorFactory = new CachingCharacterProcessorsFactory( new CharacterProcessorsFactory() ); return((new ProcessedCharactersGrouper()).GetStrings( stringNavigator, processorFactory.Get <SelectorOrStylePropertySegment>( supportSingleLineComments ? SelectorOrStyleSegment.SingleLineCommentsSupportOptions.Support : SelectorOrStyleSegment.SingleLineCommentsSupportOptions.DoNotSupport, processorFactory ) )); }
public CharacterProcessorResult Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } if ((stringNavigator.CurrentCharacter == '*') && (stringNavigator.Next.CurrentCharacter == '/')) { return(new CharacterProcessorResult( CharacterCategorisationOptions.Comment, _processorFactory.Get <SkipCharactersSegment>( CharacterCategorisationOptions.Comment, 1, _characterProcessorToReturnTo ) )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.Comment, this )); }
public CharacterProcessorResult Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } // If the next character is a backslash then the next character should be ignored if it's "special" and just considered // to be another character in the Value string (particularly important if the next character is an escaped quote) var currentCharacter = stringNavigator.CurrentCharacter; if (currentCharacter == '\\') { return(new CharacterProcessorResult( _characterCategorisation, _processorFactory.Get <SkipCharactersSegment>( _characterCategorisation, 1, this ) )); } // If this is the closing quote character then include it in the Value and then return to the previous processor if (currentCharacter == _quoteCharacter) { return(new CharacterProcessorResult( _characterCategorisation, _characterProcessorToReturnTo )); } return(new CharacterProcessorResult( _characterCategorisation, this )); }
public CharacterProcessorResult Process(IWalkThroughStrings stringNavigator) { if (stringNavigator == null) { throw new ArgumentNullException("stringNavigator"); } // If dealing with encountering a single special character such as braces, colons or whitespace then record the character type and // continue processing. Some characters may require the processingStyleOrSelector flag to be reset; eg. if we're in "selector" content // and process a colon, then this indicates that we are entering "value" content (likewise, if we're in "value" content and encounter // a brace then we must have left the value). // - If there is an optionalCharacterCategorisationBehaviourOverride value then most of these single special characters are ignored // and are forced into being categorised as something else (this is used by the code the ensures that attribute selectors and // LESS mixin argument sets are identified as being SelectorOrStyleProperty content even it could contain whitespace, quoted // sections and all sort). The optionalCharacterCategorisationBehaviourOverride does not apply to comment content, that is // always identified as being type Comment even if it's inside an attribute selector. // Is this the end of the section that the optionalCharacterCategorisationBehaviourOverride (if non-null) is concerned with? If so // then drop back out to the character processor that handed control over to the optionalCharacterCategorisationBehaviourOverride. var currentCharacter = stringNavigator.CurrentCharacter; if ((_optionalCharacterCategorisationBehaviourOverride != null) && (currentCharacter == _optionalCharacterCategorisationBehaviourOverride.EndOfBehaviourOverrideCharacter)) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, _optionalCharacterCategorisationBehaviourOverride.CharacterProcessorToReturnTo )); } // Deal with other special characters (bearing in mind the altered interactions if optionalCharacterCategorisationBehaviourOverride // is non-null) if (currentCharacter == '{') { if (_optionalCharacterCategorisationBehaviourOverride != null) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, this )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.OpenBrace, GetSelectorOrStyleCharacterProcessor() )); } else if (currentCharacter == '}') { if (_optionalCharacterCategorisationBehaviourOverride != null) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, this )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.CloseBrace, GetSelectorOrStyleCharacterProcessor() )); } else if (currentCharacter == ';') { if (_optionalCharacterCategorisationBehaviourOverride != null) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, this )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.SemiColon, GetSelectorOrStyleCharacterProcessor() )); } else if (currentCharacter == ':') { if (_optionalCharacterCategorisationBehaviourOverride != null) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, this )); } // If the colon indicates a pseudo-class for a selector then we want to continue processing it as a selector and not presume // that the content type has switched to a value (this is more complicated with LESS nesting to support, if it was just CSS // then things would have been easier!) if (_processingType == ProcessingTypeOptions.StyleOrSelector) { var pseudoClassHandlingProcessResultfApplicable = GetPseudoClassHandlingProcessResultIfApplicable(stringNavigator.Next); if (pseudoClassHandlingProcessResultfApplicable != null) { return(pseudoClassHandlingProcessResultfApplicable); } } return(new CharacterProcessorResult( CharacterCategorisationOptions.StylePropertyColon, GetValueCharacterProcessor() )); } else if ((currentCharacter != null) && char.IsWhiteSpace(currentCharacter.Value)) { if (_optionalCharacterCategorisationBehaviourOverride != null) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, this )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.Whitespace, this )); } // To deal with comments we use specialised comment-handling processors (even if an optionalCharacterCategorisationBehaviourOverride // is specified we still treat deal with comments as normal, their content is not forced into a different categorisation) var nextCharacter = stringNavigator.Next.CurrentCharacter; if ((_singleLineCommentsSupportOptions == SingleLineCommentsSupportOptions.Support) && (currentCharacter == '/') && (nextCharacter == '/')) { return(new CharacterProcessorResult( CharacterCategorisationOptions.Comment, _processorFactory.Get <SingleLineCommentSegment>(this, _processorFactory) )); } if ((currentCharacter == '/') && (nextCharacter == '*')) { return(new CharacterProcessorResult( CharacterCategorisationOptions.Comment, _processorFactory.Get <MultiLineCommentSegment>(this, _processorFactory) )); } // Although media query declarations will be marked as SelectorOrStyleProperty content, special handling is required to ensure that // any colons that exist in it are identified as part of the SelectorOrStyleProperty and not marked as a StylePropertyColon if ((_processingType == ProcessingTypeOptions.StyleOrSelector) && stringNavigator.DoesCurrentContentMatch("@media")) { return(new CharacterProcessorResult( CharacterCategorisationOptions.SelectorOrStyleProperty, _processorFactory.Get <MediaQuerySegment>(this) )); } // If we encounter quotes then we need to use the QuotedSegment which will keep track of where the quoted section end (taking // into account any escape sequences) if ((currentCharacter == '"') || (currentCharacter == '\'')) { // If an optionalCharacterCategorisationBehaviourOverride was specified then the content will be identified as whatever // categorisation is specified by it, otherwise it will be identified as being CharacterCategorisationOptions.Value if (_optionalCharacterCategorisationBehaviourOverride != null) { return(new CharacterProcessorResult( _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, _processorFactory.Get <QuotedSegment>( currentCharacter, _optionalCharacterCategorisationBehaviourOverride.CharacterCategorisation, this, _processorFactory ) )); } return(new CharacterProcessorResult( CharacterCategorisationOptions.Value, _processorFactory.Get <QuotedSegment>( currentCharacter, CharacterCategorisationOptions.Value, GetValueCharacterProcessor(), _processorFactory ) )); } // If we're currently processing StyleOrSelector content and we encounter a square or round open bracket then we're about to // enter an attribute selector (eg. "a[href]") or a LESS mixin argument set (eg. ".RoundedCorners (@radius)". In either case // we need to consider all content until the corresponding close bracket to be a StyleOrSelector, whether it's whitespace or // a quoted section (note: not if it's a comment, that still gets identified as comment content). if (_processingType == ProcessingTypeOptions.StyleOrSelector) { char?closingBracket; if (currentCharacter == '[') { closingBracket = ']'; } else if (currentCharacter == '(') { closingBracket = ')'; } else { closingBracket = null; } if (closingBracket != null) { return(new CharacterProcessorResult( CharacterCategorisationOptions.SelectorOrStyleProperty, _processorFactory.Get <BracketedSelectorSegment>( _singleLineCommentsSupportOptions, closingBracket.Value, this, _processorFactory ) )); } } // If it's not a quoted or bracketed section, then we can continue to use this instance to process the content return(new CharacterProcessorResult( _processingType == ProcessingTypeOptions.StyleOrSelector ? CharacterCategorisationOptions.SelectorOrStyleProperty : CharacterCategorisationOptions.Value, this )); }
/// <summary> /// If this next work from the current point in the given string navigator appears to be a recognised pseudo class then this will return a result that /// will ensure that the current character is identified as a selector-or-property-name content rather than a property-name-value-separator colon (the /// string navigator passed here will be for the position directly after the colon, which is what is currently being negotiated). Any whitespace at /// the current position will be moved over when looking for pseudo class content. If the content does not appear to be for a pseudo class then null /// will be returned and the caller may continue with whatever logic it wants to. /// </summary> private CharacterProcessorResult GetPseudoClassHandlingProcessResultIfApplicable(IWalkThroughStrings stringNavigatorAtStartOfPotentialPseudoClass) { if (stringNavigatorAtStartOfPotentialPseudoClass == null) { throw new ArgumentNullException("stringNavigator"); } // If the first character of the possible-psuedo-class content is a ":" then it means that we've encountered a "::" (since the first ":" triggered // this method to be called with the content after it) and that means that it's definitely a psuedo class and not a property name / value pair. As // such we'll return a process that swallows this second ":" and then processes the next content as selector-or-style-content (and not as property // name content) if (stringNavigatorAtStartOfPotentialPseudoClass.CurrentCharacter == ':') { var contentProcessor = GetSelectorOrStyleCharacterProcessor(); return(new CharacterProcessorResult( CharacterCategorisationOptions.SelectorOrStyleProperty, new SkipCharactersSegment(CharacterCategorisationOptions.SelectorOrStyleProperty, 1, GetSelectorOrStyleCharacterProcessor()) )); } // Skip over any whitespace to find the start of the next content var whiteSpaceCharactersToSkipOver = 0; while (true) { var character = stringNavigatorAtStartOfPotentialPseudoClass.CurrentCharacter; if ((character == null) || !char.IsWhiteSpace(character.Value)) { break; } stringNavigatorAtStartOfPotentialPseudoClass = stringNavigatorAtStartOfPotentialPseudoClass.Next; whiteSpaceCharactersToSkipOver++; } // Determine whether that content (if there is any) matches any of the pseudo classes if (PseudoClasses.Any(c => stringNavigatorAtStartOfPotentialPseudoClass.DoesCurrentContentMatch(c))) { IProcessCharacters contentProcessor = GetSelectorOrStyleCharacterProcessor(); if (whiteSpaceCharactersToSkipOver > 0) { contentProcessor = new SkipCharactersSegment(CharacterCategorisationOptions.Whitespace, whiteSpaceCharactersToSkipOver, contentProcessor); } return(new CharacterProcessorResult( CharacterCategorisationOptions.SelectorOrStyleProperty, contentProcessor )); } return(null); }