in lib/src/tokenizer.dart [258:358]
void consumeEntity({String? allowedChar, bool fromAttribute = false}) {
// Initialise to the default output for when no entity is matched
String? output = '&';
final charStack = [stream.char()];
if (isWhitespace(charStack[0]) ||
charStack[0] == '<' ||
charStack[0] == '&' ||
charStack[0] == eof ||
allowedChar == charStack[0]) {
stream.unget(charStack[0]);
} else if (charStack[0] == '#') {
// Read the next character to see if it's hex or decimal
var hex = false;
charStack.add(stream.char());
if (charStack.last == 'x' || charStack.last == 'X') {
hex = true;
charStack.add(stream.char());
}
// charStack.last should be the first digit
if (hex && isHexDigit(charStack.last) ||
(!hex && isDigit(charStack.last))) {
// At least one digit found, so consume the whole number
stream.unget(charStack.last);
output = consumeNumberEntity(hex);
} else {
// No digits found
_addToken(ParseErrorToken('expected-numeric-entity'));
stream.unget(charStack.removeLast());
output = '&${charStack.join()}';
}
} else {
// At this point in the process might have named entity. Entities
// are stored in the global variable "entities".
//
// Consume characters and compare to these to a substring of the
// entity names in the list until the substring no longer matches.
var filteredEntityList = entitiesByFirstChar[charStack[0]!] ?? const [];
while (charStack.last != eof) {
final name = charStack.join();
filteredEntityList =
filteredEntityList.where((e) => e.startsWith(name)).toList();
if (filteredEntityList.isEmpty) {
break;
}
charStack.add(stream.char());
}
// At this point we have a string that starts with some characters
// that may match an entity
String? entityName;
// Try to find the longest entity the string will match to take care
// of ¬i for instance.
int entityLen;
for (entityLen = charStack.length - 1; entityLen > 1; entityLen--) {
final possibleEntityName = charStack.sublist(0, entityLen).join();
if (entities.containsKey(possibleEntityName)) {
entityName = possibleEntityName;
break;
}
}
if (entityName != null) {
final lastChar = entityName[entityName.length - 1];
if (lastChar != ';') {
_addToken(ParseErrorToken('named-entity-without-semicolon'));
}
if (lastChar != ';' &&
fromAttribute &&
(isLetterOrDigit(charStack[entityLen]) ||
charStack[entityLen] == '=')) {
stream.unget(charStack.removeLast());
output = '&${charStack.join()}';
} else {
output = entities[entityName];
stream.unget(charStack.removeLast());
output = '$output${slice(charStack, entityLen).join()}';
}
} else {
_addToken(ParseErrorToken('expected-named-entity'));
stream.unget(charStack.removeLast());
output = '&${charStack.join()}';
}
}
if (fromAttribute) {
_attributeValue.write(output);
} else {
Token token;
if (isWhitespace(output)) {
token = SpaceCharactersToken(output);
} else {
token = CharactersToken(output);
}
_addToken(token);
}
}