public static IEnumerable <ICSSFragment> ParseIntoStructuredData(
            IEnumerable <CategorisedCharacterString> segments,
            CommentHandlingOptions commentHandling = CommentHandlingOptions.Exclude)
        {
            if (segments == null)
            {
                throw new ArgumentNullException("segments");
            }
            if (!Enum.IsDefined(typeof(CommentHandlingOptions), commentHandling))
            {
                throw new ArgumentOutOfRangeException("commentHandling");
            }

            var segmentEnumerator = segments.GetEnumerator();
            var parsedData        = ParseIntoStructuredData(segmentEnumerator, new Selector.SelectorSet[0], 0, commentHandling);

            while (segmentEnumerator.MoveNext())
            {
                var segment = segmentEnumerator.Current;
                if ((segment.CharacterCategorisation != CharacterCategorisationOptions.Comment) &&
                    (segment.CharacterCategorisation != CharacterCategorisationOptions.Whitespace))
                {
                    var lastFragment          = parsedData.Item1.LastOrDefault();
                    var lastFragmentLineIndex = (lastFragment == null) ? 0 : lastFragment.SourceLineIndex;
                    throw new ArgumentException("Encountered unparsable data, this indicates content (after line " + (lastFragmentLineIndex + 1) + ")");
                }
            }
            return(parsedData.Item1);
        }
        /// <summary>
        /// This returns the parsed fragments and the number of lines that were processed from the source in doing so
        /// </summary>
        private static Tuple <IEnumerable <ICSSFragment>, int> ParseIntoStructuredData(
            IEnumerator <CategorisedCharacterString> segmentEnumerator,
            IEnumerable <Selector.SelectorSet> parentSelectors,
            int sourceLineIndex,
            CommentHandlingOptions commentHandling)
        {
            if (segmentEnumerator == null)
            {
                throw new ArgumentNullException("segmentEnumerator");
            }
            if (parentSelectors == null)
            {
                throw new ArgumentNullException("parentSelectors");
            }
            if (sourceLineIndex < 0)
            {
                throw new ArgumentNullException("sourceLineIndex", "must be zero or greater");
            }
            if (!Enum.IsDefined(typeof(CommentHandlingOptions), commentHandling))
            {
                throw new ArgumentOutOfRangeException("commentHandling");
            }

            var startingSourceLineIndex = sourceLineIndex;
            var fragments = new List <ICSSFragment>();
            var selectorOrStyleContentBuffer        = new StringBuilder();
            var selectorOrStyleStartSourceLineIndex = -1;
            StylePropertyName lastStylePropertyName = null;
            var stylePropertyValueBuffer            = new PropertyValueBuffer();

            while (segmentEnumerator.MoveNext())
            {
                var segment = segmentEnumerator.Current;
                if (segment == null)
                {
                    throw new ArgumentException("Null reference encountered in segments set");
                }

                switch (segment.CharacterCategorisation)
                {
                case CharacterCategorisationOptions.Comment:
                    if (commentHandling == CommentHandlingOptions.Include)
                    {
                        fragments.Add(new Comment(segment.Value, sourceLineIndex));
                    }
                    sourceLineIndex += GetNumberOfLineReturnsFromContentIfAny(segment.Value);
                    continue;

                case CharacterCategorisationOptions.Whitespace:
                    sourceLineIndex += GetNumberOfLineReturnsFromContentIfAny(segment.Value);
                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        selectorOrStyleContentBuffer.Append(" ");
                    }
                    continue;

                case CharacterCategorisationOptions.SelectorOrStyleProperty:
                    if (selectorOrStyleContentBuffer.Length == 0)
                    {
                        selectorOrStyleStartSourceLineIndex = sourceLineIndex;
                    }
                    selectorOrStyleContentBuffer.Append(segment.Value);

                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }
                    continue;

                case CharacterCategorisationOptions.OpenBrace:
                    if (selectorOrStyleContentBuffer.Length == 0)
                    {
                        throw new ArgumentException("Encountered OpenBrace with no preceding selector at line " + (sourceLineIndex + 1));
                    }

                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }

                    var selectors = GetSelectorSet(selectorOrStyleContentBuffer.ToString());
                    var parsedNestedContentDetails = ParseIntoStructuredData(segmentEnumerator, parentSelectors.Concat(new[] { selectors }), sourceLineIndex, commentHandling);
                    if (selectors.First().Value.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase))
                    {
                        fragments.Add(new MediaQuery(
                                          selectors,
                                          parentSelectors,
                                          selectorOrStyleStartSourceLineIndex,
                                          parsedNestedContentDetails.Item1                       // Item1 are the processed fragments (Item2 is the number of lines processed to extract those fragments)
                                          ));
                    }
                    else
                    {
                        fragments.Add(new Selector(
                                          selectors,
                                          parentSelectors,
                                          selectorOrStyleStartSourceLineIndex,
                                          parsedNestedContentDetails.Item1                       // Item1 are the processed fragments (Item2 is the number of lines processed to extract those fragments)
                                          ));
                    }
                    sourceLineIndex += parsedNestedContentDetails.Item2;                             // Increase sourceLineIndex by the number of lines that the recursive call processed
                    selectorOrStyleContentBuffer.Clear();
                    continue;

                case CharacterCategorisationOptions.CloseBrace:
                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }

                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        fragments.Add(new StylePropertyName(
                                          selectorOrStyleContentBuffer.ToString(),
                                          selectorOrStyleStartSourceLineIndex
                                          ));
                    }
                    return(Tuple.Create <IEnumerable <ICSSFragment>, int>(fragments, sourceLineIndex - startingSourceLineIndex));

                case CharacterCategorisationOptions.StylePropertyColon:
                case CharacterCategorisationOptions.SemiColon:
                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }

                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        var selectorOrStyleContent = selectorOrStyleContentBuffer.ToString();
                        if (selectorOrStyleContent.StartsWith("@import", StringComparison.InvariantCultureIgnoreCase))
                        {
                            fragments.Add(new Import(
                                              selectorOrStyleContent.Substring("@import".Length).Trim(),
                                              sourceLineIndex
                                              ));
                            selectorOrStyleContentBuffer.Clear();
                            continue;
                        }

                        // Note: The SemiColon case here probably suggests invalid content, it should only follow a Value segment (ignoring
                        // Comments and WhiteSpace), so if there is anything in the selectorOrStyleContentBuffer before the SemiColon then
                        // it's probably not correct (but we're not validating for that here, we just don't want to throw anything away!)
                        lastStylePropertyName = new StylePropertyName(
                            selectorOrStyleContentBuffer.ToString(),
                            selectorOrStyleStartSourceLineIndex
                            );
                        fragments.Add(lastStylePropertyName);
                        selectorOrStyleContentBuffer.Clear();
                    }
                    continue;

                case CharacterCategorisationOptions.Value:
                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        var selectorOrStyleContent = selectorOrStyleContentBuffer.ToString();
                        if (selectorOrStyleContent.StartsWith("@import", StringComparison.InvariantCultureIgnoreCase))
                        {
                            selectorOrStyleContentBuffer.Append(segment.Value);
                            continue;
                        }

                        // This is presumably an error condition, there should be a colon between SelectorOrStyleProperty content and
                        // Value content, but we're not validating here so just lump it all together
                        lastStylePropertyName = new StylePropertyName(
                            selectorOrStyleContentBuffer.ToString(),
                            selectorOrStyleStartSourceLineIndex
                            );
                        fragments.Add(lastStylePropertyName);
                        selectorOrStyleContentBuffer.Clear();
                    }
                    if (lastStylePropertyName == null)
                    {
                        throw new Exception("Invalid content, orphan style property value encountered");
                    }
                    stylePropertyValueBuffer.Add(new StylePropertyValue(
                                                     lastStylePropertyName,
                                                     new[] { segment.Value },
                                                     selectorOrStyleStartSourceLineIndex
                                                     ));
                    continue;

                default:
                    throw new ArgumentException("Unsupported CharacterCategorisationOptions value: " + segment.CharacterCategorisation);
                }
            }

            // If we have any content in the selectorOrStyleContentBuffer and we're hitting a CloseBrace then it's probably invalid content,
            // but just stash it away and move on! (The purpose of this work isn't to get too nuts about invalid CSS).
            if (selectorOrStyleContentBuffer.Length > 0)
            {
                var selectors = GetSelectorSet(selectorOrStyleContentBuffer.ToString());
                if (selectors.First().Value.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase))
                {
                    fragments.Add(new MediaQuery(
                                      selectors,
                                      parentSelectors,
                                      sourceLineIndex,
                                      new ICSSFragment[0]
                                      ));
                }
                else
                {
                    fragments.Add(new Selector(
                                      selectors,
                                      parentSelectors,
                                      sourceLineIndex,
                                      new ICSSFragment[0]
                                      ));
                }
            }

            // It's very feasible that there will still be some style property value content in the buffer at this point, so ensure it
            // doesn't get lost
            if (stylePropertyValueBuffer.HasContent)
            {
                fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
            }

            return(Tuple.Create <IEnumerable <ICSSFragment>, int>(fragments, sourceLineIndex - startingSourceLineIndex));
        }