Alma Mater Studiorum · Università di Bologna FACOLTÀ DI SCIENZE MATEMATICHE, FISICHE E NATURALI Corso di Laurea Triennale in Informatica Assegnamento di tipi condizionali in XSDL 1.1: tecniche di verifica statica della corretta derivazione di tipi Tesi di Laurea in Tecnologie Web/Internet Relatore: Chiar.mo Prof. FABIO VITALI Presentata da: MAURIZIO CASIMIRRI Correlatore: Dott. PAOLO MARINELLI Sessione III Anno Accademico 2006/2007 Indice 1 Introduzione 5 2 XSDL 1.1 e Conditional Type Assignment 2.1 2.2 13 Co-occurrence constraint . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.1.1 Classificazione dei co-constraint . . . . . . . . . . . . . . . . . 14 2.1.2 Importanza dei co-constraint . . . . . . . . . . . . . . . . . . . 16 2.1.3 Supporto ai co-constraint nei linguaggi di validazione . . . . . 17 Supporto ai co-constraint in XSDL 1.1 . . . . . . . . . . . . . . . . . 18 2.2.1 Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.2.2 Conditional Type Assignment . . . . . . . . . . . . . . . . . . 20 3 Verifica statica dei vincoli di restrizione con CTA 25 3.1 CTA e restrizione di tipi complessi . . . . . . . . . . . . . . . . . . . 25 3.2 Tecniche per la verifica dei vincoli di restrizione . . . . . . . . . . . . 27 3.3 3.2.1 Prefix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.2.2 Vincolare il numero delle alternative . . . . . . . . . . . . . . 31 3.2.3 Run-time Checking . . . . . . . . . . . . . . . . . . . . . . . . 31 Prodotto Cartesiano . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.3.1 Normalizzazione delle type table . . . . . . . . . . . . . . . . . 42 3.3.2 Prodotto Cartesiano con type table normalizzate . . . . . . . . 44 3.3.3 Confronto con Run-Time Checking . . . . . . . . . . . . . . . 47 4 Implementazione 4.1 Xerces e XML Schema API . . . . . . . . . . . . . . . . . . . . . . . 50 4.1.1 4.2 49 XSModel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 Architettura globale . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 4.2.1 Visita generica del XSModel . . . . . . . . . . . . . . . . . . . 53 4.2.2 Gerarchia dei tipi . . . . . . . . . . . . . . . . . . . . . . . . . 54 4.2.3 XNI e SchemaPreprocessor . . . . . . . . . . . . . . . . . . . 55 3 4 INDICE 4.3 4.4 4.5 4.2.4 Semplificazione degli XPath Prodotto Cartesiano . . . . . . . . Test . . . . . . . . . . . . . . . . . 4.4.1 Parser e validatore di test . 4.4.2 Integrazione in SchemaPath 4.4.3 TestSuite . . . . . . . . . . Limitazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 57 59 59 59 59 61 5 Conclusioni 63 5.1 Sviluppi futuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Capitolo 1 Introduzione L’introduzione dell’assegnamento condizionale del tipo (Conditional Type Assignment o CTA) in XML Schema 1.1 porta con sè il problema della verifica della corretta restrizione dei tipi complessi che fanno uso di questo meccanismo. Nelle specifiche è suggerita, ma non imposta, una tecnica per effettuare tale controllo in fase di validazione. In questa tesi sarà mostrato che è possibile risolvere il problema attraverso un approccio parzialmente statico, ossia in parte a tempo di compilazione dello schema ed in parte a tempo di validazione delle istanze. L’assegnamento di tipo condizionale è un meccanismo che consente di decidere il tipo di un elemento sulla base della valutazione di condizioni XPath. Le regole che CTA consente di esprimere appartengono alla classe dei co-constraint, ovvero vincoli incrociati sulla presenza o sul valore dei nodi di un documento. Nella strutturazione e validazione di dati machine understandable, non poter esprimere una vasta categoria di vincoli come i co-constraint significa escluderne il controllo dal processo di validazione, richiedendo che tali controlli siano effettuati da componenti apposite, ed impedendo di specificare in modo formale i requisiti del sistema sui dati. Questo può portare ad un indebolimento dell’interoperabilità tra le componenti dei sistemi stessi. CTA risponde alla necessità di estendere XML Schema per il supporto dei coconstraint, consentendo specificare in modo esaustivo le regole di composizione dei documenti. Attraverso XML è possibile marcare i documenti con meta-informazioni sul contentuto. Gli autori di documenti XML possono scegliere il markup da utilizzare per imporre loro una struttura logica. I linguaggi di schema permettono di descrivere, in maniera formale, classi di documenti XML. Attraverso il loro uso è possibile definire una struttura specifica per i linguaggi di markup basati su sintassi XML, ed effet5 6 1. Introduzione tuare controlli sulla correttezza delle loro istanze. Il loro impiego è fondamentale in ogni campo applicativo che coinvolge l’uso di XML. Un linguaggio di schema permette di definire regole secondo le quali un documento XML appartiene ad una particolare categoria. Nello specifico essi consentono la descrizione di regole, prevalentemente sulla struttura, che un documento deve rispettare affinchè sia valido. Una importante categoria di restrizioni è rappresentata dai co-constraint. I coconstraint sono vincoli incrociati sull’occorrenza dei nodi all’interno di un documento XML. Per mezzo di essi è possibile, ad esempio, imporre che un documento contenga uno specifico elemento solo in presenza di un attributo con un valore particolare. I co-constraint dunque sono vincoli sulla struttura o sul contenuto di un documento in dipendenza del contesto. La categoria dei co-constraint è molto ampia. Tuttavia possiamo identificare alcune tipologie principali. Una prima importante tipologia di co-constraint è costituita dai vincoli incrociati sul contenuto di attributi o di elementi, ovvero le regole del tipo: se l’attributo x ha valore y allora l’elmento <e> deve contenere un figlio <f>. Inoltre sono co-constraint anche i vincoli di mutua dipendenza tra nodi, ad esempio: se è presente l’attributo link l’elemento <content> non deve avere figli. Infine possiamo identificare una terza categoria fondamentale di co-constraint tra quelli che vincolano il dominio di elementi o attributi al valore di altri nodi. Un semplice esempio è il seguente: il valore dell’attributo min non deve essere mai superiore al valore dell’attributo max. Per avere un’idea di quanto siano importanti i co-constraint nella descrizione di linguaggi basati su sintassi XML basta prendere in esame alcuni linguaggi molto diffusi. Tra le loro regole di validità troviamo numerose restrizioni sul contenuto che fanno riferimento alla categoria dei co-constraint. In XHTML ad esempio non è possibile annidare gli elementi <a>, mentre in XSLT è imposto che in un elemento <template> compaia almeno uno tra gli attributi match o name. XML Schema 1.0 è l’unico linguaggio di validazione ad aver raggiunto lo stadio di W3C Recommendation, la fase finale di una ratifica del W3C. Nonostante il suo largo impiego, in XML Schema non è possibile esprimere i co-constraint. Per dare una breve idea di quanto questa carenza ne limiti l’espressività basta pensare che lo schema per XML Schema non è completamente descrivibile nel linguaggio stesso. I sistemi che necessitano di co-constraint dunque devono ricorrere alla validazione 7 attraverso linguaggi differenti, ad esempio Schematron, o effettuare controlli all’interno di componenti software specifiche. Il supporto ai co-constraint è stato introdotto in XSDL 1.1 attraverso i meccanismi di Assertions e Conditional Type Assignment(CTA) e l’ampliamento del linguaggio con i relativi costrutti. Questi nuovi meccanismi fanno uso di predicati XPath per identificare i nodi su cui i vincoli sono espressi. Le asserzioni sono costrutti comuni nell’ambito dei linguaggi di programmazione. Un’asserzione è un controllo sul flusso dell’esecuzione che assicura il soddisfacimento di determinate condizioni in un preciso punto del programma. Il fallimento di un’asserzione è indice di un comportamento scorretto. In maniera analoga le asserzioni in XSDL 1.1 specificano quali condizioni debbano soddisfare gli elementi dichiarati nel content model di un tipo complesso affinchè essi siano validi. CTA è progettato sulla base di SchemaPath, un’estensione di XSDL 1.0 creata appositamente per fornire supporto ai co-constraint. Allo stesso modo infatti provvede una sintassi ampliata per le dichiarazioni di elemento, consentendo di specificare il tipo degli elementi in dipendenza dal contesto del documento d’istanza. In particolare il tipo dell’elemento può essere assegnato in base al verificarsi di predicati XPath. Ogni dichiarazione di elemento dunque può possedere una type table, ossia una sequenza di coppie, predicato-tipo, in base alla quale viene assegnato a tempo di validazione il tipo per i corrispettivi elementi. Una delle caratteristiche di CTA è che rispetta i principi di modularità ed estendibilità dello schema. In particolare è possibile derivare da tipi che racchiudono dichiarazioni di elemento condizionali. A questo proposito, come accennato, l’introduzione di CTA porta con sè alcuni interrogativi circa le relazioni di restrizione in presenza di assegnamento di tipi condizionale. Dal momento che l’assegnamento dei tipi non è più univoco si possono infatti verificare situazioni al momento della validazione per cui il tipo condizionale da assegnare ad un elemento non sia una valida restrizione del corrispettivo tipo per la dichiarazione nella base o in un tipo antenato. Si pongono nello specifico due problemi: decidere se uno schema che permette di violare a run-time i vincoli di restrizione possa essere considerato valido, e decidere in che modo possa essere verificata la validità altrimenti. Nel corso della stesura del draft di XSDL 1.1 sono state discusse diverse soluzioni, 8 1. Introduzione che potremmo identificare sostanzialmente in due tipologie. Un primo insieme di proposte riguarda l’imposizione di vincoli sintattici al fine di impedire la scrittura di schemi potenzialmente scorretti. Praticamente tutte queste soluzioni però limitano l’espressività di CTA restringendone lo spettro di applicabilità. Una seconda categoria di proposte al contrario non impone vincoli a CTA, e mira a rilevare la totalità delle situazioni erronee sia in fase statica di compilazione dello schema, che in fase dinamica. Tra le soluzioni del primo tipo Prefix è particolarmente interessante. Questa, sebbene imponga limitazioni sull’uso di CTA, si propone come valido compromesso tra espressività e decidibilità statica dei vincoli di restrizione. Sostanzialmente prefix richiede che la type table di una dichiarazione di elemento sia ripetuta all’inizio delle type table delle dichiarazioni di elemento che compaiono nei content model ristretti. Sono stati individuati, tuttavia, diversi casi d’uso che non sono contemplati da questa tecnica, e per questo non è stata presa in considerazione come soluzione adottabile. Tra le soluzioni che non impongono alcuna restrizione è necessario distinguere ancora tra soluzioni completamente statiche, completamente dinamiche ed ibride. Sebbene siano state avanzate alcune proposte di verifica completamente statica, fino ad ora non sono state individuate tecniche di verifica attuabili1 . Trascuriamo dunque tali proposte per analizzare una tecnica completamente dinamica nota come Run-Time Check ed una tecnica ibrida, considerata e ampliata in questa tesi, nota come Prodotto Cartesiano. Run-Time Check (RTC) consiste nella verifica delle relazioni di restrizione a tempo di validazione. In particolare per ogni elemento si determina il tipo in dipendenza dal contesto sia per la dichiarazione effettivamente utilizzata che per le dichiarazioni racchiuse nei tipi antenati nella gerarchia di restrizione. Su di essi vengono dunque effettuati i controlli di corretta restrizione. Un limite osservabile per tale tecnica è che, poichè le relazioni di restrizione sono proprietà dello schema, è poco conveniente verificare tali relazioni man mano che si processono gli elementi del documento di istanza. Su questo assunto si basano le motivazioni del Prodotto Cartesiano (PC). Secondo questa tecnica la corretta restrizione è controllata in fase statica, enumerando tutte le possibili situazioni che si possono verificare a run-time attraverso un’analisi delle 1 Queste tecniche infatti dovrebbero preoccuparsi di fare inferenza sulle espressioni XPath 9 type table. I risultati ottenuti in fase statica sono utilizzati dal validatore per assegnare i tipi. Sostanzialmente il PC lavora in fase di compilazione per costruire nuove type table, in funzione di quelle scritte nello schema, che causano l’assegnamento del tipo built-in xs:error 2 in presenza di violazione dei vincoli di restrizione. Il PC ha un costo computazionale proporzionale al prodotto delle type table presenti in una catena di derivazione, e dunque si propone non come soluzione generale del problema, ma come tecnica prioritaria sul Run-time Check che permette, per catene di derivazione di tipi ragionevolmente contenute, l’anticipare e il raccogliere la verifica delle restrizioni per ogni elemento dei documenti d’istanza. Al fine di ridurre il costo computazionale dell’algoritmo è stata formulata una variante del PC che sfrutta alcune proprietà di equivalenza semantica delle type table. In particolare si basa su una tecnica, che chiameremo normalizzazione delle type table, e che consiste nel fondere alcune righe di una type table ottenendone una seconda, semanticamente equivalente, ma di dimensioni ridotte. A scopo dimostrativo è stato realizzato dunque un prototipo dell’algoritmo che realizza la tecnica del PC. Dal momento che questa tecnica ha senso soltanto nell’ambito della validazione, si è resa necessaria l’integrazione dell’algoritmo all’interno di un validatore esistente. La scelta a tale proposito è caduta su una versione di Xerces, parser open source, precendentemente modificata per ospitare un validatore SchemaPath. Per prima cosa è stato adattato il meccanismo di parsing dello schema alla sintassi di CTA. Per fare questo è stato esteso il modello per le XML Schema API di Xerces con le interfacce relative alle Schema Component di CTA. Parallelamente sono stati realizzati ed integrati meccanismi relativi al controllo di vincoli di correttezza dello schema rispetto alle componenti introdotte. In secondo luogo è stata progettata una API per utilizzare in modo flessibile il Prodotto Cartesiano all’interno di differenti validatori, prestando particolare attenzione alla modularità dei meccanismi e a preservare quanto più codice possibile dalla interfaccia nativa di Xerces, in modo da agevolare futuri aggiornamenti. I test del prototipo sono organizzati in una testsuite sul modello di quella usata per XML Schema. É stata creata dunque una serie di schemi e istanze, sia valide che non valide, in modo da testare il comportamento dei validatori su di esse. 2 xs:error è un tipo introdotto in XSDL 1.1 e non è soddisfatto da nessun valore 10 1. Introduzione Nel capitolo 2 sarà introdotto il contesto in cui si sviluppa il problema affrontato. In particolare in sez. 2.1 saranno introdotti i co-constraints, dandone una definizione, mostrando alcuni casi d’uso interessanti, e discutendo della loro importanza. Sarà dunque affrontato il loro supporto in XDSL 1.1 in sez. 2.2, dove saranno descritti in dettaglio sia Assertions che Conditional Type Assignment. Nel capitolo 3 invece affronteremo il problema della restrizione tra tipi in presenza di CTA. In sez. 3.1 illustreremo brevemente il problema, anche attraverso un esempio. In sez. 3.2 parleremo delle proposte di soluzione a questo problema, in particolare in 3.2.1 illustreremo Prefix, mostrandone le caratteristiche e un caso d’uso per cui non è possibile definire uno schema mediante il suo impiego. In sez. 3.2.3 sarà invece spiegata dettagliatamente la tecnica di Run-time Checking, fornendo un algoritmo in pseudo-codice e discutendo il suo costo computazionale. Nella sezione 3.3 invece introdurremo il Prodotto Cartesiano la soluzione proposta in questa tesi. Sarà dunque mostrato in dettaglio il procedimento di analisi delle type table e di costruzione delle nuove type table. Mostreremo quindi un esempio e forniremo un algoritmo in pseudo-codice per la realizzazione di questa tecnica. Effettueremo quindi un’analisi del suo costo computazionale in fase statica e del costo relativo alla fase dinamica di assegnamento dei tipi. In sez. 3.3.1 mostreremo alcune proprietà delle type table. In particolare daremo una definizione di type table deterministica e type table normalizzata. In sez. 3.3.2 mostreremo dunque una variante del PC che si basa su queste proprietà. Daremo quindi anche in questo caso un algoritmo e un analisi del suo costo, sia in fase statica che dinamica. In sezione 3.3.3 effettueremo una comparazione degli algoritmi, evidenziando, in base alla dimensione dell’input, quali sono i casi in cui una soluzione è più conveniente dell’altra. Nel capitolo 4 illustreremo dunque il prototipo. In sez. 4.1 saranno descritte molto brevemente le XML Schema API e la loro implementazione all’interno di Xerces. Inoltre in questa sezione mostreremo anche quali cambiamenti sulle API di Xerces si sono resi necessari e come sono stati effettuati per introdurre le componenti di CTA nel modello esistente. In sez. 4.2 sarà illustrata in dettaglio l’API proposta per il prototipo. Saranno quindi 11 descritte e motivate tutte le scelte progettuali, accompagnate da esempi d’uso. In sez. 4.4 sarà mostrata la testsuite realizzata, descrivendo brevemente i test effettuati. Nel capitolo 5 infine riassumeremo quanto mostrato ed effettueremo alcune considerazioni finali, inoltre in sez. 5.1 mostreremo in che direzione potrebbe essere sviluppato questo lavoro. Capitolo 2 XSDL 1.1 e Conditional Type Assignment In questo capitolo saranno affrontate le problematiche relative all’introduzione del supporto per co-constraint in XML Schema Definition Language (XSDL 1.1)[GSMT07], evidenziandone la necessità e discutendo le soluzioni adottate. Saranno introdotti quindi alcuni concetti generali riguardanti i Co-constraint. In seguito sarà discussa la loro importanza e il loro supporto nei linguaggi di schema ed infine sarà dedicata una sezione ai meccanismi che ne forniscono il supporto in XSDL 1.1. 2.1 Co-occurrence constraint Con il termine schema XML ci si riferisce in generale ad una descrizione formale della struttura logica di una classe di documenti XML. In particolare uno schema definisce un vocabolario di elementi ed attributi e stabilisce in che modo essi debbano apparire nei documenti d’istanza. Per fare ciò impone una serie di restrizioni sul contenuto dei documenti stessi. Un sottoinsieme importante di tali restrizioni è costituito dai Co-occurrence costraints (anche detti: “co-constraint”): regole che decidono quali tipi di markup (elementi o attributi) possono occorrere insieme all’interno di un documento XML [SM06b]. Dei co-constraint fanno parte dunque quelle regole che specificano le relazioni di occorrenza reciproca tra le parti di un documento. In generale questi vincoli sono dipendenti dal contesto. Ad esempio essi sono usati per stabilire che in un elemento non compaia un attributo x se è presente l’attributo y, oppure per imporre la struttura dei figli di un elemento in base al valore di un attributo. 13 14 2. XSDL 1.1 e Conditional Type Assignment 2.1.1 Classificazione dei co-constraint La classe dei co-constraint è molto ampia ed al suo interno è possibile individuare diversi sottoinsiemi di vincoli con caratteristiche comuni. Esistono catalogazioni più o meno specifiche per quanto riguarda le tipologie di co-constraint. Di seguito sarà illustrata una ristretta serie di casi a scopo esemplificativo. Per una classificazione più esaustiva si consiglia di consultare [SM06a] o [Zin07]. Vincoli incrociati sul contenuto Appartengono a questa categoria i vincoli che esprimono restrizioni sul contenuto di un elemento in base al valore o alla presenza di un attributo o di un altro elemento. Si consideri ad esempio il frammento XML in figura 2.1, supponendo che il relativo schema definisca una grammatica per descrivere una base di dati di fonti bibligrafiche. L’elemento <entry> corrisponde ad una fonte bibliografica. Nel caso in cui la fonte sia una pagina web è lecito aspettarsi che l’URL relativa venga sempre esplicitata. All’interno dello schema potremmo dunque esprimere questo requisito mediante un co-constraint del tipo: se l’attributo kind ha valore web-page l’elemento <entry> deve contenere l’elemento <url>. In questo caso quindi la presenza di un elemento (<url>) sarebbe vincolata al valore di un attributo (kind). In generale i co-constraint di questo genere sono necessari quando è richiesto che il contenuto di un elemento sia legato al contenuto di altri attributi o elementi, imponendo ad esso un content model differente a seconda del contesto. Vincoli di mutua dipendenza Si ha un vincolo di mutua dipendenza quando a seconda della presenza di un componente (attributo od elemento) vengono decise restrizioni su un altro componente, che può diventare richiesto, non ammesso od opzionale. Nell’esempio in figura 2.1 l’ultimo elemento <entry> rappresenta un articolo appartenente ad una rivista e sono indicate dall’elemento <pages> anche le pagine in cui esso compare. Nel caso in cui non venga citato il nome della rivista è insensato che compaia un range delle sue pagine. Questa restrizione può essere espressa attraverso un vincolo di mutua dipendenza: la presenza dell’elemento <pages> è opzionale se è presente l’elemento <journal> e proibita viceversa. 2.1 Co-occurrence constraint <bibliography> <entry kind="book" key="e1"> <author>Name</author> <title>My book</title> </entry> <entry kind="web-page" key="e2"> <author>Name</author> <title>my page</title> <url>www.example.org/webpage</url> </entry> <entry kind="article"> <author>Name</author> <title>My article</title> <journal>My journal</journal> <pages from="50" to="61"/> </entry> </bibliography> Figura 2.1: Un documento XML che descrive una piccola bibliografia. 15 16 2. XSDL 1.1 e Conditional Type Assignment Vincoli incrociati sul dominio di attributi ed elementi Sempre considerando l’esempio in figura 2.1, affinchè il range delle pagine sia significativo, è richiesto che il numero della pagina iniziale sia inferiore o uguale a quello della pagina finale. Anche questo vincolo sul dominio aritmetico delle pagine appartiene alla categoria dei co-costraints. Le restrizioni di dominio sono molto comuni e possono coinvolgere sia attributi che elementi. Inoltre il rapporto tra i valori può essere meno banale di quello portato ad esempio. In generale i vincoli di dominio consentono di governare il dominio dei valori di un attributo o elemento sul valore o sulla presenza di altri attributi o elementi; ad esempio si potrebbe voler controllare che il valore di un attributo sia uguale alla somma dei valori di alcuni elementi che compaiono nel documento. 2.1.2 Importanza dei co-constraint La semplicità degli esempi proposti in sez. 2.1.1 dà un’idea di come le situazioni che rientrano tra i casi d’uso dei co-costraint siano del tutto comuni. I co-constraint hanno un campo di applicabilità vastissimo. Per mostrare la loro rilevanza pratica faremo dunque riferimento ad alcune restrizioni relative a tre linguaggi molto noti basati su sintassi XML. Annidamento degli elementi <a> in XHTML In XHTML[ea02] l’elemento <a> definisce un collegamento ipertestuale (o hyperlink ) che lega una parte del documento ad una unità esterna o ad un’altra sezione del documento stesso. In particolare qualsiasi parte di un documento può essere legata mediante un hyperlink. Per questo il contenuto dell’elemento <a> è definito in maniera libera, ovvero esso può contenere un qualsiasi altro frammento XHTML. Tuttavia la stessa porzione di documento non può fare riferimento a più di un collegamento ipertestuale. Per evitare ciò le specifiche prevedono che un elemento <a> non possa essere annidato all’interno di un altro elemento <a>. Questa restrizione rappresenta effettivamente un co-constraint. Named templates in XSLT XSLT(Extensible Stylesheet Language Transformations)[Kay07] prevede due tipi di dichiarazione di template: per nome (named templates) e per predicato. Il corrispettivo elemento <template> denota l’una o l’altra tipologia a seconda che presenti l’attributo name o l’attributo match. In base alla specifica un elemento <template> deve 2.1 Co-occurrence constraint contenere almeno uno dei due attributi. Siamo dunque in presenza di un co-constraint di mutua dipendenza. Elementi in XML Schema I requisiti sintattici di XML Schema[TBMM04] presentano molti vincoli che appartengono alla categoria dei co-constraint. Un esempio particolarmente significativo è dato dalla seguente regola per le dichiarazioni di elemento: un <element> deve contenere un attributo name e può contenere o un elemento tra <complexType> e <simpleType> oppure un attributo type. Un <element> locale inoltre può anche avere un attributo ref, in qual caso non possono apparire né l’attributo name, né l’attributo type e non possono apparire nemmeno gli elementi <complexType> e <simpleType>. É evidente che tutte queste restrizioni rispondono alla definizione di vincoli di co-occorrenza. Il contenuto di <element> è di fatti vincolato sia alla presenza dell’attributo ref che a quella dell’attributo type. Inoltre anche la presenza di ref rende proibita la presenza di name che viceversa sarebbe obbligatoria. 2.1.3 Supporto ai co-constraint nei linguaggi di validazione Un linguaggio di validazione è un formalismo mediante il quale è possibile esprimere schemi XML. In particolare possiamo distinguere due tipologie di linguaggi di validazione: • linguaggi basati su grammatica, in cui lo schema descrive una grammatica libera dal contesto come regole di produzione espresse in uno specifico formalismo. Rientrano in questa tipologia XML Schema, Relax NG [CM01] e DTD [BPSM+ 06]. • linguaggi basati su regole, in cui lo schema racchiude una lista dei predicati che discrimineranno la correttezza del documento alla validazione. Rientrano in questa tipologia Schematron [Jel02] e xlinkit [NCEF02]. I linguaggi basati su grammatica sono adatti per natura a descrivere la struttura logica del documento, e molti di essi consentono di esprimere vincoli, anche sofisticati, sul valore dei nodi. Tuttavia il supporto ai co-constraint è quasi sempre limitato, qualora non assente: né XML Schema né DTD possiedono costrutti adatti ad esprimerli. Ciò è probabilmente dovuto alla natura dei formalismi stessi, basati solitamente sulla descrizione di produzioni grammaticali, che non si prestano facilmente a rappresentare i vincoli di co-occorrenza. Al contrario i linguaggi basati su regole sono ideali per esprimere co-constraint: di fatto sia Schematron che xlinkit li 17 18 2. XSDL 1.1 e Conditional Type Assignment supportano pienamente. Il mancato supporto ai co-constraint in XML Schema 1.0 è chiaramente avvertito nell’ambito delle tecnologie web. Per nessuna delle situazioni precedentemente esposte (vedi sez. 2.1.1 e 2.1.2) è possibile esprimere una soluzione in XML Schema 1.0, inoltre è interessante notare come non sia nemmeno possibile descrivere uno schema XML per XML Schema 1.0 nel linguaggio stesso. Tuttavia non è corretto affermare che in XML Schema non sia possibile definire alcun co-constraint. In particolare esitono i cosiddetti identity constraint definition, ossia costrutti che consentono la definizione di vincoli di identità referenziale sugli elementi di un documento. Gli identity constraint sono pensati per fornire vincoli sugli elementi similari a quelli definibili sulle tuple di una base di dati. Ad esempio essi consentono di imporre che non compaiano due elementi nel documento il cui attributo name abbia lo stesso valore. Sebbene tali vincoli rientrino in effetti nella categoria dei co-constraint, essi ne rappresentano solo una minima parte e sono insufficienti nella maggioranza delle situazioni. La mancanza di costrutti specifici per co-constraint all’interno di XML Schema è causa di diversi problemi tutt’altro che teorici. L’uso di XML Schema è diffuso in modo particolare per la strutturazione e la validazione di dati machine understandable. Spesso in questi casi la validità del documento è un parametro critico per assicurare il corretto funzionamento delle applicazioni. Non poter esprimere una vasta categoria di vincoli come i co-constraint significa escluderne il controllo dal processo di validazione, indebolendo l’interoperabilità nello scambio dei dati dal momento che non è possibile specificare per intero i requisiti del sistema. 2.2 Supporto ai co-constraint in XSDL 1.1 XML Schema Definition Language 1.1, attualmente Working Draft del W3C, è presentato come estensione conservativa di XML Schema 1.0. Tra le sue caratteristiche principali si colloca sicuramente l’introduzione del supporto ai co-constraint. Abbiamo già mostrato in sez. 2.1 quanto siano comuni i casi che richiedono l’impiego di co-constraint ed accennato alla mancanza di costrutti adeguati per esprimerli in XML Schema 1.0. Tale carenza è colmata in XSDL 1.1 attraverso l’introduzione di due meccanismi fondamentali: Assertions e Condidional Type Assignment(CTA). 2.2 Supporto ai co-constraint in XSDL 1.1 2.2.1 Assertions Il meccanismo delle assertions (asserzioni) in XSDL 1.1 ricalca per molti versi quello adottato in Schematron. In particolare una assertion ha il medesimo significato funzionale: esprimere un vincolo attraverso un predicato XPath[BBC+ 07] che deve risultare sempre vero affinchè il documento sia valido. La schema component cui è delegata la realizzazione del meccanismo per le asserzioni è molto semplice e dispone solo delle due seguenti proprietà: • annotations, una sequenza di annotazioni • test, una espressione XPath 2.0 Per rappresentare in sintassi XML questa componente è stato introdotto l’elemento ‘<assert>’, che ha un attributo test il cui valore deve essere un’espressione XPath. A differenza di quanto avviene in Schematron assert non permette di specificare alcun tipo di messaggistica di errore da parte dell’utente. Inoltre Schematron associa le asserzioni ad elementi, in XSDL 1.1 le asserzioni sono associate invece ai tipi. L’elemento <assert> infatti può comparire solo alla fine della dichiarazione di un Complex Type e pertanto sarà legato a run-time agli elementi d’istanza cui esso verrà assegnato. La regola di validazione della assertion schema component è dunque la seguente: un’elemento d’istanza è valido localmente rispetto ad una assertion se il predicato XPath relativo alla proprietà test è valutato a true. Consideriamo l’esempio in fig. 2.1, il vincolo sul dominio delle pagine può essere espresso in XSDL 1.1 specificando il content model di <pages> nel modo seguente: <xs:complexType> <xs:attribute name="from" type="xs:integer"/> <xs:attribute name="to" type="xs:integer"/> <xs:assert test="@from <= @to"/> </xs:complexType> Restrizioni sulle espressioni XPath Le espressioni XPath lecite per Assertions sono solo un sottinsieme di quelle consentite dal linguaggio. Per comprendere il motivo di questa scelta bisogna fare una considerazione circa le conseguenze dovute alla verifica di co-constraint su tutto il documento. Questa pratica difatti influisce sulla politica di validazione dei documenti d’istanza. XML Schema è progettato in modo da consentire la validazione in streaming di documenti, solitamente necessaria con documenti di grandi dimensioni e di cui non si 19 20 2. XSDL 1.1 e Conditional Type Assignment dispone per intero nell’arco della validazione. In particolare la validazione in streaming è effettuata sulla base degli eventi prodotti da un parser SAX. Per agevolare questo tipo di validazione in Schema si adotta una politica secondo cui allo start element si possa ricavare il tipo di ogni elemento d’istanza e all’end element se ne conosca la validità. Tale principio è esposto in [MVZ07] ed è identificato con l’acronimo STEVE (Start tag: Type; End tag: Validity Evaluation). In base a tale principio e al fatto che una assertion è vincolata ad un elemento e al contenuto specificato dal suo tipo, l’espressione XPath associata alle assertion ammette solo gli assi attribute, child e descendant. In questo modo la validità dell’elemento sarà subito disponibile all’end element. 2.2.2 Conditional Type Assignment Il meccanismo del Conditional Type Assignment (CTA) estende le dichiarazioni di elemento di XSDL 1.0, permettendo l’assegnamento di tipi in base alla valutazione di condizioni XPath. In particolare consente di vincolare il tipo di un elemento nel documento d’istanza in funzione del valore o della presenza di uno o più attributi ad esso locali. Il meccanismo di CTA si basa dichiaratamente sulla soluzione per l’assegnamento di tipi condizionali di SchemaPath[CMV04], ricalcandone con alcune differenze i costrutti e le regole di validazione. In XSDL 1.1 ad ogni dichiarazione di elemento è associata una type table, ossia una tabella le cui righe sono coppie di predicati XPath e dichiarazioni di tipo. Ogni entry nella type table corrisponde ad un possibile tipo che l’elemento d’istanza potrà assumere in base al contesto. A livello sintattico la specifica XSDL per quanto riguarda CTA introduce un nuovo elemento: alternative, corrispondente ad una precisa schema component riferita nella specifica con il nome di type alternative. Una type alternative lega appunto un’espressione XPath ad una dichiarazione di tipo. Ad essa dunque appartengono le seguenti proprietà: • annotations, una sequenza di annotazioni • test, una espressione XPath 2.0 • type, una definizione di tipo Il relativo elemento <alternative> può comparire solo all’interno di una dichiarazione di elemento e coinciderà con una entry nella sua type table. Così come per <assert> (cfr. 2.2.1), anche <alternative> contiene un attributo test con il medesimo ruolo. Inoltre ogni <alternative> deve esplicitare un tipo. Questo può essere fatto in due modi mutuamente esclusivi: come valore di un attributo type, quindi 2.2 Supporto ai co-constraint in XSDL 1.1 con un riferimento per nome qualificato ad un tipo globale; oppure definendo un nuovo tipo anonimo locale ad <alternative>. A grandi linee l’assegnamento di tipo agli elementi d’istanza che fanno riferimento a dichiarazioni che racchiudono type alternative avviene in questo modo: la prima type alternative, in ordine di dichiarazione, il cui test XPath sia valutato a true provoca l’assegnamento del relativo tipo. Se ciò non avviene per nessuna delle type alternative allora il tipo dell’elemento sarà quello specificato nell’attributo type di element oppure quello definito localmente ad esso. Di seguito indicheremo quest’ultimo come “declared type” dell’elemento, distinguendolo dai tipi delle type alternative che identificheremo come “tipi alternativi”. Esiste inoltre la possibilità di dichiarare una type alternative di default, ossia un elemento <alternative> che compare per ultimo nell’elemento in cui e racchiuso e che non dichiara nessuna condizione XPath. Attraverso quest’ultima è possibile esplicitare il tipo che deve essere assegnato qualora la valutazione delle altre alternative non provochi nessuna selezione di tipo. Un caso d’uso tipico della alternativa di default è l’assegnamento del tipo error. Il tipo error è introdotto in XSDL 1.1 proprio per CTA, ed è definito come simple type contro cui nessun contenuto risulta valido. Il suo assegnamento come tipo ad un elemento provocherà dunque la sua invalidità. Per comprendere l’utilità di error immaginiamo una dichiarazione di elemento in cui i predicati XPath racchiusi nelle type alternative siano test sul valore di uno stesso attributo. Assegnando come alternativa di default error, si otterrà una restrizione sul dominio dell’attributo in base alla quale i soli valori leciti saranno quelli a cui le type alternative fanno riferimento. Esprimere i co-constraint attraverso CTA Di seguito sarà mostrato un caso d’uso tipico di CTA riprendendo il documento portato ad esempio in fig. 2.1 e mostrando in che modo sia possibile definirne uno schema che sia in grado di esprimere i co-constraint menzionati usando CTA. Prima però è importante enunciare una restrizione sulla dichiarazione di elementi che racchiudono type alternative 1 : ogni tipo condizionale deve essere derivato in modo valido dal tipo dichiarato dell’elemento. Questa limitazione, che non compromette comunque l’espressività di CTA, è dovuta prevalentemente alla necessità di mantenere una relazione tra i tipi che un elemento può assumere. Uno schema per il documento in fig. 2.1 potrebbe essere il seguente: 1 cfr. ‘Schema Component Constraint: Element Declaration Properties Correct’, punto 7.1 della sezione 3.3.6 delle specifiche [GSMT07]. 21 22 2. XSDL 1.1 e Conditional Type Assignment <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="www.example.com/biblio" xmlns="www.example.com/biblio"> <xs:complexType name="entry_t"> <xs:sequence> <xs:element name="author" type="xs:string"/> <xs:element name="title" type="xs:string"/> </xs:sequence> <xs:attribute name="kind" type="xs:QName"/> <xs:attribute name="key" type="xs:QName"/> </xs:complexType> <xs:complexType name="web_entry_t"> <xs:complexContent> <xs:extension base="entry_t"> <xs:sequence> <xs:element name="url" type="xs:anyURI"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="bibliography_t"> <xs:sequence> <xs:element name="entry" minOccurs="0" maxOccurs="unbounded" type="entry_t"> <xs:alternative test="@kind=‘web-page’" type="web_entry_t"/> </xs:element> </xs:sequence> </xs:complexType> <xs:element name="bibliography" type="bilbliography_t"/> </xs:schema> Restrizioni sulle espressioni XPath Le espressioni XPath consentite all’interno delle type alternative sono ancora più ristrette rispetto a quelle lecite per assertions. Per mantenere la coerenza con la politica STEVE di fatto i vincoli espressi in CTA debbono poter essere verificati interamente allo start element, poichè il tipo dell’elemento d’istanza deve essere noto al verificarsi di questo evento. Dunque le espressioni di test non potranno contenere che attribute step. Per questo attraverso CTA è possibile legare il content model di 2.2 Supporto ai co-constraint in XSDL 1.1 un elemento solo al valore o alla presenza dei suoi attributi, e non ad esempio alla presenza di figli. Una considerazione differente riguarda il tipo dei nodi del documento su cui avviene la valutazione di XPath 2.0. XPath 2.0 introduce un sistema di tipi che comprende i tipi primitivi di XML Schema e consente una valutazione delle espressioni in base ad essi. Le informazioni sul tipo dei nodi tuttavia sono ricavate attraverso la validazione stessa del documento. Ovviamente in questo caso non è possibile effettuare una valutazione dell’espressione sui nodi tipati poichè al momento della valutazione il tipo del nodo non è ancora stato assegnato. Per questo motivo il predicato XPath di una type alternative sarà valutato su nodi untyped. Differenze con SchemaPath SchemaPath è un’estensione minimale di XML Schema 1.0 presentata proprio per aggiungere il supporto ai co-constraint in XML Schema. Abbiamo già accennato al fatto che CTA sia basato su SchemaPath, in questa sezione evidenzieremo alcune delle principali differenze tra le due soluzioni. Infatti anche se le differenze sintattiche tra i due costrutti sono minime, esistono una serie di importanti differenze concettuali. Così come per CTA anche per SchemaPath può essere associata ad ogni dichiarazione di elemento una sequenza di tipi alternativi selezionati in base alla valutazione di espressioni XPath. Esiste dunque un corrispettivo <alt> dell’elemento <alternative> anche in SchemaPath con la funzione di dichiarazione di type alternative, che lega quindi una dichiarazione di tipo ad una espressione XPath. La prima differenza con CTA riguarda le espressioni XPath. In SchemaPath non esistono limitazioni sugli assi selezionabili, dunque anche following e sibilling sono leciti. Ad esempio si può scrivere un’alternativa che assegni il tipo di un elemento <E> in base alla presenza di un figlio <F>. In questo caso il principio di STEVE non è più vero in generale. Infatti, secondo questo esempio, prima di assegnare il tipo ad <E> il validatore deve ricevere almeno lo start element per l’elemento <F>. In questi casi SchemaPath adotta una politica di validazione differente proposta in [MVZ07]. Inoltre un’altra differenza fondamentale è che in SchemaPath la presenza di alternative esclude la presenza del declared type. Se nessuna alternativa è soddisfatta in tal caso si assegna error. Infine l’ultima notevole differenza è dovuta all’ordine di valutazione e selezione delle alternative. Se in CTA esso segue l’ordine lessicografico, in SchemaPath le alternative possiedono tutte una priorità, che di default è stabilita in base alla specificità della condizione XPath, in maniera del tutto analoga a quanto avviene in XSLT, ma che può essere settata esplicitamente attraverso l’attributo priority di <alt>. 23 Capitolo 3 Verifica statica dei vincoli di restrizione con CTA In questo capitolo mostreremo una soluzione parzialmente statica al problema della verifica dei vincoli di restrizione in presenza di CTA. Per prima cosa sarà introdotto il problema legato alla restrizione di tipi complessi che racchiudono type alernative. Poi saranno presentate e discusse alcune proposte per la sua soluzione. Mostreremo quindi un algoritmo in due varianti che risolva il problema parzialmente a tempo di compilazione dello schema e uno che invece effettua ogni controllo in fase di validazione. Inoltre effettueremo una comparazione tra i due algoritmi. 3.1 CTA e restrizione di tipi complessi L’introduzione di CTA in XSDL 1.1 solleva un problema riguardante le regole per il controllo della restrizione di tipi complessi in presenza di type alternative. Consideriamo il seguente documento il cui schema sia definito come in figura 3.1: <root> <e a="5" b="2"/> </root> A tempo di validazione all’elemento <e> verrà assegnato il tipo t2 in quanto il tipo dell’elemento <root> è restricted e il predicato “@a > @b” è soddisfatto. Tuttavia se <root> fosse di tipo base considerando le type alternative per <e> in questo caso sarebbe assegnato il tipo t1. Dato che però t2 non è definito come restrizione per t1, possiamo dire che nel contesto del documento d’istanza il tipo dell’elemento <e> non restringe il corrispettivo tipo di <e> in base. Ma allora lo schema in fig. 3.1 è corretto? 25 26 3. Verifica statica dei vincoli di restrizione con CTA <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="base"> <xs:sequence> <xs:element name="e"> <xs:alternative test="@a > @b" type="t1"/> <xs:alternative test="@a <= @b" type="t2"/> </xs:element> </xs:sequence> <xs:attribute name="a"/> <xs:attribute name="b"/> </xs:complexType> <xs:complexType name="restricted"> <xs:complexContent> <xs:restriction base="base"> <xs:sequence> <xs:element name="e"> <xs:alternative test="@a > @b" type="t2"/> <xs:alternative test="@a <= @b" type="t1"/> </xs:element> </xs:sequence> <xs:attribute name="a"/> <xs:attribute name="b"/> </xs:restriction> </xs:complexContent> </xs:complexType> <xs:complexType name="t1"> <xs:sequence> <xs:element name="t1"/> </xs:sequence> </xs:complexType> <xs:complexType name="t2"> <xs:sequence> <xs:element name="t2"/> </xs:sequence> </xs:complexType> <xs:element name="root" type="restricted"/> </xs:schema> Figura 3.1: Un caso d’uso che presenta una restrizione non valida. 3.2 Tecniche per la verifica dei vincoli di restrizione Dobbiamo ovviamente chiederci quindi se il complex type restricted è una restrizione valida del complex type base anche in presenza di questa eventualità a run-time. A questo proposito nelle specifiche di XML Schema 1.1 vale il vincolo seguente1 : “Ogni sequenza di elementi che sia valida rispetto al complex type ristretto deve essere valida rispetto al complex type base”. In base a questa regola lo schema precedente non può essere considerato valido. Infatti in generale la sequenza di elementi riconosciuta per il contenuto di <root> dal tipo restricted non è riconosciuta anche dal tipo base. 3.2 Tecniche per la verifica dei vincoli di restrizione A questo punto sorge un nuovo problema: come rilevare allora la presenza di situazioni di restrizione non valida in presenza di alternative? Inoltre quando deve essere fatto? A tempo di compilazione dello schema o a tempo di validazione dell’istanza? In questa sezione esamineremo alcune possibili soluzioni avanzate durante la stesura del Draft di XSDL 1.1 nella mailing list dell’intrest group di XML Schema. Per completezza nella trattazione è giusto evidenziare che durante la discussione di questo problema sono state avanzate anche proposte molto radicali, quali impedire che possano comparire dichiarazioni di elemento condizionali, oppure evitare direttamente il controllo, nel qual caso lo schema in fig. 3.1 sarebbe considerato corretto. Tuttavia in questa dissertazione non le prenderemo in considerazione, concentrandoci nell’esame di tecniche più plausibili in modo da poter fare un raffronto con la soluzione proposta. Inoltre non prenderemo in considerazione nemmeno l’ipotesi di una verifica completamente statica della restrizione, per cui si dovrebbero dedurre i tipi assegnati in base alla semantica dei predicati XPath, cosa probabilmente infattibile. Tra le soluzioni proposte dunque ne illustreremo sia alcune che consentono una verifica statica della restrizione, limitando però l’esspressività di CTA, sia altre che demandano il controllo parzialmente o completamente alla fase di validazione. 3.2.1 Prefix Una prima soluzione è nota con il nome di prefix. Si tratta di fare in modo che la type table della dichiarazione di elemento condizionale2 nel tipo base sia ripetuta 1 vedi Schema Component Constraint: Content type restricts in sez. 3.4.6 delle specifiche [GSMT07] 2 Per brevità indicheremo con “dichiarazione di elemento condizionale” le dichiarazioni di elemento per cui è presente una type table 27 28 3. Verifica statica dei vincoli di restrizione con CTA all’inizio della type table della dichiarazione corrispondente nel tipo ristretto. Indicheremo quindi l’insieme delle type alternative ripetute nella dichiarazione locale al tipo ristretto con il termine di “prefisso”. Essa è stata formulata in due varianti, una influenza solo la semantica del costrutto di restrizione, l’altra impone anche un vincolo sintattico: 1. Tutte le type alternative presenti nella dichiarazione di elemento locale alla base vengono automaticamente ereditate da quella locale al tipo ristretto. 2. Chi scrive lo schema deve ripetere, in caso di restrizione, per ogni dichiarazione nel tipo ristretto tutte le <alternative> presenti nella relativa dichiarazione nella base, lasciandone inalterato il predicato ma con la possibilità di specificare un tipo ristretto. Nel seguito faremo riferimento sempre alla seconda opzione in quanto si presta meglio ad esemplificazioni. A questo scopo si noti che tra le due essa è la meno restrittiva. Consideriamo dunque ancora lo schema espresso in fig. 3.1, riscrivendo la dichiarazione per l’elemento <e> locale al tipo ristretto secondo prefix : <xs:element name="e"> <!-- prefix inherited from base --> <xs:alternative test="@a > @b" type="t1"/> <xs:alternative test="@a <= @b" type="t2"/> <!-- restricted --> <xs:alternative test="@a > @b" type="t2"/> <xs:alternative test="@a <= @b" type="t1"/> </xs:element> É facile mostrare che possono verificarsi due sole situazioni a run-time nell’assegnamento di tipo per <e>: • Uno dei predicati nel prefisso è soddisfatto. In questo caso il tipo assegnato all’elemento è per costruzione della type table un tipo ristretto rispetto a quello che sarebbe assegnato dalla dichiarazione di base. • Nessuno dei predicati nel prefisso è soddisfatto. Questo implica che la dichiarazione di <e> in base assegnerebbe il declared type, mentre il content model ristretto assegnerebbe ad <e> uno tra gli altri tipi condizionali oppure il declared type della dichiarazione in restricted. A questo punto possono accadere ancora due cose: 3.2 Tecniche per la verifica dei vincoli di restrizione – Il tipo assegnato secondo la dichiarazione ristretta è derivato per restrizione dal declared type, nel qual caso è sicuramente anche restrizione del declared type per la dichiarazione nella base. – Il tipo assegnato secondo la dichiarazione ristretta non è derivato per restrizione dal declared type, nel qual caso non è sicuramente una restrizione del declared type per la dichiarazione nella base. Secondo questo stratagemma è possibile verificare la corretta restrizione dei tipi condizionali semplicemente verificando che il prefisso sia dichiarato correttamente in fase di compilazione, e modificando in xs:error i tipi condizionali nel content model ristretto che derivano per estensione il declared type. In pratica prefix è una soluzione quasi totalmente statica al problema. Tuttavia essa limita troppo l’espressività di CTA escludendo di fatto numerosi casi d’uso. Consideriamo l’esempio in figura 3.2. L’intento del tipo InvoiceItem è quello di descrivere il contenuto di una voce in una fattura fiscale. In particolare l’elemento <amount> dovrà contenere l’importo della voce, inoltre l’attributo payment consentirà di specificare la modalità di pagamento, mentre l’attributo type identificherà la categoria della merce a cui si riferisce la vendita. Nel caso d’uso proposto si è ipotizzato di non voler accettare un pagamento in contanti (Cash) inferiore a 20 dollari, inolre nel caso in cui il pagamento avvenga tramite transazione bancaria ipotizziamo di non accettare un pagamento inferiore ai 500 dollari. Consideriamo ora il complex type ristretto OverseaInlineItem. Esso denota una voce di fatturazione per una vendita oltre oceano. Immaginiamo come nell’esempio di non disporre della merce di tipo “Collection1” e quindi di voler impedire sintatticamente la sua presenza nella fattura, inoltre immaginiamo che per ammortizzare il costo di spedizione della merce di tipo “Collection2” si richiede un ammontare di almeno 1000 dollari. Sebbene l’esempio non sia completamente realistico, un caso d’uso di questo tipo è assolutamente ragionevole. Tuttavia i vincoli enunciati non possono essere espressi con prefix. Vediamo infatti cosa accadrebbe ripetendo il prefisso in questo caso: <xs:complexType name="OverseaInvoiceItem"> <xs:sequence> <xs:element name="amount" type="amount_t"> <xs:alternative test="@payment=‘Cash’" type="greaterThan20_t"/> <xs:alternative test="@payment=‘Credit Card’" type="greaterThan0_t"/> <xs:alternative test="@payment=‘Bank Transfer’" 29 30 3. Verifica statica dei vincoli di restrizione con CTA <xs:complexType name="InvoiceItem"> <xs:sequence> <xs:element name="amount" type="amount_t"> <xs:alternative test="@payment=‘Cash’" type="greaterThan20_t"/> <xs:alternative test="@payment=‘Credit Card’" type="greaterThan0_t"/> <xs:alternative test="@payment=‘Bank Transfer’" type="greaterThan500_t"/> </xs:element> </xs:sequence></xs:complexType> <xs:complexType name="OverseaInvoiceItem"> <xs:complexContent> <xs:restriction base="InvoiceItem"> <xs:sequence> <xs:element name="amount" type="amount_t"> <xs:alternative test="@type=‘Collection1’" type="xs:error"/> <xs:alternative test="@type=‘Collection2’" type="greaterThan1000_t"/> </xs:element> </xs:sequence> </xs:restriction> </xs:complexContent></xs:complexType> <xs:complexType name="amount_t"> <xs:simpleContent> <xs:extension base="xs:decimal"> <xs:attribute name="type" type="type_t"/> <xs:attribute name="payment" type="payment_t"/> </xs:extension> </xs:simpleContent></xs:complexType> <xs:simpleType name="payment_t"> <xs:restriction base="xs:string"> <xs:enumeration value="Cash"/> <xs:enumeration value="Credit Card"/> <xs:enumeration value="Bank Transfer"/> </xs:restriction></xs:simpleType> <xs:simpleType name="type_t"> <xs:restriction base="xs:string"> <xs:enumeration value="Collection1"/> <xs:enumeration value="Collection2"/> </xs:restriction></xs:simpleType> Figura 3.2: Uno schema per voci di fatturazione. 3.2 Tecniche per la verifica dei vincoli di restrizione type="greaterThan500_t"/> <xs:alternative test="@type=‘Collection1’" type="xs:error"/> <xs:alternative test="@type=‘Collection2’" type="greaterThan1000_t"/> </xs:element> </xs:sequence> </xs:complexType> É facile notare che i tipi condizionali dichiarati nel content model ristretto non verranno mai selezionati. Quindi il beneficio della restrizione è praticamente annullato. 3.2.2 Vincolare il numero delle alternative Una seconda possibilità è la seguente: una definizione di tipo che coinvolge CTA può essere ristretta solo da tipi in cui le dichiarazioni di elemento condizionale espongono lo stesso numero di alternative e con gli stessi predicati XPath ma con tipi ristretti. Viceversa se si vuole restringere un tipo che non coinvolge CTA basta verificare staticamente che le type alternative assegnino un tipo ristretto rispetto ai declared type. É facile vedere che in questo caso la verifica della restrizione è banalmente verificabile in fase di compilazione. Tuttavia l’espressività del costrutto per CTA risulterebbe fortemente mutilata. A tale scopo si fa notare che tale regola è ben più restrittiva di prefix e non comporta maggiori vantaggi. 3.2.3 Run-time Checking Una possibilità completamente dinamica relega il controllo della restrizione completamente alla fase di validazione. Questa è la soluzione implicitamente adottata nel Draft di XML Schema 1.1 ed è riferita come Run-time Checking(RTC). In particolare essa è descritta dal vincolo Conditional Type Substitutable in Restriction riportato di seguito. Dato un elemento del documento d’istanza <E> e un complex type T, siano: • B la definizione del tipo base di T • TT la type table di <E> in T, se esiste • TB la type table di E in B, se esiste • ST la definizione di tipo assegnata per <E> da TT , se TT esiste • SB la definizione di tipo assegnata per <E> da TB , se TB esiste 31 32 3. Verifica statica dei vincoli di restrizione con CTA <E> e T soddisfano questo vincolo se e solo se una delle seguenti condizioni è verificata: 1. TB non esiste 2. TT e TB esistono entrambe ed almeno una delle seguenti condizioni è verificata: (a) B è xs:anyType e ST è una valida restrizione per SB (b) T è derivato per restrizione ed ST è valida restrizione per SB , e sia <E> che B soddisfano questo vincolo. (c) T è derivato per estensione, e ST è identico ad SB , e sia <E> che B soddisfano questo vincolo. A tempo di validazione dunque, una volta ottenuta la type table in base alla dichiarazione di elemento, si ricava da essa il tipo da assegnare (selected type) valutando i predicati XPath delle relative type alternative. A questo punto è necessario anche valutare la type table nella dichiarazione di base così da verificare la restrizione tra il tipo selezionato dalla type table ristretta e quello selezionato da quest’ultima. Si procede ricorsivamente verificando la relazione di restrizione lungo tutta la catena di derivazione. Di seguito è riportata una descrizione in pseudo-codice dell’algoritmo3 che scaturisce naturalmente dalla definizione di Conditional Type Substitutable in Restriction: void start-element(QName name, Attribtes attr) { current-type <- get-current-type() Type Definition T <- current-type Type Definition B <- current-type.base while (B is not xs:anyType) do { Type Table type-table <- get-corresponding-type-table(name, T) // selected type Type Definition ST <- type-table.execute(name, attr) Type Table base-type-table <- get-corresponding-type-table(name, B) Type Definition SB <- base-type-table.execute(name, attr) if (validly-substitutable-restriction(ST, SB)) { T <- B 3 Il codice proposto è quello di un validatore che riceve l’evento “start element” di un parser SAX 3.2 Tecniche per la verifica dei vincoli di restrizione 33 B <- B.base } else report-schema-error( "Validation Rule: Conditional Type Substitutable in Restriction" ) } Type Table type-table <- get-corresponding-type-table(name, current-type) Type definition selected-type <- type-table.execute(name, attr) set-current-type(selected-type) } É facile vedere che questa soluzione verifica effettivamente la subsumption dei content model 4 poichè eventuali violazioni dipendenti dal contesto saranno evidenziate a run-time. Inoltre RTC non impone nessuna restrizione sintattica o semantica per CTA. Va evidenziato anche che una sua eventuale implementazione sarebbe tanto semplice quanto la sua definizione. É importante però mostrare un limite di questa tecnica, intrinseco al controllo dinamico: RTC deve controllare, potenzialmente per ogni elemento d’istanza, la restrizione tra tipi. Se infatti la valutazione dei predicati deve essere per forza effettuata dinamicamente, dal momento che dipende dal contesto, il controllo delle relazioni tra i tipi potrebbe essere risolto a tempo di compilazione dato che la catena di derivazione è una proprietà intrinseca del solo schema. Analisi del costo computazionale Ponendoci nel caso pessimo, il controllo della restrizione sarà lineare sia nella profondità della gerarchia di restrizione, sia nel numero di predicati da valutare. Consideriamo infatti che RTC dovrà al più percorrere l’intera gerarchia dalla foglia di livello più basso alla radice (anyType). Inoltre ad ogni passo dovrà valutare, sempre nel caso pessimo, tutti predicati racchiusi in tutte le type table. Supponiamo che la valutazione di qualsiasi predicato abbia costo costante unitario. Siano quindi: • c, il numero medio di predicati, coincidenti in questo caso con il numero medio di alternative, ottenuto come media aritmetica delle dimensioni delle type table. Secondo quanto detto c rappresenta anche il costo medio della valutazione dei predicati per una type table. 4 Ovvero controllare che il content model ristretto riconosca un sotto-linguaggio di quello riconosciuto dal content model di base 34 3. Verifica statica dei vincoli di restrizione con CTA • n la profondità massima della gerarchia dei tipi. • a il costo delle operazioni che RTC compie ad ogni ricorsione sulla catena eccetto la selezione del tipo sulla type table. Allora il costo computazionale complessivo introddotto da RTC nella validazione di un elemento è espresso dalla relazione: TRT C = n(a + c). Dove a è una costante che comprende i costi delle seguenti operazioni, tutte realizzabili in tempo costante: • trovare la base del tipo corrente • trovare la type table dell’elemento corrente nel tipo appena ricavato • controllo della restrizione • controllo di flusso per il caso base • chiamata ricorsiva Supponendo inoltre c costante possiamo evidenziare che TRT C è θ(n). 3.3 Prodotto Cartesiano La tecnica riferita come Prodotto Cartesiano si basa sull’idea di verificare la restrizione enumerando tutte le possibili situazioni che possono accadere a run-time. In particolare date T Tr , la type table per la dichiarazione di elemento e nel tipo ristretto, e T Tb , la rispettiva type table nel tipo base, viene costruita una nuova type table T Tp che consente di assegnare i tipi in maniera equivalente a T Tr eccetto per le situazioni in cui verrebbe assegnato un tipo scorretto secondo la restrizione, nel qual caso T Tp assegnerà xs:error. In questo modo la restrizione è completamente verificata in fase di analisi statica dello schema. A run-time il validatore effettuerà l’assegnamento di tipo semplicemente valutando le aternative racchiuse in T Tp . Per mostrare in che modo venga costruita T Tp supponiamo innanzitutto che T Tr contenga le seguenti alternative nell’ordine specificato: < cr1 , Tr1 >, < cr2 , Tr2 >, ..., < crm−1 , Trm−1 >, < crm = true, Tr > e che T Tb contenga le seguenti alternative nell’ordine specificato: < c1 , T1 >, < c2 , T2 >, ..., < ck−1 , Tk−1 >, < ck = true, T > 3.3 Prodotto Cartesiano 35 Dove T e Tr sono i declared type relativi alle due dichiarazioni di elemento. A questo punto T Tp è costruita come in figura 3.3. Consideriamo ancora l’esempio di fig. 3.2. Secondo il PC la type table che assegna il tipo ad <e> nel tipo ristretto è la seguente: < @payment=‘Cash’ ∧ @type=‘Collection1’ → xs:error > < @payment=‘Credit Card’ ∧ @type=‘Collection1’ → xs:error > < @payment=‘Bank Transfer’ ∧ @type=‘Collection1’ → xs:error > < @type=‘Collection1’ → xs:error > < @payment=‘Cash’ ∧ @type=‘Collection2’ → greaterThan1000_t > < @payment=‘Credit Card’ ∧ @type=‘Collection2’ → greaterThan1000_t > < @payment=‘Bank Transfer’ ∧ @type=‘Collection2’ → greaterThan1000_t > < @type=‘Collection2’ → greaterThan1000_t > < @payment=‘Cash’ → xs:error > < @payment=‘Credit Card’ → xs:error > < @payment=‘Bank Transfer’ → xs:error > < true() → amount_t > Possiamo dividere la tabella appena riportata in tre tronconi, uno per ogni alternativa della tabella originale di OverseaInvoiceItem. Possiamo vedere inoltre come ognuna di esse sia ripetuta per ogni possibile alternativa della type table nella base e che le relative condizioni siano messe in congiunzione. É facile notare come in questo modo ogni possibile situazione a run-time sia presa in considerazione. Per vedere in che modo siano rilevate le situazioni di errore, consideriamo il caso che per la type table ristretta venga assegnato il declared type amount_t: poichè amount_t non è restrizione di nessuno tipo tra greaterThan0_t, greaterThan20_t, greaterThan500_t, in quel caso la type table costruita assegnerà xs:error. Viceversa l’assegnamento di amount_t è consentito quando anche nel tipo base verrebbe assegnato il declared type, ovvero nessuno dei predicati @payment=‘Cash’, @payment=‘Credit Card’, 36 3. Verifica statica dei vincoli di restrizione con CTA < cr1 ∧ c1 → (Tr1 | error) > < cr1 ∧ c2 → (Tr1 | error) > ... < cr1 ∧ ck−1 → (Tr1 | error) > < cr1 → (Tr1 | xsd : error) > < cr2 ∧ c1 → (Tr2 | error) > < cr2 ∧ c2 → (Tr2 | error) > ... < cr2 ∧ ck−1 → (Tr2 | error) > < cr2 → (Tr2 | error) > .. . < crm−1 ∧ c1 → (Trm−1 | error) > < crm−1 ∧ c2 → (Trm−1 | error) > ... < crm−1 ∧ ck−1 → (Trm−1 | error) > < crm−1 → (Trm−1 | error) > < c1 → (Tr | error) > < c2 → (Tr | error) > ... < ck−1 → (Tr | error) > < true → Tr > Figura 3.3: type table ottenuta con il Prodotto Cartesiano. 3.3 Prodotto Cartesiano @payment=‘Bank Transfer’ è soddisfatto. L’algoritmo che date T Tr e T Tb costruisce T Tp può essere formalizzato nel modo seguente: perform-single-cartesian-product(type-tabe: Type Table, base-type-table: Type Table) : Type Table beginprocedure Type Table new-type-table <- empty-type-table() for each type-alternative in type-table.type-alternatives do T <- type-alternative.type test < type-alternative.test for each base-type-alternative in base-type-table.type-alternatives do B <- base-type-alternative.type base-test <- base-type-alternative.test if (T.validly-substitutable-restriction(B)) new-type-table.add(<test & base-test, T>) else new-type-table.add(<test & base-test, ERROR>) endprocedure Finora abbiamo descritto il Prodotto Cartesiano come una procedura che date due type table ne costruisce una terza in modo da poter assegnare solo in base ad essa i tipi. Tuttavia questo procedimento va ripetuto per ogni dichiarazione di elemento che fa riferimento ad alternative. Inoltre per ognuna di esse l’assegnamento deve essere sicuro non solo rispetto alla base, ma anche rispetto agli antenati nella gerarchia dei tipi. Per questo l’intero schema è processato secondo il metodo appena esposto, procedendo in una visita top-down della gerarchia dei tipi e applicando perform-single-cartesian-product per ogni coppia di type table baseristretta di ogni dichiarazione di elemento. Il processo completo può essere descritto in pseudo-codice come segue: perform-all-cartesian-products(S: schema) beginprocedure for each T in S.types do T.mapping <- empty set for each term in T.content_model do 37 38 3. Verifica statica dei vincoli di restrizione con CTA term-type-table <- get-type-table(term) T.mapping.add(<term, term-type-table>) for each T in S.types in a top-down traversal of the hierarchy do if (T is not xs:anyType) then B <- T.base for each <term, type-table> in T.mapping do base-type-table <- get-corresponding-type-table(term, B); // overwrite the term type table type-table <- perform-single-cartesian-product(type-table, base-type-table); endprocedure Dove si è usata la funzione get-corresponding-type-table che dato un termine all’interno di un tipo complesso ritorna la corrispettiva type table dell’elemento corrispondente nel tipo base. É importante notare che questo è sempre possibile in quanto deve valere il vincolo Element Declarations Consistent 5 . Al momento della validazione, in particolare alla ricezione di eventi “start element” da un parser SAX, è possibile assegnare il tipo valutando solo la tabella dei tipi costruita, come espresso di seguito: start-element(name: QName) beginprocedure current-type <- get-current-type() ElementDelcaration D <- current-type.transition(name) Type Table type-table <- crrent-type.mapping.get(D) T selected-type <- type-table.execute() if (selected-type == ERROR) then report-schema-error( "Conditional Type Substitutable in Restriction" ) else current-type <- selected-type endprocedure 5 Confrontare Element Declarations Consistent in sez. 3.8.6 delle specifiche [GSMT07] 3.3 Prodotto Cartesiano Analisi del costo computazionale In questa sezione effettueremo un’analisi del costo nello spazio e nel tempo dell’algoritmo esposto. Inoltre, poichè il Prodotto Cartesiano influisce sul costo della validazione, incrementando il numero di alternative da valutare, effettueremo anche una stima del costo a run-time. Chiamiamo db la dimensione di T Tb e dr la dimensione di T Tr . Allora la procedura perform-single-cartesian-product deve cosrtuire db · dr alternative. Sia poi n la profondità massima della gerarchia di restrizione. La procedura viene richiamata n volte lungo il cammino dalla base alla foglia, usando come T Tb la T Tp ricavata al passo precedente. Il costo del passo generico per costruire T Tpi date T Tpi−1 e Q T Tri è pari alla dimensione di T Tpi−1 , ovvero ij=1 dj , per la dimensione di T Tri . Dunque il costo computazionale e lo spazio necessario per l’algoritmo sono asintoticamente equivalenti a g n , dove g è la media geometrica delle dimensioni d1 , ..., dn : √ n d1 · ... · dn . Per quanto riguarda il carico di lavoro introdotto nella fase di validazione consideriamo che esso sia pari al numero di predicati da valutare nel caso pessimo per assegnare il tipo ad un elemento. Siano dunque T T1 , ..., T Tn type table di dimensione rispettivamente d1 , ..., dn , dove con dimensione sono intese il numero di alternative in esse racchiuse. La dimensione di una generica type table T T sarà indicata con dim(T T ). Prima di operare il Prodotto Cartesiano tra le varie type table, la dimensione di una type table coincide con il numero di condizioni atomiche presenti in essa, dove con condizioni atomiche sono intese quelle specificate dall’autore dello schema. Per applicare il Prodotto Cartesiano si deve iterare lungo la catena di type table T T1 , ..., T Tn eseguendo le seguenti operazioni: • Passo 1: T T2 × T T1 • Passo 2: T T3 × (T T2 × T T1 ) • ... • Passo n − 1: T Tn × (...(T T3 × (T T2 × T T1 ))...) L’operatore “×” è definito tra due type table e dà come risultato una type table. In particolare, siano T Ta e T Tb due generiche type table, allora T Ta × T Tb corrisponde 39 40 3. Verifica statica dei vincoli di restrizione con CTA alla type table costruita come mostrato in 3.3, considerando T Ta come type table ristretta e T Tb come type table di base. Consideriamo quindi la funzione così definita: T T se k = 1, 1 P C(k) = T T × P C(k − 1) se k > 1. k Dove per ogni k in 1, .., n, P C(k) denota una type table. Siamo quindi interessati a calcolare il numero di condizioni atomiche presenti nella table P C(n). Siano quindi T T e T T 0 due type table e siano r ed s le loro dimensioni, ovvero: • r = dim(T T ) • s = dim(T T 0 ) Indichiamo quindi le r condizioni di T T con c1 , ..., cr e le s condizioni di T T 0 con c01 , ..., c0s . La type table risultante sarà quindi composta dalle seguenti alternative: < c1 ∧ c01 → (T1 |error) > .. . < c1 ∧ c0s → (T1 |error) > .. . < cr ∧ c01 → (Tr |error) > < cr ∧ c0s → (Tr |error) > Come detto dim(T T × T T 0 ) = r · s. Notiamo inoltre che ogni alternativa ha due condizioni messe in and. Vediamo quindi come varia il numero di condizioni atomiche nella tabella risultante da P C(n). Al primo passo otteniamo una type table T T2 × T T1 di dimensione d2 · d1 , in cui ogni alternativa specifica una condizione composta da due condizioni atomiche messe in and. Quindi abbiamo che il numero totale di condizioni atomiche presenti nella type table T T2 × T T1 (indicata anche con P C(2)) è dato da d2 · d1 · 2. Al secondo passo abbiamo la type table P C(3), ovvero T T3 × (T T2 × T T1 ). Tale type 3.3 Prodotto Cartesiano table ha dimensione: dim(P C(3)) = d3 · dim(P C(2)) = d3 · d2 · d1 Ogni alternativa di P C(3) ha una condizione composta due condizioni messe in and : • una condizione atomica di T T3 • una condizione composta a sua volta di due condizioni atomiche messe in and (una di T T2 e l’altra di T T1 ). A questo punto ha senso considerare la funzione N C così definita: 1 se k = 1, N C(k) = 1 + N C(k − 1) se k > 1. Abusando leggermente della sitnassi, definiamo anche la seguente funzione: d se k = 1, 1 dim(k) = d · dim(k − 1) se k > 1. k che calcola la dimensione della type table P C(k). É immediato ora definire una funzione che calcola il numero totale di condizioni atomiche nella type table P C(k) come segue: T N C(k) = dim(k) · N C(k) Abbiamo quindi che T N C(n) = dim(n) · N C(n) = g n · n dove g è la media geometrica dei d1 , ..., dn . Infatti, notiamo immediatamente che: dim(n) = dn · dn−1 · ... · d1 = g n N C(n) = 1 + N C(n − 1) = 1 + 1 + N C(n − 1) == 1 + ... + 1 = n La stessa cosa è possibile dire dunque del costo computazionale riguardante la fase dinamica. L’assegnamento di tipo che fa riferimento a T Tpn nel caso pessimo richiede la valutazione di tutti i predicati, che sono appunto n · g n . 41 42 3. Verifica statica dei vincoli di restrizione con CTA 3.3.1 Normalizzazione delle type table Consideriamo la seguente type table T T : < c1 → T1 > < c2 → T2 > .. . < cn−1 → Tn−1 > < true → Tn > Quando si incontra un elemento <e> che deve essere validato contro questa type table, le condizioni devono essere valutate nell’ordine specificato. Ovvero sono eseguite le seguenti operazioni: if c1 then T1 else if c2 then T2 else ... else if cn-1 then Tn-1 else Tn Consideriamo allora la funzione T (<e>, tt) che assegna il tipo al generico elemento <e> in base alla type table tt. La seguente affermazione è vera: T (<e>, T T ) = Ti se e solo se: Ci = ¬c1 ∧ ¬c2 ∧ . . . ∧ ¬ci−1 ∧ ci , con i = 1...k è vera. In altre parole affinchè venga assegnato un tipo in base ad una alternativa in T T devono essere valutati a false tutti i predicati delle alternative che la precedono e deve essere soddisfatto il predicato dell’alternativa stessa. É facile quindi notare che la T T è equivaltente alla seguente type table T Tn : < c1 → T1 > < ¬c1 ∧ c2 → T2 > 3.3 Prodotto Cartesiano 43 .. . < ¬c1 ∧ ¬c2 ∧ ... ∧ ¬cn−2 ∧ cn−1 → Tn−1 > < ¬c1 ∧ ¬c2 ∧ ... ∧ ¬cn−1 , → Tn > In questo modo si ottiene una type table semanticamente equivalente a quella originale, ma tale per cui dato un qualunque elemento <e> del documento di istanza, <e> soddisfa una ed una sola delle condizioni (eventualmente l’ultima che è la negazione di tutte le condizioni precedenti). Diamo quindi la seguente definizione di type table deterministica: Una type table è deterministica se per ogni elemento <e> del documento di istanza, <e> soddisfa una ed una sola condizione. Secondo quanto mostrato, data una qualunque type table è sempre possibile costruire una type table deterministica equivalente. Si noti inoltre che il costo di questo processo è chiaramente lineare nella dimensione della type table originale. Diciamo che una type table è normalizzata se tutte le alternative assegnano un tipo diverso. Indicheremo che la type table T T è normalizzata con la notazione |T T |. Mostriamo anche che una qualsiasi type table in forma deterministica può essere normalizzata applicando la seguente regola: date due alternative qualsiasi di una type table deterministica < a → Ta > e < b → Tb >, se Ta e Tb sono lo stesso tipo T allora è possibile sostituire le due alternative con una terza della forma: < a ∨ b → T >. Si noti che anche questo procedimento può essere realizzato in tempo lineare nella dimensione della type table originale. Inoltre è possibile normalizzare una type table qualunque6 applicando la seguente regola per tutte le sue alternative: date due alternative qualsiasi di una type table qualsiasi nell’ordine: < ci → Ta > e < cj → Tb >, se Ta e Tb sono lo stesso tipo T allora è possibile eliminare la seconda alternativa e trasformare la prima come segue: < ci ∨ (¬ci+1 ∧ .. ∧ ¬cj−1 ∧ cj ) → T >. Sia dtt il numero di predicati per la tabella T T , allora il numero d|tt| di predicati 6 Non necessariamente deterministica. 44 3. Verifica statica dei vincoli di restrizione con CTA racchiusi nella rispettiva |T T | ottenuta come descritto risulta essere7 : d|tt| ≤ d2tt − 2dtt +1 4 Enunciamo infine la seguente proprietà: data una qualsiasi type table T T e la relativa type table normalizzata |T T | si ha che |T T | espone un numero di alternative pari al numero di tipi selezionabili da T T . 3.3.2 Prodotto Cartesiano con type table normalizzate Una variante del Prodotto Cartesiano, basata sulle proprietà esposte in sez. 3.3.1, prevede la normalizzazione di tutte le type table costruite durante il processo. Adottando la stessa notazione introdotta in 3.3 dunque, ogni volta che si costruisce T Tp essa viene normalizzata. L’algoritmo relativo è mostrato di seguito: perform-single-cartesian-product(type-tabe: Type Table, base-type-table: Type Table) : Type Table beginprocedure Type Table new-type-table <- empty-type-table() for each type-alternative in type-table.type-alternatives do T <- type-alternative.type test <- type-alternative.test for each base-type-alternative in base-type-table.type-alternatives do B <- base-type-alternative.type base-test <- base-type-alternative.test if (validly-substitutable-restriction(T, B)) new-type-table.add(<test & base-test, T>) else new-type-table.add(<test & base-test, ERROR>) normalize(new-type-table) return new-type-table endprocedure 7 Ci si riferisce al caso pessimo per cui la T T è della forma: < c1 → T1 >, < c2 → T2 >, ..., < cn−1 → T2 >, < cn → T1 > 3.3 Prodotto Cartesiano 45 Costo computazionale Secondo quanto detto in sez. 3.3.1, poichè T Tp espone sicuramente gli stessi tipi di T Tr , |T Tp | ha dimensione uguale o minore della relativa T Tr . Attraverso questo stratagemma è possibile ridurre il costo computazionale del Prodotto Cartesiano nella fase statica da O(cn ) ad O(c2 n), dove si è supposto che tutte le type table abbiano stessa dimensione c. Infatti durante il processo si lavora su type table la cui dimensione non aumenta più in maniera quadratica rispetto alle originali. Tuttavia il numero di condizioni XPath da valutare continua a crescere esponenzialmente. Va detto che però il costo della normalizzazione visto in 3.3.1 considera un caso pessimo che non si potrà verificare nel PCN8 , infatti non ci interessa ottenere una type table completamente normalizzata, ci basta ottenerne una lineare nelle dimensioni di quella originale. Applicazione a due generiche type table Siano T T e T T 0 due generiche type table. Siano inoltre r ed s le loro dimensioni. Indichiamo quindi le condizioni di T T con c1 , ..., cr Indichiamo invece le condizioni di T T 0 con c01 , ..., c0s La tecnica di normalizzazione mira a costruire una type table T Tpn semanticamente equivalente alla type table T T × T T 0 , ma la cui dimensione non dipenda da T T 0 e che sia lineare nella dimensione di T T . L’idea è quindi quella di costruire una type table in due fasi: 1. nella prima fase si genera una type table T T × T T 0 come descrito in sez. 3.3. 2. nella seconda fase si esegue una procedura di raggruppamento delle condizioni che assegnano lo stesso tipo, mettendole in disgiunzione. Consideriamo quindi la fase di raggruppamento delle condizioni. Ci troviamo nel caso pessimo quando la prima fase genera una type table tale per cui, per ogni i = 1, ..., r, si ha: < ci ∧ c01 → Ti > < ci ∧ c02 → error > 8 Con PCN intendiamo la variante del Prodotto Cartesiano appena descritta. 46 3. Verifica statica dei vincoli di restrizione con CTA .. . < ci ∧ c0m−1 → Ti > < ci ∧ c0m → error > Dove si sta assumendo per semplicità m pari. In questo caso abbiamo m/2 tipi T i ed m/2 tipi error. Per ogni i = 1, ..., r, la fase di raggruppamento genera quindi due alternative: (ci ∧ c01 ) ∨ (¬(ci ∧ c02 ) ∧ (ci ∧ c03 )) ∨ ... ∨ (¬(ci ∧ c0m−2 ) ∧ (ci ∧ c0m−1 )) → T i (ci ∧ c02 ) ∨ (¬(ci ∧ c03 ) ∧ (ci ∧ c04 )) ∨ ... ∨ (¬(ci ∧ c0m−1 ) ∧ (ci ∧ c0m )) → error Possiamo osservare che per la prima alternativa, la condizione ci è ripetuta m − 1 volte. Inoltre, ci sono m − 1 condizioni di T T 0 . Le m − 1 condizioni di T T 0 sono c01 , ..., c0m−1 . Analogamente, per la seconda alternativa, la condizione ci è ripetuta m − 1 volte. Inoltre, ci sono m − 1 condizioni di T T 0 (c02 , ..., c0m ) Inoltre notiamo che la dimensione delle type table risultante dalla fase di ragruppamento è 2·r Infatti, per ogni alternativa in T T sono generate, nel caso pessimo, le due alternative descritte sopra. Applicazione alla catena di type table Supponiamo d’ora in poi che l’operatore × esegua il Prodotto Cartesiano nelle due fasi, come descritto in precedenza. Quindi, consideriamo ancora le tabelle T T1 , ..., T Tn di dimensioni d1 , ..., dn . Ripetiamo inoltre la definizione di N C(k) data per ogni k in 1, ..., n come: 1 se k = 1, N C(k) = 1 + N C(k − 1) se k > 1. Qundi ridefiniamo la funzione che calcola la dimensione della type table P C(k): d 1 dim(k) = 2 · d k se k = 1, se k > 1. 3.3 Prodotto Cartesiano 47 La funzione che conta il numero di condizioni atomiche per alternativa della type table P C(k) diventa dunque: 1 se k = 1, N C(k) = (dim(k − 1) − 1) + (dim(k − 1) − 1) · N C(k − 1) se k > 1. che è equivalente a: 1 se k = 1, N C(k) = (dim(k − 1) − 1) · (1 + N C(k − 1)) se k > 1. A questo punto, il numero totale di condizioni presente nella type table P C(k) è dato dalla formula: T N C(k) = dim(k) · N C(k) Dal momento che noi siamo iteressati a calcolarla in n, abbiamo: T N C(n) = dim(n) · N C(n) 3.3.3 Confronto con Run-Time Checking L’algoritmo del Prodotto Cartesiano evita i costi intrinseci alla verifica completamente dinamica dei vincoli di restrizione. Tuttavia nelle due varianti introduce un costo computazionale proporzionale al prodotto delle type table presenti in una catena di derivazione. Nonostante ciò va considerato che uno schema, nella maggior parte dei casi, non presenta catene di restrizione molto profonde. In molti casi inoltre è possibile ottimizzare le T Tp effettuando riscritture dei predicati. É evidente, visto il suo costo computazionale, che il Prodotto Cartesiano è improponibile in un contesto generale. L’idea è quella di combinare entrambi gli algoritmi, run-time checking (RTC) e Prodotto Cartesiano(PC) o Prodotto Cartesiano con normalizzazione (PCN), ed utilizzarli a seconda della convenienza. Il costo di ciascuno di essi può essere determinato staticamente sullo schema in tempo lineare nella dimensione della gerarchia dei tipi. A tal proposito, per ciascuno dei tre algoritmi RTC, PC e PCN, riassumiamo di seguito le formule per il calcolo del costo computazionale a tempo di validazione: • TRT C (n, c) = n(a + c) • TP C (n, g) = n · g n • TP CN (n) = dim(n) · N C(n) 48 3. Verifica statica dei vincoli di restrizione con CTA In tabella 3.1 sono riportate alcune valutazioni delle funzioni per cui si è scelto di imporre c = g, ed è stato assegnato costo unitario ad ogni valutazione di predicato XPath. Per quanto riguarda RTC sono stati presi in considerazione i pesi 5 e 50 per a. É possibile vedere in quali casi PC e PCN sono migliori di RTC. c n RTC 1 1 1a + 1 1 2 2a + 2 1 3 3a + 3 1 4 4a + 4 1 5 5a + 5 2 1 1a + 2 2 2 2a + 4 2 3 3a + 6 2 4 4a + 8 2 5 5a + 10 3 1 1a + 3 3 2 2a + 6 3 3 3a + 9 3 4 4a + 12 3 5 5a + 15 4 1 1a + 4 4 2 2a + 8 4 3 3a + 12 4 4 4a + 16 4 5 5a + 20 5 1 1a + 5 5 2 2a + 10 5 3 3a + 15 5 4 4a + 20 5 5 5a + 25 PC 1 2 3 4 5 2 8 24 64 160 3 18 81 324 1215 4 32 192 1024 5120 5 50 375 2500 15625 PCN 1 4 6 8 10 2 8 36 120 372 3 24 150 780 3930 4 48 392 2800 19656 5 80 810 7380 66510 RTC(a = 5) 6 12 18 24 30 7 14 21 28 35 8 16 24 32 40 9 18 27 36 45 10 20 30 40 50 RTC(a = 50) 51 102 153 204 255 52 104 156 208 260 53 106 159 212 265 54 108 162 216 270 55 110 165 220 275 Tabella 3.1: Costo introdotto nella fase di validazione da RTC, PC e PCN. Notiamo quindi che, per certe dimensioni dell’input, l’approccio statico è più conveniente rispetto all’approccio completamente dinamico. Inoltre, nel caso di validazione di documenti multipli, i vantaggi del Prodotto Cartesiano potrebbero essere maggiori. Capitolo 4 Implementazione A scopo dimostrativo è stato realizzato un prototipo in Java degli algoritmi per il Prodotto Cartesiano (PC, PCN). Dal momento che essi hanno senso solo all’interno di un processo di validazione, si è scelto di fornirne un’implementazione all’interno di un parser XML open source esistente: Xerces [xer08]. Per la precisione il prototipo del Prodotto Cartesiano è stato integrato all’interno di una versione “hacked ” di Xerces che ospita un prototipo di validatore SchemaPath, in modo da disporre in partenza di un meccanismo di validazione completo di assegnamento condizionale, pronto per i test. La versione di Xerces usata per il prototipo SchemaPath è la 2.7.4. In primo luogo dunque è stato effettuato un avanzamento delle API del parser all’ultima versione disponibile (2.9.1). Inoltre è stato adattato il parsing alla sintassi di CTA, in quanto SchemaPath adotta una sintassi XML differente. In seguito è stata progettata una interfaccia per le feature introdotte, in particolare un meccanismo di pre-processing del modello ad oggetti di uno schema, implementata dalla classe che realizza il prodotto cartesiano. É stato realizzato inoltre un meccanismo di semplificazione dei predicati XPath, in modo da ridurre il carico sulla validazione. Infine i componenti introdotti sono stati integrati prima in un validatore di test creato appositamente, e poi nel validatore SchemaPath. Un’ultima fase riguarda la creazione di una piattaforma di test, sul modello della testsuite di XML Schema[TTC07], comprensiva di una serie di gruppi di test, atti alla verifica della correttezza dei meccanismi realizzati. Per prima cosa illustreremo brevemente le XML Schema API, soffermandoci in particolare sulle componenti principali dell’XSModel. In seguito passeremo a descriverne l’implementazione all’interno di Xerces, mostrando i cambiamenti effettuati per fare spazio a CTA. Passeremo poi a descrivere l’architettura globale delle API proposte, descrivendo alcune delle principali classi e mostrando in che modo possono esse49 50 4. Implementazione re utilizzate. Descriveremo poi la piattaforma di test realizzata, spiegando i test proposti. 4.1 Xerces e XML Schema API La rappresentazione interna di un’istanza di schema XSDL, così come i suoi processi di validazione, aderisce in Xerces alla specifica di XML Schema API[Lit04]. Le XML Schema API forniscono un’interfaccia, indipendente dal linguaggio, cui fanno riferimento i processi di validazione. Principalmente questa specifica fornisce quattro categorie di interfacce, in base alla loro funzionalità: • un modello per la rappresentazione e per la manipolazione delle Schema Component, detta anche XSModel ; • un’interfaccia per l’accesso e la modifica del contributo della validazione sugli elementi dei documenti d’istanza; • un’interfaccia che si occupa della regolamentazione del caricamento di uno schema, inteso come risorsa, e della creazione della relativa rappresentazione; • una serie di strutture dati ausiliarie, quali: liste, mappe, set. Xerces implementa pienamente queste API. Per quanto riguarda il prototipo realizzato tuttavia ci interessa solo una parte dell’interfaccia, in particolare XSModel, in quanto esso non è coinvolto direttamente nella fase di validazione. Di seguito saranno presentate le classi principali, evidenziando i cambiamenti necessari effettuati per adeguare l’API a CTA. 4.1.1 XSModel XSModel è il modello di rappresentazione di uno schema. Esso ospita una struttura dati di tipo grafo (lo schema), i cui ogni nodo è rappresentazione di una schema component. Ogni particolare schema components implementa l’interfaccia di XSObject, che racchiude appunto tutte le proprietà comuni ad esse. In particolare qualunque XSObject possiede una proprietà che consente di identificare il tipo particolare della componente ed il nome ed il namespace cui essa fa riferimento. Ad esempio la dichiarazione dell’elemento <elem> nello schema: <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="www.example.org/elem"> 4.1 Xerces e XML Schema API <xs:element name="elem"/> </xs:schema> Sarà rappresentata da un XSObject che ha proprietà: • name = “elem”; • namespace = “www.example.org/elem”; • type = ELEMENT_DECLARATION; Ogni schema component dunque ha una relativa interfaccia nel XSModel. Nell’implementazione fornita da Xerces ad ogni XSObject specificato nelle API è associata una interfaccia, e le componenti che hanno una specifica rappresentazione XML presentano una classe concreta che implementa le interfacce specificate dai supertipi, ovvero le componenti di cui sono un tipo specifico. Ad esempio una dichiarazione di elemento, secondo le specifiche di Schema 1.0 e 1.1, è un tipo particolare di term component. Dunque l’interfaccia di una element declaration è estesa dalla relativa interfaccia XSTerm oltre che da quella di XSObject. Dunque in quanto in XSDL 1.1 è stata introdotta la schema component alternative type, con lo scopo di rappresentare un assegnamento di tipo alternativo, in modo analogo sono state introdotte durante la realizzazione del prototipo un’interfaccia XSAlternativeTypeDefinition e la sua implementazione XSAlternativeTypeDef nell’XSModel. Per lo stesso motivo si sono rese necessarie alcune modifiche all’interfaccia di XSElementDeclaration. Nello specifico è stato introdotto il metodo getTypeTable() per ottenere la type table associata alla dichiarazione di elemento, ed un metodo hasConditionalType() attraverso cui è possibile sapere se la dichiarazione di elemento racchiude alternative. Costruzione dell’XSModel L’XSModel è costruito in Xerces a partire da un DOM document. Il DOM è attraversato in una visita da differenti traverser, oggetti che hanno il compito di creare ed assemblare le varie componenti a seconda della loro rappresentazione XML. Esiste un traverser per ogni component. Le chiamate dei traverser seguono l’annidamento delle componenti stesse. Ad esempio durante la visita di un nodo di tipo dichiarazione di elemento, il rispettivo traverser, XSDElementTraverser, invocherà il traverser XSDComplexTypeTraverser qualora incontrerà un elemento DOM <complexType> tra i figli di quello corrente. 51 52 4. Implementazione Per la costruzione delle XSAlternativeTypeDefinition è stato realizzato il traverser relativo (AlternativeTypeDefinitionTraverser), che è invocato all’occorrenza di ogni nodo <alternative> dal traverser XSDElementTraverser, opportunamente modificato. Estensione dei tipi primitivi Per quanto riguarda CTA inoltre XSDL fa riferimento a due nuovi tipi semplici: xs:error, di cui si è già parlato in sez. 2.2.2, e xs:xpathDefaultNamespace. Sono stati creati dunque due rispettivi data-type validator in Xerces. Il validatore per xs:error chiaramente lancia sempre un eccezione di valore non valido. Il validatore per xs:xpathDefaultNamespace invece si comporta allo stesso modo di quello di xs:anyUri ed accetta inoltre anche i valori: ##defaultNamespace, ##targetNamespace e ##local. Implementazione dei vincoli di correttezza dello schema Al fine di riprodurre correttamente i meccanismi specificati in XSDL per quanto riguarda CTA, sono stati realizzati i vincoli sulle componenti introdotte, appartenenti a due categorie: vincoli sulla rappresentazione XML delle componenti e regole di validità dello schema. Per quanto riguarda la prima classe di vincoli ci riferiamo alle regole del tipo: un elemento <alternative> deve necessariamente possedere un attributo type o in alternativa un elemento <complexType> o <simpleType>. Eventuali violazioni nella rappresentazione XML sono rilevate dai rispettivi traverser in fase di costruzione degli XSObject. Viceversa le regole di validità dello schema sono implementate all’interno dei metodi di una classe già esistente in Xerces, che ha il compito di verificare appunto la correttezza dello schema: XSConstraints. In particolare sono stati implementati controlli di corretta restrizione tra il declared type di una dichiarazione di elemento e i tipi condizionali presenti nella sua type table. Inoltre è stato esteso il vincolo Element Declarations Consistent in presenza di dichiarazioni condizionali: nello stesso content model non possono apparire due dichiarazioni condizionali con stesso nome e namespace, a meno che non abbiano stesso tipo e se presente, stessa type table. 4.2 Architettura globale 4.2 Architettura globale In questa sezione presenteremo l’architettura progettuale del prototipo. Saranno descritti in modo generale i modelli per realizzare il Prodotto Cartesiano in Xerces. In particolare saranno descritte le classi e le interfacce introdotte motivandone la loro necessità e mostrandone le loro caratteristiche funzionali. Per prima cosa va detto che ogni componente è integrata in Xerces con un impatto minimo sul codice esistente. I file dell’implementazione risiedono tutti in un package java separato: cta. Sono stati adottati diversi accorgimenti in questo senso, ad esempio l’uso di traverser sul XSModel, per implementare operazioni sulle componenti, piuttosto che la modifica delle interfacce stesse. 4.2.1 Visita generica del XSModel Ogni lavoro sull’XSModel richiede una visita del grafo che esso rappresenta. A questo scopo è stato realizzato un meccanismo generico di visita delXSModel basato sul pattern observer, presente nel package cta.traversers. Un traverser, rappresentato dall’interfaccia ObservableXSModelTraverser, si occupa di navigare il modello e di sollevare un evento alla visita di ogni componente. Altri oggetti, che sono registrati sul traverser come listener, si occupano di gestire l’evento svolgendo le operazioni sugli XSObject. In questo modo il comportamento della visita è separato dalla navigazione del modello. Si noti che, secondo questo meccanismo, è possibile mettere in pipeline differenti listener, così da realizzare una sequenza di operazioni differenti in classi separate, ed imporre un ordine nella loro applicazione sulle singole componenti. L’ordine di visita è imposto dalle sottoclassi di ObservableXSModelTraverser, in particolare per il prototipo è stato implementato nella classe XSModelDFS l’ordine depth-first-search(DFS). La classe XSObjectTraversedEvent rappresenta l’evento relativo alla navigazione di una componente. In quanto tale esso contiene un riferimento all’XSObject relativo. Ogni listener per gli eventi XSObjectTraversedEvent deve implementare invece l’interfaccia XSObjectTraversingListener. Per quanto riguarda la creazione degli oggetti di tipo ObservableXSModelTraverser non è stato realizzato nessun meccanismo specifico. I traverser sono infatti un meccanismo di basso livello, dunque non sono fatti per comparire nel codice client. Inoltre a causa della presenza di uno stato non è possibile vincolare l’esistenza di un traverser ad una singola istanza, allorchè sarebbe stata una buona scelta farlo. Viceversa non è previsto un elevato numero di traverser durante l’esecuzione e quindi sono state scartate ipotesi quali clonazione e prototype. 53 54 4. Implementazione 4.2.2 Gerarchia dei tipi La componente XSTypeDefinition di XSModel possiede un riferimento al tipo base. Il Prodotto Cartesiano però ha bisogno di percorrere la gerarchia in maniera top-down, per questo è necessario creare una struttura ad albero che consenta di attraversare la gerarchia dei tipi in quest’ordine. Anche in questo caso si è scelto chiaramente di adottare un modello separato, lasciando inalterato l’XSModel. Le classi relative a questo modello si trovano nel package cta.typehierarchy. La gerarchia di tipi è dunque un albero i cui nodi sono istanze di TypeHierarchyNode. TypeHierarchyNode è un’interfaccia che consente l’accesso alla relativa dichiarazione di tipo e la navigazione dell’albero. Allo scopo di ottenere un comportamento modulare, separando la gerarchia dalle operazioni effettuate su di essa, è stato adottato il pattern visitor : ogni nodo espone un metodo accept() che ne consente la visita. TypeHierarchyNode è implementata da due sottoclassi: SimpleTypeNode, che rappresenta una dichiarazione di tipo semplice, e ComplexTypeNode, che rappresenta una dichiarazione di tipo complesso. Attraverso la classe TypeHierarchy è possibile infine accedere ad una gerarchia di tipi attraverso un riferimento alla sua radice. La creazione della gerarchia di tipi è effettuata a partire da un XSModel ed è un procedimento complesso. A questo scopo assolve la factory TypeHierarchyFactory. Il compito specifico della creazione della gerarchia è effettuato attraverso l’uso di XSModelDFS dalla classe TypeHierarchyBuilder. Una considerazione importante al proposito va fatta nel caso in cui si voglia estendere una gerarchia esistente con i tipi appartenenti ad un nuovo XSModel. Per questa situazione TypeHierarchyBuilder è inteso come oggetto state-full, che mantiene un riferimento alla gerarchia creata, e un set dei tipi in essa presenti. In questo modo, ogni volta che viene invocato il processo di costruzione su un XSModel, TypeHierarchyBuilder aggiungerà i nuovi nodi alla gerarchia esistente, senza il rischio di duplicazioni. Ogni istanza di TypeHierarchyBuilder può essere riutilizzata, per costruire una gerarchia completamente nuova, invocando il metodo reset(). TypeHierarchyFactory dunque assolve a due compiti fondamentali: la creazione diretta di oggetti di tipo TypeHierarchy a partire da un XSModel e l’istanziazione di oggetti di tipo TypeHierarchyBuilder, in modo da consentire la creazione di gerarchie su XSModel differenti. 4.2 Architettura globale 4.2.3 XNI e SchemaPreprocessor XNI(Xerces Native Interface), interfaccia di Xerces per la costruzione di parser, consente di costruire pipeline di componenti coinvolti nel processo di parsing e validazione dei documenti XML. Sulla base di quest’interfaccia sono costruiti dunque i parser SAX e DOM. XNI specifica un’architettura ad eventi, simile a quella di SAX, al verificarsi dei quali ogni componente del processo di parsing invoca l’handler per l’evento della componente successiva. La prima componente della pipeline sarà ovviamente uno scanner XML, che innesca il meccanismo. Questo meccanismo consente di riutilizzare le stesse componenti all’interno di parser di natura differente. Ogni validatore in Xerces costituisce una componente per un parser. Si è posto dunque il problema di fare spazio al Prodotto Cartesiano e più in generale di come processare lo schema prima della validazione e rendere tale procedura riutilizzabile in validatori differenti. Questo compito è delegato alle classi dell’interfaccia SchemaPreprocessor. Le istanze di SchemaPreprocessor sono incaricate quindi di processare lo schema, ad esempio per effettuare analisi e ottimizzazioni in dipendenza del particolare algoritmo di validazione. Chiaramente queste operazioni non possono essere effettuate sempre al caricamento dello schema, poichè variano da validatore a validatore. Tuttavia non è giusto neanche implementarle tutte all’interno del validatore stesso, in quanto sarebbe ridotta la modularità e la riusabilità del codice. Per questo si è scelto di creare una classe apposita. Attraverso SchemaPreprocessor sono implementati, ad esempio, sia il Prodotto Cartesiano che la semplificazione dei predicati XPath. Dato che nella fase di pre-processing dello schema è possibile creare strutture dati da usare durante la validazione, anche le istanze di SchemaPreprocessor sono state intese come oggetti state-full. Tuttavia lo stato dell’oggetto è diverso a seconda del suo sottotipo. Per permettere al codice client di accedere allo stato senza dover conoscere il particolare tipo concreto di SchemaPreprocessor, nell’interfaccia è stato introdotto un metodo getStateByName(String id), il cui scopo è quello di ritornare un generico oggetto di stato associato con l’identificatore id. In modo analogo sono stati introdotti dei metodi generici per accedere e modificare le proprietà dell’oggetto. In questo modo è possibile ad esempio settare un flag per abilitare la stampa di informazioni di debug ed più in generale modificare il comportamento dei pre-processor senza dover usare i metodi specifici delle sottoclassi. É stata inoltre introdotta un’interfaccia PreprocessorSequence, che estende direttamente SchemaPreprocessor e rappresenta una composizione di più pre-processori. Essa consente di poter effettuare una sequenza di operazioni distinte su un XSModel 55 56 4. Implementazione attraverso l’invocazione di un solo metodo. Creazione di SchemaPreprocessor concreti Il compito di creare oggetti di tipo SchemaPreprocessor è delegata alla factory PreprocessorFactory, realizzata come singleton, che si occupa della creazione sia di singoli sottotipi di SchemaPreprocessor che di sequenze. Poichè però la factory non conosce direttamente i sottotipi da istanziare, l’istanziazione avviene attraverso reflection. I metodi createSchemaPreprocessor e createPreprocessorSequence istanziano i sottotipi di SchemaPreprocessor in base ad identificatori passati come argomento, che sono i nomi semplici delle sottoclassi, effettuando un look-up per le classi corrispondenti nel package cta.preprocessor.impl. Per comprendere meglio la flessibilità di questo meccanismo consideriamo la situazione nel prototipo: un validatore, al caricamento dello schema, deve semplificare gli XPath presenti nello schema e poi applicare l’algoritmo del prodotto cartesiano. Fatto ciò, durante la validazione vuole accedere alle type table ricavate per assegnare il tipo ad un elemento. Nel package cta.preprocessor.impl sono presenti dunque gli SchemaPreprocessor: • CartesianProductPreprocessor, che applica il prodotto cartesiano ricavando una mappa di type table ristrutturate. • CTAXPathRewriterPreprocessor, che semplifica e riscrive tutti i predicati XPath banali presenti nello schema. Il compito precedentemente illustrato può essere assolto dal seguente frammento di codice nel validatore: ... PreprocessorFactory pf = PreprocessorFactory.getInstance(); PreprocessorSequence ps = pf.createPreprocessorSequence( new String[]{"CTAXPathRewriterPreprocessor", "CartesianProductPreprocessor"}); XSModel model = loadSchema(); ps.processModel(model); //now we must retreive the result of the Cartesian Product: SchemaPreprocessor cp = ps.getComponentProcessors().get(1); 4.3 Prodotto Cartesiano Map typeTableMap = (Map) cp.getStateByName("typeTableMap"); ... 4.2.4 Semplificazione degli XPath La forma sintattica delle espressioni XPath all’interno dello schema è rilevante in termini di lavoro al momento della validazione. Considerando che a run-time può essere necessario valutare più volte gli stessi predicati si è deciso di implementare un meccanismo di riscrittura e semplificazione delle espressioni XPath. I casi d’uso ipotizzati in questo caso sono due. Il primo è strettamente correlato con il Prodotto Cartesiano: la semplificazione contribuirebbe ad accorciare le espressioni all’interno delle type table processate, che per costruzione sono molto lunghe e potrebbero contenere predicati banali. Un secondo caso d’uso è quello che coinvolge la semplificazione di tutti gli XPath durante la fase statica a prescindere dal Prodotto Cartesiano, considerato come generica ottimizzazione del processo di valutazione a run-time. L’implementazione di questo meccanismo è contenuta nel package cta.rewrite. L’interfaccia principale di questo meccanismo è costituita da Rewriter. Gli oggetti di questo tipo espongono due soli metodi reset e rewrite. In particolare questo secondo metodo è incaricato di effettuare una riscrittura di un’espressione XPath. Questa interfaccia, come tutto il meccanismo, fa riferimento al modello per le espressioni XPath presente in Xerces. In particolare il metodo rewrite lavora su oggetti di tipo ExpressionOwner, una classe pensata appositamente per consentire riscritture. Nelle API di Xerces la classe XPath inoltre implementa ExpressionOwner, dunque è possibile riscrivere direttamente gli oggetti XPath contenuti nelle type alternative. Un’implementazione di quest’interfaccia è data dalla classe DefaultRewriter. Questa provvede alla riscrittura dei predicati logici and, or, e not. Le rispettive regole di riscrittura sono riportate in tabella 4.1. 4.3 Prodotto Cartesiano Come già accennato l’algoritmo del Prodotto Cartesiano viene richiamato in entrambe le varianti proposte (PC e PCN) dalla la classe CartesianProductPreprocessor. Tuttavia la sua implementazione è espressa nella classe CartesianProductVisitor che è un visitor per la gerarchia dei tipi. In pratica l’algoritmo viene applicato in una visita dei nodi corrispondenti ai complex type della gerarchia. Per questo CartesianProductPreprocessor deve provvedere a creare la gerarchia ed aggior- 57 58 4. Implementazione (a) and (b) or And a∧a→a a ∧ ¬a → f alse ¬a ∧ a → f alse a ∧ f alse → f alse f alse ∧ a → f alse a ∧ true → a true ∧ a → a true ∧ true → true f alse ∧ f alse → f alse true ∧ f alse → f alse f alse ∧ true → f alse Or a∨a→a a ∨ ¬a → true ¬a ∨ a → true a ∨ f alse → a f alse ∨ a → a a ∨ true → true true ∨ a → true true ∨ true → true f alse ∨ f alse → f alse true ∨ f alse → true f alse ∨ true → true (c) not not ¬f alse → true ¬true → f alse Tabella 4.1: Regole di riscrittura dei predicati implementate nella classe DefaultRewriter narla ogni volta che viene chiamato su un XSModel. Una volta ottenuta la gerarchia dei tipi esso può invocare il visitor sulla radice innescando l’algoritmo. La variante dell’algoritmo può essere selezionata come proprietà del pre-processor attraverso l’apposita chiave riferita dall’URI http://web.cs.unibo.it/~mcasimir/ cp-properties#normalize. Il valore della proprietà è un oggetto di tipo Boolean attraverso cui si specifica se abilitare o meno la normalizzazione. Le type table riscritte vengono memorizzate ed indicizzate secondo il complex type e la dicharazione cui fanno riferimento in una mappa. Nello specifico tale mappa è un oggetto di tipo java.util.Map che lega una dichiarazione di tipo complesso ad una seconda mappa contenente l’associazione tra dichiarazione di elemento, intesa come stringa della forma “namespace:name” con la corrispondente type table processata, ovvero un oggetto di tipo XSObjectList. L’accesso alla mappa è consentito attraverso il metodo getStateById invocato con paramentro “typeTableMap”. Durante la visita di un tipo complesso, non visitato in precedenza, si attraversa il content model e per ogni dichiarazione di elemento incontrata: 1. Si cerca nella mappa delle type table processate la tabella della dichiarazione corrispondente nel complex type base, se non ce n’è una affinchè lo schema sia valido deve essere presente una wildcard relativa alla dichiarazione cercata, dunque si cerca la dichiarazione top-level corrispondente. Qualora non venga trovata si memorizza la type table corrente nella mappa e si continua con la 4.4 Test 59 prossima dichiarazione dal momento che, se lo schema è corretto, in questo caso non è necessario effettuare alcuna operazione. 2. Ottenute le due type table si applica il Prodotto Cartesiano. 3. Se la normalizzazione e abilitata si effettua la normalizzazione della tabella risultante. 4. La tabella processata viene memorizzata nella mappa delle type table 4.4 Test Durante la realizzazione del prototipo sono stati realizzati numerosi test riguardanti sia le specifiche componenti, con test mirati effettuati attraverso il framework JUnit, che il funzionamento globale dei meccanismi realizzati, con una suite di test sul modello della testsuite di XML Schema, accompagnata da una interfaccia grafica minimale. 4.4.1 Parser e validatore di test In primo luogo sono stati realizzati un parser ed un validatore di test, nelle classi cta.CTAParser e CTAXMLSchemaValidator. Il parser è un parser SAX che include semplicemente il validatore di test nella propria pipeline. Il validatore usato invece è costruito sul validatore XML Schema di Xerces, con la sola eccezione che allo start element assegna i tipi secondo CTA utilizzando le type table riscritte dal Prodotto Cartesiano. 4.4.2 Integrazione in SchemaPath Una seconda importante modalità di test è stata effettuata attraverso l’inclusione del Prodotto Cartesiano nel processo di validazione di SchemaPath. Nel validatore esistente è stato per questo richiamato il pre-processor che applica il Prodotto Cartesiano sullo schema al momento del suo caricamento. 4.4.3 TestSuite La testsuite di XML Schema è strutturata in modo da automatizzare i test per la validazione di schemi XSDL. Fondamentalmente una test suite è costituita da una serie di coppie schema-istanza, che costituiscono l’oggetto dei test, e da una descrizione XML dei test (test set), che comprende indicazioni circa i file coinvolti, sulla 60 4. Implementazione documentazione e soprattutto sul risultato atteso per i test. Un test set è organizzato in test group, ovvero gruppi di test accomunati da una particolare caratteristica da verificare. Ogni test group racchiude una serie di elementi <schemaTest> e <instanceTest>. Poichè il comportamento dei test varia a seconda del particolare gruppo, la grammatica del file di descrizione è stata modificata in modo che gli elementi <testGroup> contengano un attributo type di tipo anyUri. In questo modo il comportamento delle procedure di test è deciso univocamente all’interno del file di descrizione test set. Parallelamente all’interno del modello per i test è stata creata una classe TestDriver per ogni tipo di gruppo. Ognuna di esse è associata ad un URI relativo ad un test group. Le classi che realizzano la piattaforma di test sono racchiuse all’interno del package it.unibo.cs.web.mcasimir.cta.test.testsuite. Di seguito saranno descritti i gruppi di test presenti nel test set. Correttezza dello schema Il test di correttezza dello schema, implementato dalla classe SchemaCheckingTest, carica uno schema ed abilita il controllo totale dei vincoli. Correttezza del Prodotto Cartesiano I test di correttezza del Prodotto Cartesiano fanno riferimento ad una serie di schemi, che racchiudono gerarchie di derivazione per restrizione, più o meno complesse, con dichiarazioni condizionali. La classe che effettua questo test è CartesianProductTest, essa non fa altro che caricare lo schema e invocare il metodo processModel di CartesiaProductPreprocessor sul XSModel risultante, stampando la struttura delle type table prima e dopo il processing. Validazione I test della validazione sono stati eseguiti sia attraverso il validatore di test che attaverso il validatore SchemaPath. La procedura di test prevede il parsing delle istanze attraverso un parser che include uno di questi validatori nella pipeline. A seconda del validatore utilizzato questi test sono effettuati dagli oggetti di classe InstanceValidationTest e SchemapathValidationTest. 4.5 Limitazioni 4.5 Limitazioni Le limitazioni di questo prototipo riguardano principalmente la dipendenza dal modello per espressioni XPath 1.0 di Xerces. Infatti al fine di aderire correttamente alle specifiche XSDL 1.1 dovrebbe essere gestita la sintassi XPath 2.0, tuttavia visto lo scarso numero di implementazioni esistenti, e il suo mancato supporto in SchemaPath si è scelto di adottare il modello di Xerces, basato ancora sulle specifiche della versione 1.0. 61 Capitolo 5 Conclusioni In questo lavoro abbiamo mostrato che è possibile verificare i vincoli di restrizione con CTA attraverso una tecnica parzialmente statica, enumerando tutte le possibili situazioni a run-time: il Prodotto Cartesiano. Per prima cosa abbiamo esaminato una serie di tecniche alternative, tra cui abbiamo presentato sia soluzioni statiche che dinamiche. In merito alle soluzuioni statiche ne abbiamo considerate alcune che, attraverso restrizioni sintattiche, impediscono la scrittura di schemi potenzialmente scorretti. La più importante di esse è Prefix che risolve il problema imponendo la ripetizione delle type table come prefisso delle type table ristrette. Abbiamo mostrato come questa tecnica limiti in maniera eccessiva l’esspressività dei costrutti per Conditional Type Assignment. Inoltre una seconda proposta, simile, vincola la struttura delle type table lungo la catena di restrizione. Ovvero impone che ogni dichiarazione di elemento che compaia in un tipo derivato per restrizione, abbia la stessa type table della corrispettiva dichiarazione nel tipo base: stesso numero di alternative, stessi predicati e tipi assegnati ristretti. Chiaramente questa soluzione manca di espressività ancora più di Prefix. Abbiamo dunque analizzato una soluzione, totalmente dinamica, che non impone limiti sintattici. La relativa tecnica di verifica è chiamata Run-time Check (RTC) ed è stata formalizzata in un algoritmo scaturito dalla definizione del vincolo Conditional Type Substitutable in Restriction, presente nelle specifiche di XSDL 1.1. RTC ravvisa le condizioni che violano le relazioni di corretta restrizione al momento della validazione ed in dipendenza dal contesto. A tempo di validazione si dispone del documento d’istanza, dunque una volta assegnato il tipo in base al contesto e 63 64 5. Conclusioni secondo una dichiarazione di elemento, RTC provvede a controllare che esso non violi le restrizioni. A questo proposito abbiamo fatto notare che, poichè le relazioni di restrizione sono proprietà dello schema, è poco conveniente verificare tali relazioni man mano che si processono gli elementi del documento di istanza. Per Run-time Check abbiamo dunque fornito un algoritmo in pseudo-codice e mostrato il costo computazionale. In particolare abbiamo mostrato che la verifica è lineare nella profondità della catena di derivazione. Abbiamo quindi proposto la tecnica del Prodotto Cartesiano, che effettua i controlli di restrizione in maniera statica enumerando ed analizzando tutte le possibili situazioni che si possono verificare durante la validazione. Il Prodotto Cartesiano deriva da questa analisi un nuovo insieme di type table, che assegnano tipo xs:error in presenza di situazioni erronee. Queste nuove type table saranno utilizzate a runtime dal validatore per assegnare il tipo agli elementi al posto di quelle originali. Abbiamo mostrato in che modo vengano costruite le type table e che questo processo ha costo quadratico in termini di spazio e tempo. Inoltre abbiamo esaminato il costo complessivo dell’algoritmo e mostrato come esso sia proporzionale al prodotto delle type table presenti in una catena di derivazione, data l’enumerazione di tutte le situazioni. Abbiamo inoltre definito il numero di predicati all’interno delle type table risultato in funzione di quelli racchiusi nelle type table originali. Al fine di ridurre il costo computazionale dell’algoritmo abbiamo introdotto alcune proprietà delle type table. Nello specifico abbiamo definito il concetto di type table deterministica, ovvero una type table per cui un solo predicato sia soddisfacibile, e di type table normalizzata, ovvero una type table in cui non ci sono tipi ripetuti. Abbiamo dunque mostrato alcune relazioni di equivalenza semantica tra type table e una tecnica di normalizzazione. In seguito abbiamo dunque mostrato una variante del Prodotto Cartesiano, PCN, che effettua una normalizzazione parziale delle tabelle. Abbiamo messo in evidenza come, attraverso questa pratica, il costo dell’intera procedura in fase statica sia ridotto nel tempo ad una funzione quadratica nella profondità della gerarchia di derivazione. Abbiamo quindi fornito dunque una relazione tra il numero di condizioni XPath presenti nello schema e quelle presenti nelle type table ristrutturate in questa maniera, da cui se ne deduce il costo computazionale introdotto a run-time. Abbiamo mostrato infine una comparazione tra PC, PCN e RTC in base ai costi di assegnamento dei tipi per un elemento nel caso pessimo. 5.1 Sviluppi futuri É stato inoltre realizzato un prototipo in java degli algoritmi PC e PCN integrato nella versione 2.9.1 di Xerces insieme ad un validatore SchemaPath. In base ai risultati ottenuti concludiamo che una verifica parzialmente statica dei vincoli di restrizione con CTA è possibile. Tuttavia visto il costo della enumerazione delle condizioni essa non è attuabile in una situazione generica. 5.1 Sviluppi futuri La verifica a tempo di compilazione della corretta restrizione fornisce numerosi vantaggi. Innanzitutto l’autore dello schema è messo nelle condizioni di ravvisare alcune situazioni impreviste in cui i vincoli sono violati, e in secondo luogo essa riduce il carico dell’assegnamento dei tipi in fase di validazione. Sarebbe interessante a tale proposito indagare soluzioni differenti. Ad esempio studiando la possibilità di vincoli sintattici che impediscano le violazioni senza ridurre l’espressività del meccanismo. 65 Bibliografia [BBC+ 07] Anders Berglund, Scott Boag, Don Chamberlin, Mary F. Fernández, Michael Kay, Jonathan Robie, and Jérôme Siméon. Xml path language (xpath) 2.0. http://www.w3.org/TR/xpath20, W3C Recommendation, 23 January 2007. [BPSM+ 06] T. Bray, J. Paoli, C. M. Sperberg-McQueen, E. Maler, and F. Yergeau. Extensible markup language (xml) 1.0 (fourth edition). http://www. w3.org/TR/REC-xml, W3C Recommendation, 16 August 2006. [CM01] J. Clark and M. Murata. RELAX NG Specification. http://relaxng. org/, Dicembre 2001. [CMV04] Claudio Sacerdoti Coen, Paolo Marinelli, and Fabio Vitali. Schemapath, a minimal extension to xml schema for conditional constraints. In WWW ’04: Proceedings of the 13th international conference on World Wide Web, pages 164–174, New York, NY, USA, 2004. ACM. [ea02] S. Pemberton et al. Xhtml 1.0 the extensible hypertext markup language (second edition). http://www.w3.org/TR/xhtml1/, W3C Recommendation, 1 Agosto 2002. [GSMT07] S. Gao, C. M. Sperberg-McQueen, and H. S. Thompson. W3C XML Schema Definition Language (XSDL) 1.1 Part 1: Structures. http: //www.w3.org/TR/xmlschema11-1/, W3C Working Draft, 30 Agosto 2007. [Jel02] R. Jelliffe. The schematron assertion language 1.5. http://xml.ascc. net/resource/schematron/Schematron2000.html, Ottobre 2002. [Kay07] Michael Kay. Xsl transformations (xslt) version 2.0. http://www.w3. org/TR/xslt20, W3C Recommendation, 23 January 2007. 67 68 BIBLIOGRAFIA [Lit04] Elena Litani. XML Schema API. http://www.w3.org/Submission/ 2004/SUBM-xmlschema-api-20040122/, W3C Member Submission, 22 January 2004. [MVZ07] P. Marinelli, F. Vitali, and S. Zacchiroli. Streaming validation of schemata: the lazy typing discipline. In In Proceedings of Extreme Markup Languages 2007: The Markup Theory and Practice Conference, Montreal, Canada, 7 August 2007. IDEAlliance. [NCEF02] Christian Nentwich, Licia Capra, Wolfgang Emmerich, and Anthony Finkelstein. xlinkit: a consistency checking and smart link generation service. ACM Trans. Inter. Tech., 2(2):151–185, 2002. [SM06a] C. M. Sperberg-McQueen. Co-constraint use cases - esw wiki. The ESW Wiki, official W3C wiki for discussing standard and specification, http: //esw.w3.org/topic/Co-constraint_Use_Cases, 31 Marzo 2006. [SM06b] C. M. Sperberg-McQueen. Co-occurrence constraints - esw wiki. The ESW Wiki, official W3C wiki for discussing standard and specification, http://esw.w3.org/topic/Co-occurrence_constraints, 28 Marzo 2006. [TBMM04] H. S. Thompson, D. Beech, M. Maloney, and N. Mendelsohn. Xml schema part 1: Structures. http://www.w3.org/TR/xmlschema-1, W3C Recommendation, 28 October 2004. [TTC07] Henry S. Thompson, John Tebbutt, and Tony Cincotta. XML Schema Test Suite. http://www.w3.org/XML/2004/xml-schema-test-suite, 20 June 2007. [xer08] Xerces java parser readme. http://xerces.apache.org/xerces-j, Febbraio 2008. [Zin07] Jacopo Zingoni. Introduzione di co-constraint nello schema for schema 1.1 normativo. Tesi di laurea universitá di Bologna, 2007.