static CssValue Consume_MediaFeature_Value(DataConsumer <CssToken> Stream) { if (Stream is null) { throw new CssParserException(CssErrors.STREAM_IS_NULL); } /* Consume: <number> | <dimension> | <ident> | <ratio> */ Consume_All_Whitespace(Stream); switch (Stream.Next.Type) { case ECssTokenType.Number: { var numTok = Stream.Consume() as NumberToken; /* This could be a ratio - so check if it is */ /* A ratio is a <number> <?whitespace> / <?whitespace> <number> */ if (ParserCommon.Starts_Ratio_Value(Stream.AsSpan())) { Consume_All_Whitespace(Stream); DelimToken dtok = Stream.Consume() as DelimToken; Consume_All_Whitespace(Stream); NumberToken numTok2 = Stream.Consume() as NumberToken; double ratioValue = ((double)numTok.Number / (double)numTok2.Number); return(new CssValue(ECssValueTypes.RATIO, ratioValue)); } /* Nope, its just a number */ return(new CssValue(ECssValueTypes.NUMBER, numTok.Number)); } case ECssTokenType.Dimension: case ECssTokenType.Ident: { return(Consume_CssValue(Stream)); } } // throw new CssSyntaxErrorException($"Expected Number/Dimension/Keyword token but got: \"{Enum.GetName(typeof(ECssTokenType), Stream.Next.Type)}\""); throw new CssSyntaxErrorException(CssErrors.UNEXPECTED_TOKEN, Stream); }
/// <summary> /// Consumes a new <see cref="MediaCondition"/> or <see cref="MediaFeature"/> /// </summary> /// <param name="Stream"></param> /// <returns></returns> static IMediaCondition Consume_Media_Condition(DataConsumer <CssToken> Stream) {/* Docs: https://www.w3.org/TR/mediaqueries-4/#media-condition */ if (Stream is null) { throw new CssParserException(CssErrors.STREAM_IS_NULL); } Consume_All_Whitespace(Stream); if (ParserCommon.Starts_Media_Feature(Stream.AsSpan())) { /* Consume opening parentheses */ Stream.Consume(); var feature = Consume_Media_Feature(Stream); /* Consume closing parentheses */ if (Stream.Next.Type == ECssTokenType.Parenth_Close) { Stream.Consume(); } return(feature); } else if (ParserCommon.Starts_Media_Condition(Stream.AsSpan())) { EMediaCombinator Combinator = EMediaCombinator.None; var conditionList = new LinkedList <IMediaCondition>(); if (Stream.Next.Type == ECssTokenType.Parenth_Close) { /* Empty media condition block */ Stream.Consume(); return(new MediaCondition(EMediaCombinator.None, Array.Empty <MediaFeature>())); } else if (Stream.Next.Type == ECssTokenType.Parenth_Open) { Stream.Consume(); } Consume_All_Whitespace(Stream); /* Repeatedly consume sub-media-conditions until we hit a closing parentheses */ do { Consume_All_Whitespace(Stream); if (Stream.Next.Type == ECssTokenType.Parenth_Close) { /* End of this media condition block */ break; } /* Anything other than the first condition *MUST* specify a combinator */ if (conditionList.Count > 0) { if (!ParserCommon.Is_Combinator(Stream.Next)) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_COMBINATOR, Stream); } } /* Otherwise we just COULD have a combinator */ if (ParserCommon.Is_Combinator(Stream.Next)) { /* Consume combinator */ IdentToken combinatorToken = Stream.Consume() as IdentToken; if (!Lookup.TryEnum(combinatorToken.Value, out EMediaCombinator combLookup)) { throw new CssSyntaxErrorException(String.Format(CultureInfo.InvariantCulture, CssErrors.INVALID_COMBINATOR, combinatorToken.Value), Stream); } else if (Combinator == EMediaCombinator.None) {/* This is the first combinator specified */ Combinator = combLookup; } else if (Combinator != EMediaCombinator.None && combLookup != Combinator) {/* Ensure this new combinator matches the combinator for this method group */ throw new CssSyntaxErrorException(CssErrors.INVALID_MULTIPLE_COMBINATORS_ON_MEDIARULE, Stream); } } Consume_All_Whitespace(Stream); if (Stream.Next.Type != ECssTokenType.Parenth_Open) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_OPENING_PARENTHESES, Stream); } /* Oh look a yummy little sub-condition for us to gobble up! */ var feature = Consume_Media_Condition(Stream); conditionList.AddLast(feature); }while (Stream.Next.Type != ECssTokenType.EOF); /* Consume closing parentheses */ if (Stream.Next.Type == ECssTokenType.Parenth_Close) { Stream.Consume(); } return(new MediaCondition(Combinator, conditionList)); } throw new CssSyntaxErrorException(CssErrors.EXPECTING_MEDIA_CONDITION_START, Stream); }
static IMediaCondition Consume_Media_Feature(DataConsumer <CssToken> Stream) {/* Docs: https://drafts.csswg.org/mediaqueries-4/#mq-syntax */ if (Stream is null) { throw new CssParserException(CssErrors.STREAM_IS_NULL); } /* Consume feature name */ if (ParserCommon.Starts_Boolean_Feature(Stream.AsSpan())) { /* Consume feature name */ IdentToken nameTok = Stream.Consume() as IdentToken; /* Resolve the name */ if (!Lookup.TryEnum(nameTok.Value, out EMediaFeatureName Name)) { throw new CssParserException(String.Format(CultureInfo.InvariantCulture, CssErrors.INVALID_MEDIA_TYPE, nameTok.Value), Stream); } return(new MediaFeature(Name)); } else if (ParserCommon.Starts_Discreet_Feature(Stream.AsSpan())) { /* Consume feature name */ IdentToken nameTok = Stream.Consume() as IdentToken; /* Resolve the name */ if (!Lookup.TryEnum(nameTok.Value, out EMediaFeatureName Name)) { throw new CssParserException(String.Format(CultureInfo.InvariantCulture, CssErrors.INVALID_MEDIA_TYPE, nameTok.Value), Stream); } /* Consume the value to match */ Consume_All_Whitespace(Stream); var value = Consume_MediaFeature_Value(Stream); return(new MediaFeature(new CssValue[] { CssValue.From(Name), value }, new EMediaOperator[] { EMediaOperator.EqualTo })); } else if (ParserCommon.Starts_Range_Feature(Stream.AsSpan())) { /* This is a range feature of some sort, it could be a short one or a long one */ /* Repeatedly consume CssValues, operator, and a single ident */ LinkedList <CssValue> Values = new LinkedList <CssValue>(); LinkedList <EMediaOperator> Ops = new LinkedList <EMediaOperator>(); bool firstToken = true; bool lastWasComparator = false; while (Stream.Next != CssToken.EOF) { Consume_All_Whitespace(Stream); if (Stream.Next.Type == ECssTokenType.Parenth_Close) { break; } else if (Stream.Next.Type == ECssTokenType.Ident) { if (!firstToken && !lastWasComparator) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_COMPARATOR, Stream); } var nameTok = (IdentToken)Stream.Consume(); /* Resolve the name */ if (!Lookup.TryEnum(nameTok.Value, out EMediaFeatureName Name)) { throw new CssParserException(String.Format(CultureInfo.InvariantCulture, CssErrors.INVALID_MEDIA_TYPE, nameTok.Value), Stream); } var value = CssValue.From(Name); Values.AddLast(value); lastWasComparator = false; } else if (ParserCommon.Starts_MF_Ident_Or_Value(Stream.AsSpan())) { if (!firstToken && !lastWasComparator) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_COMPARATOR, Stream); } CssValue value = Consume_MediaFeature_Value(Stream); Values.AddLast(value); lastWasComparator = false; } else if (ParserCommon.Is_Comparator(Stream.Next)) { if (lastWasComparator || firstToken) { throw new CssSyntaxErrorException(CssErrors.UNEXPECTED_TOKEN, Stream); } var comparatorTok = (ValuedTokenBase)Stream.Consume(); if (!Lookup.TryEnum(comparatorTok.Value, out EMediaOperator outComparator)) { throw new CssParserException(CssErrors.EXPECTING_COMPARATOR, Stream); } Ops.AddLast(outComparator); lastWasComparator = true; } firstToken = false; } return(new MediaFeature(Values.ToArray(), Ops.ToArray())); } return(null); }
static MediaQuery Consume_MediaQuery(DataConsumer <CssToken> Stream = null) {/* Docs: https://drafts.csswg.org/mediaqueries-4/#mq-syntax */ if (Stream is null) { throw new CssParserException(CssErrors.STREAM_IS_NULL); } if (Stream.Next.Type != ECssTokenType.Ident) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_IDENT, Stream); } EMediaQueryModifier modifier = 0x0; EMediaType mediaType = 0x0; LinkedList <IMediaCondition> conditionList = new LinkedList <IMediaCondition>(); /* First check for media modifier */ if (Stream.Next.Type != ECssTokenType.Ident) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_IDENT, Stream); } if (Lookup.TryEnum((Stream.Next as IdentToken).Value, out EMediaQueryModifier mod)) { Stream.Consume();// consume this token modifier = mod; } /* Skip 'and' keyword if present */ if (ParserCommon.Is_Combinator(Stream.Next)) { Stream.Consume(); } /* Set the media type */ if (Stream.Next.Type != ECssTokenType.Ident) { throw new CssSyntaxErrorException(CssErrors.EXPECTING_IDENT, Stream); } if (!Lookup.TryEnum((Stream.Next as IdentToken).Value, out EMediaType type)) { throw new CssParserException(String.Format(CultureInfo.InvariantCulture, CssErrors.INVALID_MEDIA_TYPE, (Stream.Next as IdentToken).Value), Stream); } else { Stream.Consume(); } /* Skip thew first combinator keyword if present */ Consume_All_Whitespace(Stream); if (ParserCommon.Is_Combinator(Stream.Next)) { Stream.Consume(); } /* Now consume media conditions until we cant anymore */ do { Consume_All_Whitespace(Stream); if (Stream.Next.Type != ECssTokenType.Parenth_Open) {/* This isn't invalid, it just signals that we have no more features to consume */ break; } var condition = Consume_Media_Condition(Stream); conditionList.AddLast(condition); }while (Stream.Next.Type != ECssTokenType.EOF); return(new MediaQuery(modifier, mediaType, conditionList)); }