Ajout parseur HTML + ses tests.
This commit is contained in:
parent
3e62258518
commit
0b7ed284ae
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "freedatas2html",
|
"name": "freedatas2html",
|
||||||
"version": "0.7.4",
|
"version": "0.8.0",
|
||||||
"description": "Conversion and display of data in different formats (CSV, JSON, HTML) with the possibility of filtering, classifying and paginating the results.",
|
"description": "Conversion and display of data in different formats (CSV, JSON, HTML) with the possibility of filtering, classifying and paginating the results.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
<link rel="stylesheet" href="CSS/paper.min.css">
|
<link rel="stylesheet" href="CSS/paper.min.css">
|
||||||
<link rel="stylesheet" href="CSS/fd2html.css">
|
<link rel="stylesheet" href="CSS/fd2html.css">
|
||||||
<script src="JS/exampleWithCSV.app.js" defer></script>
|
<script src="JS/exampleWithHTML.app.js" defer></script>
|
||||||
<title>freeDatas2HTML : démo d'affichage de données venant d'un fichier CSV</title>
|
<title>freeDatas2HTML : démo d'affichage de données venant du DOM (HTML)</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="paper container">
|
<body class="paper container">
|
||||||
<h1>freeDatas2HTML</h1>
|
<h1>freeDatas2HTML</h1>
|
||||||
<p><a href="./withJSON.html">Autre page d'exemple avec des données transmises en JSON</a>.</p>
|
<p>Autres pages d'exemple avec des données transmises <a href="./withJSON.html">en JSON</a> ou <a href="./withCSV.html">en CSV</a>.</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="filtre1" class="sm-12 md-6 lg-4 col"></div>
|
<div id="filtre1" class="sm-12 md-6 lg-4 col"></div>
|
||||||
@ -23,15 +23,113 @@
|
|||||||
|
|
||||||
<h3>Nombre total de résultats : <span id="compteur"></span></h3>
|
<h3>Nombre total de résultats : <span id="compteur"></span></h3>
|
||||||
<article id="datas">
|
<article id="datas">
|
||||||
<p>Si tout se passe bien, ce texte sera remplacé par un tableau de données extraites du fichier csv.</p>
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Séquence</th><th>Signification</th><th>Décrit en section</th><th>Défini originellement en CSS niveau</th>
|
||||||
|
</tr>
|
||||||
|
</thead
|
||||||
|
<tbody>
|
||||||
|
<tr><td>*</td><td>tout élément</td><td>Sélecteur universel</td><td>2</td></tr>
|
||||||
|
<tr><td>E</td><td>tout élément de type E</td><td>Sélecteur de type d'élément</td><td>1</td></tr>
|
||||||
|
<tr><td>E[foo]</td><td>tout élément E portant l'attribut "foo"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
|
||||||
|
<tr><td>E[foo="bar"]</td><td>tout élément E portant l'attribut"
|
||||||
|
foo" et dont la valeur de cet attribut est exactement "bar"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
|
||||||
|
<tr><td>E[foo~="bar"]</td><td>tout élément E dont l'attribut "foo"
|
||||||
|
contient une liste de valeurs séparées par des espaces, l'une
|
||||||
|
de ces valeurs étant exactement égale à "bar"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
|
||||||
|
<tr><td>E[foo^="bar"]</td><td>tout élément E dont la valeur de l'attribut
|
||||||
|
"foo" commence exactement par la chaîne "bar"</td><td>Sélecteurs d'attribut</td><td>3</td></tr>
|
||||||
|
<tr><td>E[foo$="bar"]</td><td>tout élément E dont la valeur de l'attribut
|
||||||
|
"foo" finit exactement par la chaîne "bar"</td><td>Sélecteurs d'attribut</td><td>3</td></tr>
|
||||||
|
<tr><td>E[foo*="bar"]</td><td>tout élément E dont la valeur de l'attribut
|
||||||
|
"foo" contient la sous-chaîne "bar"</td><td>Sélecteurs d'attribut</td><td>3</td></tr>
|
||||||
|
<tr><td>E[lang|="en"]</td><td>tout élément E dont l'attribut 'lang"
|
||||||
|
est une liste de valeurs séparées par des tirets et commençant
|
||||||
|
(à gauche) par "en"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
|
||||||
|
<tr><td>E:root</td><td>un élément E, racine du document</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:nth-child(n)</td><td>un élément E qui est le n-ième enfant
|
||||||
|
de son parent</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:nth-last-child(n)</td><td>un élément E qui est le n-ième enfant
|
||||||
|
de son parent en comptant depuis le dernier enfant</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:nth-of-type(n)</td><td>un élément E qui est le n-ième enfant
|
||||||
|
de son parent et de ce type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:nth-last-of-type(n)</td><td>un élément E qui est le n-ième enfant
|
||||||
|
de son parent et de ce type en comptant depuis le dernier enfant</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:first-child</td><td>un élément E, premier enfant de son parent</td><td>Pseudo-classes structurelles</td><td>2</td></tr>
|
||||||
|
<tr><td>E:last-child</td><td>un élément E, dernier enfant de son parent</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:first-of-type</td><td>un élément E, premier enfant de son type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:last-of-type</td><td>un élément E, dernier enfant de son type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td height="19">E:only-child</td><td height="19">un élément E, seul enfant de
|
||||||
|
son parent</td><td height="19">Pseudo-classes
|
||||||
|
structurelles</td><td height="19">3</td></tr>
|
||||||
|
<tr><td>E:only-of-type</td><td>un élément E, seul enfant de son type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:empty</td><td>un élément E qui n'a aucun enfant (y compris
|
||||||
|
noeuds textuels purs)</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
|
||||||
|
<tr><td>E:link <br>
|
||||||
|
E:visited</td><td>un élément E qui est la source d'un hyperlien
|
||||||
|
dont la cible n'a pas encore été visitée (:link) ou
|
||||||
|
a déjà été visitée (:visited)</td><td>Les pseudo-classes de lien</td><td>1</td></tr>
|
||||||
|
<tr><td>E:active <br>
|
||||||
|
E:hover <br>
|
||||||
|
E:focus</td><td>un élément E pendant certaines actions de
|
||||||
|
l'usager</td><td>Les pseudo-classes d'action
|
||||||
|
Usager </td><td>1 and 2</td></tr>
|
||||||
|
<tr><td>E:target</td><td>un élément E qui est la cible de l'URL d'origine
|
||||||
|
contenant lui-même un fragment identifiant.</td><td>La pseudo-classe :target</td><td>3</td></tr>
|
||||||
|
<tr><td>E:lang(c)</td><td>un élément E dont le langage (humain) est
|
||||||
|
c (le langage du document spécifie comment le langage humain est
|
||||||
|
déterminé)</td><td>La pseudo-classe :lang() </td><td>2</td></tr>
|
||||||
|
<tr><td>E:enabled<br>
|
||||||
|
E:disabled </td><td>un élément d'interface utilisateur E qui
|
||||||
|
est actif ou inactif.</td><td>Les pseudo-classes d'état
|
||||||
|
d'élément d'interface</td><td>3</td></tr>
|
||||||
|
<tr><td>E:checked<br>
|
||||||
|
E:indeterminate </td><td>un élément d'interface utilisateur E qui
|
||||||
|
est coché ou dont l'état est indéterminé (par
|
||||||
|
exemple un bouton-radio ou une case à cocher)</td><td>Les pseudo-classes d'état
|
||||||
|
d'élément d'interface</td><td>3</td></tr>
|
||||||
|
<tr><td>E:contains("foo")</td><td>un élément E dont le contenu textuel concaténé
|
||||||
|
contient la sous-chaîne "foo"</td><td>La pseudo-classe de contenu</td><td>3</td></tr>
|
||||||
|
<tr><td>E::first-line</td><td>la première ligne formatée d'un élément
|
||||||
|
E</td><td>The :first-line pseudo-element</td><td>1</td></tr>
|
||||||
|
<tr><td>E::first-letter</td><td>le premier caractère formaté d'un élément
|
||||||
|
E</td><td>Le pseudo-élément
|
||||||
|
::first-letter </td><td>1</td></tr>
|
||||||
|
<tr><td>E::selection</td><td>la partie d'un élément E qui est actuellement
|
||||||
|
sélectionnée/mise en exergue par l'usager</td><td>Les pseudo-éléments
|
||||||
|
fragments d'éléments d'interface</td><td>3</td></tr>
|
||||||
|
<tr><td>E::before</td><td>le contenu généré avant un élément
|
||||||
|
E</td><td>Le pseudo-élément
|
||||||
|
::before </td><td>2</td></tr>
|
||||||
|
<tr><td>E::after</td><td>le contenu généré après un
|
||||||
|
élément E</td><td>Le pseudo-élément
|
||||||
|
::after </td><td>2</td></tr>
|
||||||
|
<tr><td>E.warning</td><td><i>Uniquement en HTML</i>. Identique à E[class~="warning"].</td><td>Sélecteurs de classe</td><td>1</td></tr>
|
||||||
|
<tr><td>E#myid</td><td>un élément E dont l'ID est égal à
|
||||||
|
"myid".</td><td>Sélecteurs d'ID</td><td>1</td></tr>
|
||||||
|
<tr><td>E:not(s)</td><td> un élément E qui n'est pas représenté
|
||||||
|
par le sélecteur simple s</td><td>La pseudo-classe de négation</td><td>3</td></tr>
|
||||||
|
<tr><td>E F</td><td>un élément F qui est le descendant d'un
|
||||||
|
élément E</td><td>Combinateur de descendance
|
||||||
|
</td><td>1</td></tr>
|
||||||
|
<tr><td>E > F</td><td>un élément F qui est le fils d'un élément
|
||||||
|
E</td><td>Combinateur filial</td><td>2</td></tr>
|
||||||
|
<tr><td>E + F</td><td>un élément F immédiatement précédé
|
||||||
|
par un élément E</td><td>Combinateur d'adjacence
|
||||||
|
directe</td><td>2</td></tr>
|
||||||
|
<tr><td>E ~ F</td><td>un élément F précédé
|
||||||
|
par un élément E</td><td>Combinateur d'adjacence
|
||||||
|
indirecte</td><td>3</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</article>
|
</article>
|
||||||
<p id="pages"></p>
|
<p id="pages"></p>
|
||||||
<aside><p>Les données affichées viennent d’<a href="datas/elements-chimiques.csv">un fichier CSV</a> reprenant une partie des informations trouvées <a href="https://fr.wikipedia.org/wiki/%C3%89l%C3%A9ment_chimique#Caract%C3%A9ristiques_des_diff%C3%A9rents_%C3%A9l%C3%A9ments" target="_blank">sur Wikipédia</a>.</p>
|
<aside><p>Le tableau vient du <a href="https://www.w3.org/Style/css3-selectors-updates/WD-css3-selectors-20010126.fr.html#selectors" targe="_blank">W3C</a> qui pourra vous donner quelques pistes pour créer vos sélecteurs CSS si besoin.</p>
|
||||||
<p>Le dernier filtre (Mots-clés) permet de montrer la possibilité d’<b>extraire des données d’un champ ayant plusieurs valeurs par ligne</b>, ici séparées par une virgule, sachant que l’on peut désigner n’importe quel autre caractère séparateur.</p>
|
|
||||||
<p>Certains champs (=colonnes) peuvent être désignées comme devant permettre de <b>classer les données</b> : un 1ᵉʳ clic lance un classement par ordre croissant, le 2ᵉ pour ordre décroissant et ainsi de suite.</p>
|
<p>Certains champs (=colonnes) peuvent être désignées comme devant permettre de <b>classer les données</b> : un 1ᵉʳ clic lance un classement par ordre croissant, le 2ᵉ pour ordre décroissant et ainsi de suite.</p>
|
||||||
<p>Il est également possible d’afficher le nombre total de résultats, avec prise en compte des éventuels filtres.</p>
|
<p>Il est également possible d’afficher le nombre total de résultats, avec prise en compte des éventuels filtres.</p>
|
||||||
<p>Une autre option permet de <b>paginer les résultats</b>.</p>
|
<p>Une autre option permet de <b>paginer les résultats</b>.</p>
|
||||||
<p>Enfin il est possible d’<b>afficher les données autrement que sous forme de tableau HTML</b>. Si vous affichez cette page sur un écran large de moins de 800 px, les données seront simplement listées. Si vous avez un grand écran, vous pouvez visualiser cet affichage en faisant « Alt+Maj+M », puis « F5 ». Appuyez de nouveau sur « F5 », une fois de retour sur grand écran pour revoir le tableau.</p>
|
|
||||||
<p>Design basé sur <a href="https://www.getpapercss.com" target="_blank">PaperCSS</a></p></aside>
|
<p>Design basé sur <a href="https://www.getpapercss.com" target="_blank">PaperCSS</a></p></aside>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -10,11 +10,15 @@ module.exports =
|
|||||||
paginationNeedOptionsValues: "Vous n'avez fourni aucune options possibles pour la pagination.",
|
paginationNeedOptionsValues: "Vous n'avez fourni aucune options possibles pour la pagination.",
|
||||||
paginationNeedPositiveInteger: "Merci de fournir un nombre entier supérieur à zéro pour désigner chaque option de pagination.",
|
paginationNeedPositiveInteger: "Merci de fournir un nombre entier supérieur à zéro pour désigner chaque option de pagination.",
|
||||||
parserDatasNotFound : "Aucune donnée n'a été trouvée.",
|
parserDatasNotFound : "Aucune donnée n'a été trouvée.",
|
||||||
|
parserElementsNotFound: "Aucun élément trouvé dans le document pour le sélecteur fourni : ",
|
||||||
parserFail: "La lecture des données a échoué.",
|
parserFail: "La lecture des données a échoué.",
|
||||||
|
parserFieldNameFail: "Les noms de champs fournis doivent être uniques et ne peuvent être vides.",
|
||||||
|
parserFieldsNotFound: "Aucun nom de champs n'a été trouvé par le parseur.",
|
||||||
parserMeetErrors : "Au moins une erreur a été rencontrée durant le traitement des données.",
|
parserMeetErrors : "Au moins une erreur a été rencontrée durant le traitement des données.",
|
||||||
parserNeedDatas: "Merci de fournir une chaîne de caractères valide à parser.",
|
parserNeedDatas: "Merci de fournir une chaîne de caractères valide à parser.",
|
||||||
parserNeedSource: "Merci de fournir une chaîne de caractères où une url pour les données à parser.",
|
parserNeedSource: "Merci de fournir une chaîne de caractères où une url pour les données à parser.",
|
||||||
parserRemoteFail: "Erreur rencontrée durant l'accès aux données distantes.",
|
parserRemoteFail: "Erreur rencontrée durant l'accès aux données distantes.",
|
||||||
|
parserSelectorsIsEmpty: "Les sélecteurs CSS ne peuvent pas être une chaîne vide.",
|
||||||
parserTypeError: "Une donnée a été trouvée avec un type imprévu : ",
|
parserTypeError: "Une donnée a été trouvée avec un type imprévu : ",
|
||||||
remoteSourceHeaderUnallowed: "Le nom d'une des entêtes passées n'est pas autorisé.",
|
remoteSourceHeaderUnallowed: "Le nom d'une des entêtes passées n'est pas autorisé.",
|
||||||
remoteSourceNeedUrl: "Merci de fournir une url valide pour la source distante de données.",
|
remoteSourceNeedUrl: "Merci de fournir une url valide pour la source distante de données.",
|
||||||
|
@ -4,6 +4,7 @@ const errors=require("./errors.js");
|
|||||||
import { Counter, Datas, DatasRenders, DOMElement, Paginations, Parsers, ParseErrors, RemoteSource, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces";
|
import { Counter, Datas, DatasRenders, DOMElement, Paginations, Parsers, ParseErrors, RemoteSource, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces";
|
||||||
import { Pagination} from "./freeDatas2HTMLPagination";
|
import { Pagination} from "./freeDatas2HTMLPagination";
|
||||||
import { ParserForCSV} from "./freeDatas2HTMLParserForCSV";
|
import { ParserForCSV} from "./freeDatas2HTMLParserForCSV";
|
||||||
|
import { ParserForHTML} from "./freeDatas2HTMLParserForHTML";
|
||||||
import { ParserForJSON} from "./freeDatas2HTMLParserForJSON";
|
import { ParserForJSON} from "./freeDatas2HTMLParserForJSON";
|
||||||
import { Render} from "./freeDatas2HTMLRender";
|
import { Render} from "./freeDatas2HTMLRender";
|
||||||
import { Selector } from "./freeDatas2HTMLSelector";
|
import { Selector } from "./freeDatas2HTMLSelector";
|
||||||
@ -61,8 +62,7 @@ export class FreeDatas2HTML
|
|||||||
this.parser=new ParserForCSV(datasRemoteSource);
|
this.parser=new ParserForCSV(datasRemoteSource);
|
||||||
break;
|
break;
|
||||||
case "HTML":
|
case "HTML":
|
||||||
this.parser=new ParserForCSV(datasRemoteSource);
|
this.parser=new ParserForHTML(datasRemoteSource);
|
||||||
console.error("Appeler le parseur HTML");
|
|
||||||
break;
|
break;
|
||||||
case "JSON":
|
case "JSON":
|
||||||
this.parser=new ParserForJSON(datasRemoteSource);
|
this.parser=new ParserForJSON(datasRemoteSource);
|
||||||
|
@ -67,7 +67,8 @@ export interface Parsers
|
|||||||
{
|
{
|
||||||
datasRemoteSource: RemoteSource;
|
datasRemoteSource: RemoteSource;
|
||||||
setRemoteSource(settings : RemoteSourceSettings): void;
|
setRemoteSource(settings : RemoteSourceSettings): void;
|
||||||
datas2Parse: string;
|
datas2Parse?: string;
|
||||||
|
document2Parse?: HTMLDocument;
|
||||||
parseResults: ParseResults|undefined;
|
parseResults: ParseResults|undefined;
|
||||||
parse(): Promise<void>;
|
parse(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
142
src/freeDatas2HTMLParserForHTML.ts
Normal file
142
src/freeDatas2HTMLParserForHTML.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
const errors=require("./errors.js");
|
||||||
|
import { ParseErrors, ParseResults, Parsers, RemoteSource, RemoteSourceSettings } from "./freeDatas2HTMLInterfaces";
|
||||||
|
import { RemoteSources } from "./freeDatas2HTMLRemoteSources";
|
||||||
|
|
||||||
|
export class ParserForHTML implements Parsers
|
||||||
|
{
|
||||||
|
private _datasRemoteSource: RemoteSource;
|
||||||
|
private _document2Parse: HTMLDocument=document;
|
||||||
|
private _parseResults: ParseResults|undefined=undefined;
|
||||||
|
private _fieldsSelector: string="table > thead > tr > th";
|
||||||
|
private _rowsSelector: string="table > tbody > tr";
|
||||||
|
private _datasSelector: string="tr > td";
|
||||||
|
|
||||||
|
// L'instance d'une autre classe que RemoteSource peut être passée au constructeur
|
||||||
|
constructor(datasRemoteSource?: RemoteSource)
|
||||||
|
{
|
||||||
|
if(datasRemoteSource !== undefined)
|
||||||
|
this._datasRemoteSource=datasRemoteSource;
|
||||||
|
else
|
||||||
|
this._datasRemoteSource=new RemoteSources({ url:"" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public setRemoteSource(source: RemoteSourceSettings)
|
||||||
|
{
|
||||||
|
this._datasRemoteSource=new RemoteSources(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
get datasRemoteSource() : RemoteSource
|
||||||
|
{
|
||||||
|
return this._datasRemoteSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
get document2Parse() : HTMLDocument
|
||||||
|
{
|
||||||
|
return this._document2Parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
set fieldsSelector(selector: string)
|
||||||
|
{
|
||||||
|
if(selector.trim() === "")
|
||||||
|
throw new Error(errors.parserSelectorsIsEmpty);
|
||||||
|
else
|
||||||
|
this._fieldsSelector=selector.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
get fieldsSelector() : string
|
||||||
|
{
|
||||||
|
return this._fieldsSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
set rowsSelector(selector: string)
|
||||||
|
{
|
||||||
|
if(selector.trim() === "")
|
||||||
|
throw new Error(errors.parserSelectorsIsEmpty);
|
||||||
|
else
|
||||||
|
this._rowsSelector=selector.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
get datasSelector() : string
|
||||||
|
{
|
||||||
|
return this._datasSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
set datasSelector(selector: string)
|
||||||
|
{
|
||||||
|
if(selector.trim() === "")
|
||||||
|
throw new Error(errors.parserSelectorsIsEmpty);
|
||||||
|
else
|
||||||
|
this._datasSelector=selector.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
get rowsSelector() : string
|
||||||
|
{
|
||||||
|
return this._rowsSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parseResults() : ParseResults|undefined
|
||||||
|
{
|
||||||
|
return this._parseResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async parse(): Promise<any>
|
||||||
|
{
|
||||||
|
const parser=this;
|
||||||
|
const realFields: string[]=[], datas: {[index: string]:string}[]=[], parseErrors: ParseErrors[]=[];
|
||||||
|
|
||||||
|
// Document HTML distant ?
|
||||||
|
if(parser._datasRemoteSource.url !== "")
|
||||||
|
{
|
||||||
|
const settings: {}=parser._datasRemoteSource.getFetchSettings();
|
||||||
|
const response=await fetch(parser._datasRemoteSource.url, settings);
|
||||||
|
if (! response.ok)
|
||||||
|
throw new Error(errors.parserRemoteFail);
|
||||||
|
const responseHTML=await response.text();
|
||||||
|
const parserDOM=new DOMParser();
|
||||||
|
this._document2Parse=parserDOM.parseFromString(responseHTML, "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les noms des champs qui doivent avoir été fournis
|
||||||
|
// Ils ne peuvent être une chaîne vide ou en doublon
|
||||||
|
const fields=this._document2Parse.querySelectorAll(this._fieldsSelector);
|
||||||
|
if(fields.length === 0)
|
||||||
|
throw new Error(errors.parserElementsNotFound+this._fieldsSelector);
|
||||||
|
for(let i=0; i < fields.length; i++)
|
||||||
|
{
|
||||||
|
let checkField=(fields[i].textContent+"").trim(); // ajout de "" pour éviter erreur "TS2531: Object is possibly 'null'"
|
||||||
|
if(checkField !== "" && realFields.indexOf(checkField) === -1)
|
||||||
|
realFields.push(checkField);
|
||||||
|
else
|
||||||
|
console.error(errors.parserFieldNameFail); // lancer une exception, car risque de bugs par la suite ???
|
||||||
|
}
|
||||||
|
if(realFields.length === 0)
|
||||||
|
throw new Error(errors.parserFieldsNotFound);
|
||||||
|
|
||||||
|
// Puis récupération des données.
|
||||||
|
// Il peut n'y avoir aucune ligne si aucune donnée trouvée.
|
||||||
|
const rows=this._document2Parse.querySelectorAll(this._rowsSelector);
|
||||||
|
let datasElts;
|
||||||
|
for(let i=0; i < rows.length; i++)
|
||||||
|
{
|
||||||
|
// Les nombre de données par ligne ne devrait pas être différent du nombre de champs.
|
||||||
|
datasElts=rows[i].querySelectorAll(this._datasSelector);
|
||||||
|
if(datasElts.length !== realFields.length)
|
||||||
|
parseErrors.push({ row:i, message:errors.parserMeetErrors});
|
||||||
|
// Les chaînes vides sont par contre acceptées ici.
|
||||||
|
let dataObject: {[index: string]: string} = {}
|
||||||
|
for(let j=0; j < datasElts.length && j < realFields.length; j++)
|
||||||
|
dataObject[realFields[j]]=datasElts[j].textContent+"";
|
||||||
|
// Mais les lignes complétement vides doivent être ignorées.
|
||||||
|
if(Object.keys(dataObject).length !== 0)
|
||||||
|
datas.push(dataObject)
|
||||||
|
else
|
||||||
|
parseErrors.push({ row:i, message:errors.parserMeetErrors});
|
||||||
|
}
|
||||||
|
parser._parseResults =
|
||||||
|
{
|
||||||
|
datas: datas,
|
||||||
|
errors: parseErrors,
|
||||||
|
fields: realFields,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
1
tests/datas/datas.html
Normal file
1
tests/datas/datas.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<html><head></head><body><table><thead><tr><th>Champ 1</th><th>Champ 2</th></tr></thead></table></body></html>
|
@ -13,4 +13,7 @@ module.exports =
|
|||||||
lastLineForPageSelection1:"<td>100</td><td>Fermium</td><td>Fm</td><td>Actinide</td><td>Inexistant</td>",
|
lastLineForPageSelection1:"<td>100</td><td>Fermium</td><td>Fm</td><td>Actinide</td><td>Inexistant</td>",
|
||||||
firstLineForPageSelection2:"<td>101</td><td>Mendélévium</td><td>Md</td><td>Actinide</td><td>Inexistant</td>",
|
firstLineForPageSelection2:"<td>101</td><td>Mendélévium</td><td>Md</td><td>Actinide</td><td>Inexistant</td>",
|
||||||
lastLineForPageSelection2:"<td>118</td><td>Oganesson</td><td>Og</td><td>Indéfinie</td><td>Inexistant</td>",
|
lastLineForPageSelection2:"<td>118</td><td>Oganesson</td><td>Og</td><td>Indéfinie</td><td>Inexistant</td>",
|
||||||
|
htmlParserDatas: "<table><thead><tr><th> Z (numéro atomique)</th><th> Élément </th><th>Symbole</th><th>Famille</th><th>Mots-clés</th></tr></thead><tbody><tr><td>1</td><td>Hydrogène</td><td>H</td><td>Non-métal</td><td></td></tr><tr><td>2</td><td>Hélium</td><td>He</td><td>Gaz noble</td><td>Mot-clé2</td></tr><tr><td>3</td><td>Lithium</td><td>Li</td><td>Métal alcalin</td><td>Mot-clé2,Mot-clé1</td></tr><tr><td>4</td><td>Béryllium</td><td>Be</td><td>Métal alcalino-terreux</td><td>Mot-clé3</td></tr></tbody></table>",
|
||||||
|
htmlParserDatasBadFields: "<table><thead><tr><th> Z (numéro atomique)</th><th> Élément </th><th>Symbole</th><th>Famille </th><th>Mots-clés</th></tr></thead><tbody><tr><td>1</td><td>Hydrogène</td><td>H</td><td>Non-métal</td></tr><tr><td>2</td><td>Hélium</td><td>He</td><td>Gaz noble</td><td>Mot-clé2</td><td>je suis le méchamp</td></tr><tr><td>3</td><td>Lithium</td><td>Li</td><td>Métal alcalin</td><td>Mot-clé2,Mot-clé1</td></tr><tr><td>4</td><td>Béryllium</td><td>Be</td><td>Métal alcalino-terreux</td><td>Mot-clé3</td></tr></tbody></table>",
|
||||||
|
htmlParserDatasEmptyLine: "<table><thead><tr><th> Z (numéro atomique)</th><th> Élément </th><th>Symbole</th><th>Famille</th><th>Mots-clés</th></tr></thead><tbody><tr><td>1</td><td>Hydrogène</td><td>H</td><td>Non-métal</td><td></td></tr><tr><td>2</td><td>Hélium</td><td>He</td><td>Gaz noble</td><td>Mot-clé2</td></tr><tr><td>3</td><td>Lithium</td><td>Li</td><td>Métal alcalin</td><td>Mot-clé2,Mot-clé1</td></tr><tr></tr><tr><td>4</td><td>Béryllium</td><td>Be</td><td>Métal alcalino-terreux</td><td>Mot-clé3</td></tr></tbody></table>",
|
||||||
}
|
}
|
118
tests/parserForHTMLSpec.ts
Normal file
118
tests/parserForHTMLSpec.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { RemoteSource } from "../src/freeDatas2HTMLInterfaces";
|
||||||
|
import { ParserForHTML as Parser } from "../src/freeDatas2HTMLParserForHTML";
|
||||||
|
const errors=require("../src/errors.js");
|
||||||
|
const fixtures=require("./fixtures.js");
|
||||||
|
|
||||||
|
describe("Tests du parseur HTML", () =>
|
||||||
|
{
|
||||||
|
let parser: Parser, datasElt: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach( () =>
|
||||||
|
{
|
||||||
|
parser=new Parser();
|
||||||
|
document.body.insertAdjacentHTML('afterbegin', "<div id='datas'></div>");
|
||||||
|
datasElt=document.getElementById("datas");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach( () =>
|
||||||
|
{
|
||||||
|
document.body.removeChild(document.getElementById("datas"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Doit avoir créé une instance du parseur.", () =>
|
||||||
|
{
|
||||||
|
expect(parser).toBeInstanceOf(Parser);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Ne doit pas accepter des valeurs vides pour les sélecteurs CSS.", () =>
|
||||||
|
{
|
||||||
|
expect(() => { return parser.fieldsSelector=""; }).toThrowError(errors.parserSelectorsIsEmpty);
|
||||||
|
expect(() => { return parser.rowsSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty);
|
||||||
|
expect(() => { return parser.datasSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Accès à des données distantes.", () =>
|
||||||
|
{
|
||||||
|
it("Doit générer une erreur, si l'accès aux données distantes ne fonctionne pas.", async () =>
|
||||||
|
{
|
||||||
|
parser.setRemoteSource({ url:"http://localhost:9876/datas/datas.htm" }); // une seule lettre vous manque...
|
||||||
|
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserRemoteFail));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Si le parseur est appelé avec une url correcte, le DOM du document distant doit être enregistré.", async () =>
|
||||||
|
{
|
||||||
|
parser.setRemoteSource({ url:"http://localhost:9876/datas/datas.html" });
|
||||||
|
await parser.parse();
|
||||||
|
const parserDOM=new DOMParser();
|
||||||
|
const htmlString="<html><head></head><body><table><thead><tr><th>Champ 1</th><th>Champ 2</th></tr></thead></table></body></html>";
|
||||||
|
const doc=parserDOM.parseFromString(htmlString, "text/html");
|
||||||
|
expect(parser.document2Parse).toEqual(doc);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Extraction des données du document.", () =>
|
||||||
|
{
|
||||||
|
it("Doit générer une erreur si aucun élément n'est trouvé dans le document pour les sélecteurs CSS fournis pour les noms de champs.", async () =>
|
||||||
|
{
|
||||||
|
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserElementsNotFound+"table > thead > tr > th")); // valeurs par défaut, mais rien dans le DOM
|
||||||
|
datasElt.innerHTML=fixtures.htmlParserDatas;
|
||||||
|
await expectAsync(parser.parse()).not.toBeRejectedWith(new Error(errors.parserElementsNotFound+"table > thead > tr > th"));
|
||||||
|
parser.fieldsSelector="ul#dontExist > li"; // n'existe pas dans ce qui a été injecté
|
||||||
|
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserElementsNotFound+"ul#dontExist > li"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Les noms de champ vides ou en doublon doivent être ignorés.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML="<ul><li>Champ1</li><li> </li><li>Champ2</li><li>Champ2</li></ul>";
|
||||||
|
parser.fieldsSelector="ul > li";
|
||||||
|
await parser.parse();
|
||||||
|
expect(parser.parseResults.fields).toEqual(["Champ1","Champ2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Au moins un nom de champ valide doit avoir été trouvé.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML="<ul><li></li><li> </li></ul>";
|
||||||
|
parser.fieldsSelector="ul > li";
|
||||||
|
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserFieldsNotFound));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Les espaces entourant les noms de champs doivent être supprimés.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML=fixtures.htmlParserDatas;
|
||||||
|
await parser.parse();
|
||||||
|
expect(parser.parseResults.fields).toEqual(["Z (numéro atomique)","Élément", "Symbole", "Famille", "Mots-clés"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Si des champs en trop sont trouvés dans une ligne de données, ils doivent être ignorés. Idem pour les champs absents. Ces anomalies doivent être reportées.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML=fixtures.htmlParserDatasBadFields; // un "td" manquant en ligne 0 et un en trop en ligne 1
|
||||||
|
await parser.parse();
|
||||||
|
expect(parser.parseResults.datas).toEqual([{"Z (numéro atomique)":"1","Élément":"Hydrogène", Symbole:"H", Famille:"Non-métal" }, {"Z (numéro atomique)":"2","Élément":"Hélium", Symbole:"He", Famille:"Gaz noble", "Mots-clés":"Mot-clé2" }, {"Z (numéro atomique)":"3","Élément":"Lithium", Symbole:"Li", Famille:"Métal alcalin", "Mots-clés":"Mot-clé2,Mot-clé1" }, {"Z (numéro atomique)":"4","Élément":"Béryllium", Symbole:"Be", Famille:"Métal alcalino-terreux", "Mots-clés":"Mot-clé3" }]);
|
||||||
|
expect(parser.parseResults.errors[0]).toEqual({row:0,message:errors.parserMeetErrors});
|
||||||
|
expect(parser.parseResults.errors[1]).toEqual({row:1,message:errors.parserMeetErrors});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Le fait qu'aucune donnée ne soit trouvée ne doit pas générer une erreur.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML="<table><thead><tr><th>Champ1</th><th>Champ2</th></tr></thead></table>"
|
||||||
|
await expectAsync(parser.parse()).toBeResolved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Une ligne n'ayant aucune donnée sera ignorée et l'erreur reportée. Les valeurs vides sont par contre acceptées.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML=fixtures.htmlParserDatasEmptyLine; // avant dernière ligne sans "td", "Mots-clé" vide pour la 1ière ligne.
|
||||||
|
await parser.parse();
|
||||||
|
expect(parser.parseResults.datas).toEqual([{"Z (numéro atomique)":"1","Élément":"Hydrogène", Symbole:"H", Famille:"Non-métal", "Mots-clés":"" }, {"Z (numéro atomique)":"2","Élément":"Hélium", Symbole:"He", Famille:"Gaz noble", "Mots-clés":"Mot-clé2" }, {"Z (numéro atomique)":"3","Élément":"Lithium", Symbole:"Li", Famille:"Métal alcalin", "Mots-clés":"Mot-clé2,Mot-clé1" }, {"Z (numéro atomique)":"4","Élément":"Béryllium", Symbole:"Be", Famille:"Métal alcalino-terreux", "Mots-clés":"Mot-clé3" }]);
|
||||||
|
expect(parser.parseResults.errors[0]).toEqual({row:3,message:errors.parserMeetErrors});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Si le code HTML fourni est ok, aucune erreur de lecture ne doit être reportée.", async () =>
|
||||||
|
{
|
||||||
|
datasElt.innerHTML=fixtures.htmlParserDatas;
|
||||||
|
await parser.parse();
|
||||||
|
expect(parser.parseResults.datas).toEqual([{"Z (numéro atomique)":"1","Élément":"Hydrogène", Symbole:"H", Famille:"Non-métal", "Mots-clés":"" }, {"Z (numéro atomique)":"2","Élément":"Hélium", Symbole:"He", Famille:"Gaz noble", "Mots-clés":"Mot-clé2" }, {"Z (numéro atomique)":"3","Élément":"Lithium", Symbole:"Li", Famille:"Métal alcalin", "Mots-clés":"Mot-clé2,Mot-clé1" }, {"Z (numéro atomique)":"4","Élément":"Béryllium", Symbole:"Be", Famille:"Métal alcalino-terreux", "Mots-clés":"Mot-clé3" }]);
|
||||||
|
expect(parser.parseResults.errors.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -6,6 +6,7 @@ module.exports =
|
|||||||
entry:
|
entry:
|
||||||
{
|
{
|
||||||
exampleWithCSV: "./src/exampleWithCSV.ts",
|
exampleWithCSV: "./src/exampleWithCSV.ts",
|
||||||
|
exampleWithHTML: "./src/exampleWithHTML.ts",
|
||||||
exampleWithJSON: "./src/exampleWithJSON.ts",
|
exampleWithJSON: "./src/exampleWithJSON.ts",
|
||||||
},
|
},
|
||||||
output:
|
output:
|
||||||
|
Loading…
Reference in New Issue
Block a user