* @return the tag node or {@code null} if none found
*/
private XmlTagNode parseTagNode() {
// Assume that the current node is a tag node start TokenType#LT
Token nodeStart = currentToken;
currentToken = currentToken.getNext();
// Get the tag or create a synthetic tag and report an error
Token tag;
if (currentToken.getType() == TokenType.TAG) {
tag = currentToken;
currentToken = currentToken.getNext();
} else {
reportUnexpectedToken();
tag = insertSyntheticToken(TAG);
}
// Parse the attributes
List<XmlAttributeNode> attributes = parseAttributes();
// Token ending attribute list
Token attributeEnd;
if (currentToken.getType() == GT || currentToken.getType() == SLASH_GT) {
attributeEnd = currentToken;
currentToken = currentToken.getNext();
} else {
reportUnexpectedToken();
attributeEnd = insertSyntheticToken(SLASH_GT);
}
// If the node has no children, then return the node
if (attributeEnd.getType() == SLASH_GT || isSelfClosing(tag)) {
return createTagNode(
nodeStart,
tag,
attributes,
attributeEnd,
XmlTagNode.NO_TAG_NODES,
currentToken,
null,
attributeEnd);
}
// Parse the child tag nodes
List<XmlTagNode> tagNodes = parseChildTagNodes();
// Token ending child tag nodes
Token contentEnd;
if (currentToken.getType() == LT_SLASH) {
contentEnd = currentToken;
currentToken = currentToken.getNext();
} else {
// TODO (danrubel): handle self closing HTML elements by inserting synthetic tokens
// but not reporting an error
reportUnexpectedToken();
contentEnd = insertSyntheticToken(LT_SLASH);
}
// Closing tag
Token closingTag;
if (currentToken.getType() == TAG) {
closingTag = currentToken;
currentToken = currentToken.getNext();
} else {
reportUnexpectedToken();
closingTag = insertSyntheticToken(TAG);
}
// Token ending node
Token nodeEnd;
if (currentToken.getType() == GT) {
nodeEnd = currentToken;
currentToken = currentToken.getNext();
} else {
reportUnexpectedToken();