семантический анализатор для html fail - PullRequest
0 голосов
/ 03 июня 2019

Я написал фреймворк для 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, и я хочузнаю почему.

1 Ответ

0 голосов
/ 04 июня 2019

я решил этот вопрос.HTML-код

<input value="<iframe src='http://player.youku.com/embed/XNTQwMTgxMTE2' allowfullscreen></iframe>"/>

раньше, я обрежу его до

<,input,value="<iframe src='http://player.youku.com/embed/XNTQwMTgxMTE2' allowfullscreen,>,</iframe>,",/>

на самом деле, это должно быть

<,input,value="<iframe src='http://player.youku.com/embed/XNTQwMTgxMTE2' allowfullscreen</iframe>",/>

Я решил этот вопрос, поэтому я закрываюэто.

...