Я написал фреймворк для Java, такой как Jsoup.Теперь у меня есть некоторые проблемы в анализе DOM.Я тестирую анализатор HTML для html-страницы https://list.youku.com/show/id_zcc001f06962411de83b1.html и не могу получить правильный ответ.
Я опубликовал свой код в github, и в проекте есть несколько тестовых случаев.Я надеюсь, что любой может клонировать код и запустить тестовые сценарии, чтобы узнать ответ.
Это мой адрес на github: https://github.com/sunyue1380/QuickHttp
HTMLParser: проанализировать исходную строку и создать список HTMLToken
HTMLTokenParser: проанализировать список HTMLToken и построить дерево DOM.
1: тест HTMLParser в порядке, поэтому QuickHttp успешно разделил исходную строку на htmltoken (как я определил в проекте).
2: Тест HTMLTokenParser не выполнен, поэтому процесс сборки DOM не выполнен.
HTMLToken:
public class HTMLToken {
public int start;
public int end;
public String value;
public TokenType tokenType;
public String toString() {
return value.replaceAll("\r\n", "换行符") + "[" + tokenType.name + "]";
}
public enum TokenType {
openTag("开始标签"),
tagName("标签名称"),
attribute("标签属性"),
openTagClose("开始标签结束"),
textContent("标签文本内容"),
closeTag("结束标签"),
literal("在结束标签与开始标签之间的空白中"),
commentTag("注释标签");
private String name;
TokenType(String name) {
this.name = name;
}
}
}
HTMLParser:
/**词法分析*/
private void parseHTML(){
while(pos<chars.length){
switch(state){
case openingTag:{
if(isNextMatch("!--")){
//<!--comment-->
addToken(HTMLToken.TokenType.openTag);
state = State.inComment;
}else if(pos>0&&chars[pos-1]=='<'){
//<body
addToken(HTMLToken.TokenType.openTag);
state = State.inTagName;
}
}break;
case inTagName:{
if(chars[pos]==' '){
//<body id="identify">
addToken(HTMLToken.TokenType.tagName);
String tagName = tokenList.get(tokenList.size()-1).value.toLowerCase();
if(isSingleNode(tagName)){
singleNode = true;
}else {
singleNode = false;
}
state = State.inAttribute;
}else if(chars[pos]=='>'){
//<body> <input> <br/>
addToken(HTMLToken.TokenType.tagName);
String tagName = tokenList.get(tokenList.size()-1).value.toLowerCase();
if(isSingleNode(tagName)){
singleNode = true;
state = State.closingTag;
}else {
singleNode = false;
state = State.openTagClosing;
}
}else if(isNextMatch("/>")){
addToken(HTMLToken.TokenType.tagName);
state = State.closingTag;
}
}break;
case inComment:{
//<!--comment-->
if(chars[pos]=='>'&&chars[pos-1]=='-'&&chars[pos-2]=='-'){
addToken(HTMLToken.TokenType.commentTag);
singleNode = true;
state = State.closingTag;
}
}break;
case inAttribute:{
if(chars[pos]=='>'||(isNextMatch("?>"))){
addToken(HTMLToken.TokenType.attribute);
state = singleNode? State.closingTag: State.openTagClosing;
}else if(isNextMatch("/>")){
addToken(HTMLToken.TokenType.attribute);
state = State.closingTag;
}
}break;
case openTagClosing:{
//<input>
if(chars[pos-1]=='>'&&chars[pos]!='<'){
//<body>text</body>
addToken(HTMLToken.TokenType.openTagClose);
state = State.inTextContent;
}else if(isNextMatch("</")){
//<body></body>
addToken(HTMLToken.TokenType.openTagClose);
state = State.closingTag;
}else if(chars[pos]=='<'){
//<body><p></p>
addToken(HTMLToken.TokenType.openTagClose);
state = State.openingTag;
}
}break;
case inTextContent:{
if(isInStyleOrScript){
if(isNextMatch("</script>")||isNextMatch("</style>")){
addToken(HTMLToken.TokenType.textContent);
isInStyleOrScript = false;
state = State.closingTag;
}
}else if(isNextMatch("</")){
//<body>textContent</body>
addToken(HTMLToken.TokenType.textContent);
state = State.closingTag;
}else if(chars[pos]=='<'){
//<body>textContent<p></p>
addToken(HTMLToken.TokenType.textContent);
state = State.openingTag;
}
}break;
case closingTag:{
if(chars[pos-1]=='>'&&isNextMatch("</")){
//</body></html>
addToken(HTMLToken.TokenType.closeTag);
}else if(chars[pos-1]=='>'&&chars[pos]!='<'){
//</body> </html>
addToken(HTMLToken.TokenType.closeTag);
state = State.inLiteral;
}else if(chars[pos-1]=='>'&&chars[pos]=='<'){
//</body><script>
addToken(HTMLToken.TokenType.closeTag);
state = State.openingTag;
}else if(pos==chars.length-1){
//</html>$
addToken(HTMLToken.TokenType.closeTag);
break;
}
}break;
case inLiteral:{
if(isNextMatch("</")){
//</body> </html>
addToken(HTMLToken.TokenType.literal);
state = State.closingTag;
}else if(chars[pos]=='<'){
//</body> <p>
addToken(HTMLToken.TokenType.literal);
state = State.openingTag;
}
}break;
}
pos++;
}
logger.trace("[Token列表]{}",tokenList.toString());
}
HTMLTokenParser:
AbstractElement current = root;
for(int i=0;i<htmlTokenList.size();i++){
HTMLToken htmlToken = htmlTokenList.get(i);
try {
switch(htmlToken.tokenType){
case openTag:{
AbstractElement newElement = new AbstractElement();
allElements.add(newElement);
if(current==null){
root = newElement;
}else{
newElement.parent = current;
newElement.parent.childList.add(newElement);
}
current = newElement;
}break;
case tagName:{
current.tagName = htmlToken.value.toLowerCase();
}break;
case commentTag:{
current.isComment = true;
current.ownOriginText = htmlToken.value;
current.ownText = escapeOwnOriginText(current.ownOriginText);
}break;
case attribute:{
current.attribute = htmlToken.value;
current.attributes.putAll(AttributeParser.parse(htmlToken.value));
}break;
case openTagClose:{
}break;
case textContent:{
current.originTextNodes.add(htmlToken.value);
current.textNodes.add(escapeOwnOriginText(htmlToken.value));
}break;
case closeTag:{
if(htmlToken.value.equals(">")||htmlToken.value.equals("/>")){
current.isSingleNode = true;
}
//sometimes current may be null and i don't know why
current = current.parent;
}break;
}
}catch (Exception e){
break;
}
}
Элемент:
class AbstractElement implements Element {
/**节点名称*/
private String tagName;
/**是否是单节点*/
private boolean isSingleNode;
/**是否是注释节点*/
private boolean isComment;
/**父节点*/
private AbstractElement parent;
/**属性*/
private Map<String,String> attributes = new HashMap<>();
/**属性文本*/
private String attribute = "";
/**原始文本内容*/
private String ownOriginText;
/**转义后文本内容*/
private String ownText;
/**子节点*/
private List<Element> childList = new ArrayList<>();
/**深度遍历后的元素*/
private Elements allElements;
/**所有节点文本*/
private String textContent;
/**原始节点文本列表*/
private List<String> originTextNodes = new ArrayList<>();
/**转义节点文本列表*/
private List<String> textNodes = new ArrayList<>();
/**节点在父节点的子节点中的索引*/
private int elementSiblingpos = -1;
/**用于深度遍历*/
private boolean isVisited;
}
url: https://list.youku.com/show/id_zcc001f06962411de83b1.html
фактический результат:
case closeTag:{
if(htmlToken.value.equals(">")||htmlToken.value.equals("/>")){
current.isSingleNode = true;
}
//sometimes current may be null and i don't know why
current = current.parent;
}break;
current может быть нулевым, и это вызывает сбой процесса сборки DOM, и я хочузнаю почему.