La nascita di Gecko: il motore di parsing nel 1998

La nascita di Gecko: il motore di parsing nel 1998

Quella che segue è la traduzione di un documento storico della Mozilla Foundation che testimonia la nascita di uno dei componenti fondamentali di Gecko. Siamo nel 1998.

Sguardo d'insieme

Il parser è il primo passo nella sequenza di sistemi che interagiscono per permettere ad un browser di visualizzare i documenti HTML. Affinché NGLayout abbia successo, il parser deve essere veloce, estensibile e soprattutto deve offrire una robusta gestione degli errori.

Il motore di parsing in NGLayout ha un design modulare che permette al sistema di analizzare ogni tipo di dati (naturalmente il motore è ottimizzato per l'HTML).

A livello concettuale, un motore di parsing viene usato per trasformare un documento sorgente da una forma all'altra. Nel caso dell'HTML, il parser trasforma la gerarchia di tag HTML (la forma sorgente) nella forma che costituisce la struttura del layout richiesta dal motore di layout (la forma di destinazione).

Componenti principali

Il motore di parsing fornisce un insieme di componenti che hanno una funzione nel processo di trasformazione man mano che il documento passa dalla forma sorgente a quella di destinazione. Definiamo questi oggetti componenti perché formano in modo dinamico un runtime per ottenere la trasformazione. Sostituendo un diverso insieme di componenti, potete eseguire trasformazioni alternative.

Scanner

Il primo componente principale nel motore di parsing è lo scanner. Lo scanner fornisce una API incrementale che offre metodi per accedere ai caratteri nel flusso di input (di solito un URL), trovando particolari sequenze, collazionando dati di input e scartando dati non desiderati. La nostra esperienza ha dimostrato che un semplice scanner può essere usato per analizzare tutto, dall'HTML all'XML, fino al C++.

Parser

Il secondo componente principale è il parser stesso. Il parser controlla e coordina l'attività degli altri componenti del sistema. Questo approccio si basa sul fatto che a prescindere dalla forma del documento sorgente, il processo di trasformazione resta lo stesso (come spiegheremo in seguito). Mentre gli altri componenti del sistema sono concepiti per essere sostituiti dinamicamente secondo il tipo di documento sorgente, raramente è necessario modificare il parser.

Il parser guida anche la tokenizzazione. La tokenizzazione è il processo di collazione di unità atomiche (caratteri) nel flusso di input in strutture di livello più alto chiamate token. Per esempio, il tokenizzatore HTML converte un flusso di input grezzo di caratteri in tag HTML. Per ottenere la massima flessibilità, il tokenizzatore non fa supposizioni sulla grammatica. I dettagli della grammatica effettiva vengono invece inviati al componente DTD che comprende i costrutti su cui si basa la grammatica. L'importanza di questa decisione nel design è che si permette al motore di cambiare dinamicamente il linguaggio tokenizzato senza modificare il tokenizzatore stesso.

DTD

Il componente finale nel motore di parsing è la DTD, che descrive le regole per i documenti ben formati e/o validi nella grammatica di destinazione. In HTML, la DTD dichiara e definisce l'insieme di tag, l'insieme di attributi associati e le regole gerarchiche di annidamento dei tag HTML. Separando questo componente dagli altri diviene possibile usare lo stesso sistema per analizzare un'ampia gamma di tipi di documento. In breve, questo significa che lo stesso parser può fornire input al browser, comportandosi (tramite la DTD) come Navigator, IE o qualsiasi altro browser HTML. Lo stesso si può dire per XML.

Sink

Una volta che il processo di tokenizzazione è completo, il motore di parsing deve emettere il suo contenuto (token). Poiché il parser non conosce il modello di documento, l'applicazione che lo contiene deve fornire un sink di contenuto. Il sink è una semplice API che accetta un nodo contenitore, delle foglie e dei nodi di testo, e costruisce di conseguenza il sottostante modello del documento. La DTD interagisce con il sink per fare in modo che il modello di contenuto venga costruito in base all'insieme di token di input.

Sebbene questi oggetti possano sembrare confusi, questo semplice diagramma illustra le relazioni di runtime tra di loro:

<inserire qui un'immagine del parser>

Implementazione

Fase 1 – Costruzione degli oggetti

Analizzare un documento è un'operazione semplice. L'applicazione da il via al parsing creando un oggetto nIURL, un oggetto nsTokenizer e un oggetto nsHTMLParse. Al parser viene assegnato un sink e una DTD (ricordate che la DTD comprende la grammatica del documento da analizzare, mentre l'interfaccia sink permette alla DTD di costruire un modello di contenuto in modo corretto).

Fase 2 – Aprire un flusso di input

Il processo di parsing comincia quando viene aperto l'URL, e il contenuto viene fornito sotto forma di flusso di input di rete. Il flusso viene dato allo scanner, che controlla gli accessi. Il motore di parsing istruisce il tokenizzatore ad iniziare la fase di tokenizzazione. La tokenizzazione è un processo incrementale, e può interrompersi quando la scanner si blocca in attesa di dati dalla rete.

Fase 3 – Tokenizzazione

Il tokenizzatore controlla e coordina la tokenizzazione del flusso di input in una collezione di CTokens (differenti grammatiche avranno le loro sottoclassi di Ctoken, come abbiamo fatto creando CHTMLToken, e le loro DTD). Quando il tokenizzatore è in esecuzione, chiama ripetutamente il metodo GetToken(). Continua fino a quando non incontra una EOF nel flusso di input, o un errore fatale.

Fase 4 – Iterazione dei token/costruzione del documento

Dopo che la fase di tokenizzazione è completa, si passa alla fase di iterazione dei token che valida il documento e costruisce il modello di contenuto. L'iterazione dei token va avanti fino a quando non si incontra un errore fatale, o fino a quando il parser non ha visitato ciascun token. I token vengono raccolti in gruppi di informazioni correlate secondo le regole fornite dalla classe nsDTD. La DTD controlla l'ordine nel quale appaiono i token in relazione l'uno con l'altro. Ad intervalli di tempo precisi, il parser notifica al sink di contenuto il contesto del parsing, istruendo il sik a costruire il documento secondo lo stato del parser.

Fase 5 – Distruzione degli oggetti

Una volta che la tokenizzazione e l'iterazione sono concluse, gli oggetti del sistema di parsing vengono distrutti per conservare la memoria.

Di altro interesse...

Oltre all'analisi dei documenti e al supporto dinamico alla DTD, il motore di parsing supporta anche gli osservatori per i dati di I/O. Lo scopo di queste interfacce è quello di permettere ad oggetti sicuri di inserirsi nel sistema di I/O durante il runtime, trasformando il flusso sottostante prima che il parser lo veda. Questo risulta utile nei casi in cui viene richiesta una preelaborazione, o una trasformazione di tipi di documento diversi in HTML.

Dipendenze

Il motore di parsing dipende dalle seguenti classi/sistemi:

  • nsString
  • nsCore.h (e prtypes.h)
  • Il sistema XP_COM
  • Netlib (per gli URL e i flusso di input)

Roadmap

I prossimi miglioramenti al parser riguarderanno le seguenti aree:

  • Supporto per documenti XML ben formati e/o validi.
  • Supporto per processori del documento come XSL e altri.
  • Compatibilità a ritroso – miglioramenti alla DTD HTML.
  • Affinamento della performance.

Bug conosciuti

Attualmente il lavoro sulle DTD è ancora in svolgimento. Ci si aspetta un rapido miglioramento nei prossimi mesi.

Torna su