2013-08-01 21 views
5

trước khi bạn bắt đầu liên kết với RegEx match open tags except XHTML self-contained tags đọc toàn bộ câu hỏi.Làm cách nào để tạo trình phân tích cú pháp HTML?

Tôi muốn viết trình phân tích cú pháp HTML (chỉ cho HTML 5, nó nên kiểm tra xem đó là HTML 5 và nếu không, trả lại lỗi) chỉ để tìm hiểu bản thân một cái gì đó mới, nhưng tôi không biết cách tốt nhất để làm điều đó. Hãy để tôi chỉ cho bạn một ví dụ:

<!doctype html> 
<html> 
<head> 
    <!-- #TITLE --> 
    <title>Just an example</title> 
</head> 
<body> 
    <p class='main'>Simple paragraph with an <a href='/a.html'>anchor</a></p> 
</body> 
</html> 

Bây giờ, bất cứ ai có thể chỉ cho tôi cách phân tích cú pháp này (biểu mẫu cuối cùng không quan trọng, chỉ là khái niệm)? Tôi đã có một số ý tưởng (như sử dụng các hàm đệ qui, hoặc tham chiếu đến mảng chứa thẻ thực), nhưng tôi không nghĩ đây là những khái niệm tốt nhất. Tôi có nên kiểm tra char bằng char và sau đó gọi các chức năng cụ thể hoặc sử dụng cụm từ thông dụng (được giải thích bên dưới)?

Bằng cách sử dụng cụm từ thông dụng, tôi không có nghĩa là một mẫu cho toàn bộ thẻ. Tôi thích sử dụng một mẫu cho tagname (và nếu cái này trả về true, hãy kiểm tra các mẫu tiếp theo), sau đó cho thuộc tính (và nếu thuộc tính này trả về true, hãy kiểm tra lại), và cuối cùng kiểm tra phần cuối của thẻ.

Tôi nên làm gì khi tìm thấy thẻ? Chạy một vòng lặp kiểm tra các thẻ (và nếu nó tìm thấy thẻ, gọi nó một lần nữa và một lần nữa ...)? Nhưng đối với tôi, nó có vẻ như hàm đệ quy hoặc ít nhất là đệ quy một nửa khi hàm X gọi Y gọi là X ...

Vì vậy, câu hỏi cuối cùng là: cấu trúc chính xác và hiệu quả nhất cho điều đó là gì?

+2

Tôi không nghĩ câu trả lời của bạn giúp tôi ...Tôi đã thấy câu hỏi đó trước đây và tôi đã viết câu hỏi của mình "** Bằng cách sử dụng cụm từ thông dụng, tôi không có nghĩa là một mẫu cho toàn bộ thẻ. **" Và nhân tiện, bạn đã đọc bài này trong chưa đầy 2 phút? – user1951214

Trả lời

2

Phần lớn nhất của việc viết một trình phân tích dựa trên SGML là lexer. Đây là một bài viết về xây dựng một lexer tùy chỉnh: http://onoffswitch.net/building-a-custom-lexer/.

Theo tôi, các biểu thức thông thường có thể quá mức/không phù hợp - bạn muốn khớp mã HTML và ký tự bằng phân tích ký tự có lẽ là cách tốt nhất để thực hiện việc này.

+1

+1 để đề cập đến một từ khóa. Đó là điều mà OP thực sự cần phải xem xét. Để biết thêm chi tiết về sự cần thiết cho một lexer, bình luận của @ JXG trong chủ đề này là hướng dẫn: http://stackoverflow.com/questions/2400623/how-do-html-parses-work-if-theyre-not-using-regexp ? rq = 1 –

+1

Xin chào, tôi là tác giả của bài đăng trên blog đó, chỉ muốn đề cập rằng nếu bạn biết ngữ pháp của mình, bạn có thể bắt đầu và chạy nhanh hơn bằng trình tạo trình phân tích cú pháp như ANTLR. Xây dựng toàn bộ lexer từ đầu có thể là quá mức cần thiết tùy thuộc vào những gì bạn muốn. Ngoài ra, nó sẽ là giá trị nhìn vào các bộ phối hợp phân tích cú pháp và thư viện tổ hợp để có được một AST mà không cần phải đi qua một giai đoạn tokenize – devshorts

3

@ Câu trả lời của Kian đề cập đến việc sử dụng từ vựng, nhưng về mặt thuật toán tôi nghĩ bạn sẽ muốn sử dụng đệ quy. HTML sau tất cả cấu trúc đệ quy:

<div> 
    <div> 
     <div> 
     </div> 
    </div> 
</div> 

Đây là một ví dụ JS ngây thơ - mặc dù nó không phải là một triển khai hoàn chỉnh. (Tôi đã bao gồm không hỗ trợ cho <empty /> yếu tố, ví <!-- comments -->; cho &entities;; cho xmlns:namespaces ... viết một HTML đầy đủ fledged hoặc phân tích cú pháp XML là một công việc rất lớn, do đó, không mang nó nhẹ)

Giải pháp này đáng chú ý bỏ qua quá trình phân tích từ vựng, nhưng tôi đã cố tình bỏ qua điều đó để đối chiếu câu trả lời của tôi với @ Kian's.

var markup = "<!DOCTYPE html>\n"+ 
      "<html>\n"+ 
      " <head>\n"+ 
      " <title>Example Input Markup</title>\n"+ 
      " </head>\n"+ 
      " <body>\n"+ 
      " <p id=\"msg\">\n"+ 
      "  Hello World!\n"+ 
      " </p>\n"+ 
      " </body>\n"+ 
      "</html>"; 

parseHtmlDocument(markup); 

// Function definitions 

function parseHtmlDocument(markup) { 
    console.log("BEGIN DOCUMENT"); 
    markup = parseDoctypeDeclaration(markup); 
    markup = parseElement(markup); 
    console.log("END DOCUMENT"); 
} 

function parseDoctypeDeclaration(markup) { 
    var regEx = /^(\<!DOCTYPE .*\>\s*)/i; 
    console.log("DOCTYPE DECLARATION"); 
    var matches = regEx.exec(markup); 
    var doctypeDeclaration = matches[1]; 
    markup = markup.substring(doctypeDeclaration.length); 
    return markup; 
} 

function parseElement(markup) { 
    var regEx = /^\<(\w*)/i; 
    var matches = regEx.exec(markup); 
    var tagName = matches[1]; 
    console.log("BEGIN ELEMENT: "+tagName); 
    markup = markup.substring(matches[0].length); 
    markup = parseAttributeList(markup); 
    regEx = /^\>/i; 
    matches = regEx.exec(markup); 
    markup = markup.substring(matches[0].length); 
    markup = parseNodeList(markup); 
    regEx = new RegExp("^\<\/"+tagName+"\>"); 
    matches = regEx.exec(markup); 
    markup = markup.substring(matches[0].length); 
    console.log("END ELEMENT: "+tagName); 
    return markup; 
} 

function parseAttributeList(markup) { 
    var regEx = /^\s+(\w+)\=\"([^\"]*)\"/i; 
    var matches; 
    while(matches = regEx.exec(markup)) { 
     var attrName = matches[1]; 
     var attrValue = matches[2]; 
     console.log("ATTRIBUTE: "+attrName); 
     markup = markup.substring(matches[0].length); 
    } 
    return markup; 
} 

function parseNodeList(markup) { 
    while(markup) { 
     markup = parseTextNode(markup); 
     var regEx = /^\<(.)/i; 
     var matches = regEx.exec(markup); 
     if(matches[1] !== '/') { 

      markup = parseElement(markup); 
     } 
     else { 
      return markup; 
     } 
    } 
} 

function parseTextNode(markup) { 
    var regEx = /([^\<]*)\</i; 
    var matches = regEx.exec(markup); 
    markup = markup.substring(matches[1].length); 
    return markup; 
} 

Lý tưởng nhất là mỗi chức năng này sẽ ánh xạ rất gần với ngữ pháp được xác định trong XML specification. Ví dụ, đặc tả định nghĩa một element như vậy:

element ::= EmptyElemTag | STag content ETag 

... do đó, lý tưởng chúng tôi muốn các parseElement() chức năng để trông như thế này:

function parseElement(markup) { 
    if(nextTokenIsEmptyElemTag) { // this kind of logic is where a lexer will help! 
     parseEmptyElemTag(markup); 
    } 
    else { 
     parseSTag(markup); 
     parseContent(markup); 
     parseETag(markup); 
    } 
} 

... nhưng tôi đã cắt một số góc bằng văn bản ví dụ của tôi, vì vậy nó không phản ánh ngữ pháp thực tế chặt chẽ như nó phải.

Các vấn đề liên quan