2013-08-01 15 views
5

prima di iniziare il collegamento a RegEx match open tags except XHTML self-contained tags leggere tutta la domanda.Come creare un parser HTML?

Mi piacerebbe scrivere un parser HTML (solo per HTML 5, dovrebbe controllare se è HTML 5 e se no, restituire un errore) solo per imparare qualcosa di nuovo, ma non so cosa sia il modo migliore per farlo. Lasciate che vi mostri un esempio:

<!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> 

Ora, Qualcuno mi potrebbe mostrare come analizzare questo (forma finale non importa, solo un concetto)? Ho avuto alcune idee (come l'uso di funzioni ricorsive, o fare riferimenti ad array che contengono tag reali), ma non penso che questi fossero i migliori concetti. Devo controllare char by char e quindi chiamare funzioni specifiche o usare espressioni regolari (spiegato sotto)?

Utilizzando le espressioni regolari, non intendo un modello per l'intero tag. Piuttosto intendo usare un pattern per il tagname (e se questo restituisce true, controlla i pattern successivi), quindi per l'attributo (e se questo restituisce true, controlla di nuovo), e infine controlla la fine del tag.

Cosa devo fare quando trovo il tag? Esegui un ciclo che controlla i tag (e se trova il tag, chiamalo ancora e ancora ...)? Ma per me sembra come funzione ricorsiva o almeno a metà ricorsivo quando la funzione X chiama Y che chiama X ...

Quindi la domanda finale è: qual è la struttura più efficiente e corretta per questo?

+2

Non credo che la tua risposta mi aiuti ...Ho visto quella domanda prima e ho scritto nella mia domanda "** Usando le espressioni regolari non intendo un modello per l'intero tag. **" E a proposito, come hai letto questo in meno di 2 minuti? – user1951214

risposta

2

La parte più importante della scrittura di un parser basato su SGML è il lexer. Ecco un articolo sulla costruzione di un lexer personalizzato: http://onoffswitch.net/building-a-custom-lexer/.

A mio parere, le espressioni regolari sarebbero probabilmente eccessive/inappropriate: si desidera associare i token HTML e l'analisi carattere per carattere è probabilmente il modo migliore per farlo.

+1

+1 per aver menzionato un lexer. Questo è ciò che l'OP ha davvero bisogno di guardare. Per ulteriori informazioni sulla necessità di un lexer, il commento di @ JXG in questa discussione è istruttivo: http://stackoverflow.com/questions/2400623/how-do-html-parses-work-if-theyre-not-using-regexp ? rq = 1 –

+1

Ciao, sono l'autore di quel post sul blog, volevo solo dire che se conosci la tua grammatica puoi iniziare a lavorare più velocemente usando un parser generator come ANTLR. Costruire tutto il lexer da zero potrebbe essere eccessivo a seconda di ciò che si desidera. Inoltre, varrebbe la pena esaminare i combinatori di parser e le librerie combinator per ottenere un AST senza dover passare attraverso una fase tokenize – devshorts

3

@ La risposta di Kian menziona usando un lexer, ma in termini di algoritmi penso che vorrete usare la ricorsione. HTML è dopo tutto una struttura ricorsiva:

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

Ecco un esempio ingenuo JS - anche se non è un'implementazione completa. (Ho incluso il supporto per <empty /> elementi, per <!-- comments -->, per &entities;; per xmlns:namespaces ... scrivere un HTML completo a tutti gli effetti o parser XML è un impegno enorme, in modo da non prendere alla leggera)

Questa soluzione in particolare ignora il processo di analisi lessicale, ma ho deliberatamente omesso di contrastare la mia risposta con @ 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; 
} 

Idealmente ciascuna di queste funzioni sarebbero mappa molto da vicino sulla grammatica definita nel XML specification. Per esempio, la specifica definisce un element in questo modo:

element ::= EmptyElemTag | STag content ETag 

... così idealmente vorremmo la funzione parseElement() a guardare più in questo modo:

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

... ma ho tagliare alcuni angoli nello scrivere il mio esempio, in modo che non rifletta la grammatica reale quanto dovrebbe.

Problemi correlati