private static Selector ParseRawCompoundSelector(string raw, int start, int stop, string filePath)
{
if (raw[0] == '&')
{
return new ConcatWithParentSelector(ParseRawCompoundSelector(raw.Substring(1), start, stop, filePath), start, stop, filePath);
}
var ret = new List<Selector>();
bool skippedColon = false;
for (int i = 0; i < raw.Length; i++)
{
var c = raw[i];
if (c != ':')
{
skippedColon = false;
}
if (c == '*')
{
ret.Add(WildcardSelector.Singleton);
continue;
}
var j = _NextIndexOf(raw, i + 1, '#', ':', '.', '[', ']');
if (j == -1) j = raw.Length;
var name = raw.Substring(i, j - i);
if (name.StartsWith(":not(") && name[name.Length - 1] != ')')
{
j = raw.IndexOf(')', j);
if (j == -1) throw new InvalidOperationException("Expected to find closing )");
j++;
name = raw.Substring(i, j - i);
}
i = j - 1;
if (c == '#')
{
var id = name.Substring(1);
if (!Validation.IsIdentifier(id))
{
Current.RecordError(ErrorType.Parser, Position.Create(start, stop, filePath), "[" + id + "] is not a valid identifier");
throw new StoppedParsingException();
}
ret.Add(new IdSelector(id, start, stop, filePath));
continue;
}
if (c == '.')
{
var @class = name.Substring(1);
ret.Add(new ClassSelector(@class, start, stop, filePath));
continue;
}
if (c == ':')
{
// :: syntax, which is *technically different... but in practice every browser just
// treats the same because they have to
if (i + 1 < raw.Length && raw[i + 1] == ':' && !skippedColon)
{
// act like that colon isn't there, but just once,
// if we want to make a distinction in the AST later, we can do it here
skippedColon = true;
continue;
}
var pseudo = (PseudoClassSelector)PseudoClassSelector.Parse(name, start, stop, filePath);
if (pseudo.IsElement && !skippedColon)
{
Current.RecordWarning(ErrorType.Parser, pseudo, pseudo.Name + " was used as a class, but is an element. Use :: instead.");
}
if (!pseudo.IsElement && skippedColon)
{
// Because nothing is ever easy, :before and :after are legal to refer to either way despite being elements
if (!pseudo.Name.In("before", "after"))
{
Current.RecordWarning(ErrorType.Parser, pseudo, pseudo.Name + " was used as an element, but is a class. Use : instead.");
}
}
ret.Add(pseudo);
continue;
}
if (c == '[')
{
ret.Add(AttributeSelector.Parse(name, start, stop, filePath));
i++;
continue;
}
if (!Validation.IsIdentifier(name))
{
Current.RecordError(ErrorType.Parser, Position.Create(start, stop, filePath), "[" + name + "] is not a valid identifier");
throw new StoppedParsingException();
}
ret.Add(new ElementSelector(name, start, stop, filePath));
}
if (ret.Count == 1) return ret[0];
return new ConcatSelector(ret, start, stop, filePath);
}