Relecture code et tests du parseur HTML.

This commit is contained in:
Fabrice PENHOËT 2021-10-19 11:46:35 +02:00
parent df2658686d
commit 036c3b3400
2 changed files with 32 additions and 24 deletions

View File

@ -11,7 +11,6 @@ export class ParserForHTML implements Parsers
private _rowsSelector: string="table > tbody > tr"; private _rowsSelector: string="table > tbody > tr";
private _datasSelector: string="tr > td"; private _datasSelector: string="tr > td";
// L'instance d'une autre classe que RemoteSource peut être passée au constructeur
constructor(datasRemoteSource?: RemoteSources) constructor(datasRemoteSource?: RemoteSources)
{ {
if(datasRemoteSource !== undefined) if(datasRemoteSource !== undefined)
@ -81,14 +80,13 @@ export class ParserForHTML implements Parsers
public async parse(): Promise<any> public async parse(): Promise<any>
{ {
const parser=this;
const realFields: string[]=[], datas: {[index: string]:string}[]=[], parseErrors: ParseErrors[]=[]; const realFields: string[]=[], datas: {[index: string]:string}[]=[], parseErrors: ParseErrors[]=[];
// Document HTML distant ? // Document HTML distant ?
if(parser._datasRemoteSource.url !== "") if(this._datasRemoteSource.url !== "")
{ {
const settings: {}=parser._datasRemoteSource.getFetchSettings(); const settings: {}=this._datasRemoteSource.getFetchSettings();
const response=await fetch(parser._datasRemoteSource.url, settings); const response=await fetch(this._datasRemoteSource.url, settings);
if (! response.ok) if (! response.ok)
throw new Error(errors.parserRemoteFail); throw new Error(errors.parserRemoteFail);
const responseHTML=await response.text(); const responseHTML=await response.text();
@ -96,8 +94,7 @@ export class ParserForHTML implements Parsers
this._document2Parse=parserDOM.parseFromString(responseHTML, "text/html"); this._document2Parse=parserDOM.parseFromString(responseHTML, "text/html");
} }
// Récupérer les noms des champs qui doivent avoir été fournis // Récupération du noms de champs
// Ils ne peuvent être une chaîne vide ou en doublon
const fields=this._document2Parse.querySelectorAll(this._fieldsSelector); const fields=this._document2Parse.querySelectorAll(this._fieldsSelector);
if(fields.length === 0) if(fields.length === 0)
throw new Error(errors.parserElementsNotFound+this._fieldsSelector); throw new Error(errors.parserElementsNotFound+this._fieldsSelector);
@ -107,13 +104,12 @@ export class ParserForHTML implements Parsers
if(checkField !== "" && realFields.indexOf(checkField) === -1) if(checkField !== "" && realFields.indexOf(checkField) === -1)
realFields.push(checkField); realFields.push(checkField);
else else
console.error(errors.parserFieldNameFail); // lancer une exception, car risque de bugs par la suite ??? parseErrors.push({ row:-1, message: errors.parserFieldNameFail});
} }
if(realFields.length === 0) if(realFields.length === 0)
throw new Error(errors.parserFieldsNotFound); throw new Error(errors.parserFieldsNotFound);
// Puis récupération des données. // Puis récupération des éventuelles données.
// Il peut n'y avoir aucune ligne si aucune donnée trouvée.
const rows=this._document2Parse.querySelectorAll(this._rowsSelector); const rows=this._document2Parse.querySelectorAll(this._rowsSelector);
let datasElts; let datasElts;
for(let i=0; i < rows.length; i++) for(let i=0; i < rows.length; i++)
@ -121,7 +117,7 @@ export class ParserForHTML implements Parsers
// Les nombre de données par ligne ne devrait pas être différent du nombre de champs. // Les nombre de données par ligne ne devrait pas être différent du nombre de champs.
datasElts=rows[i].querySelectorAll(this._datasSelector); datasElts=rows[i].querySelectorAll(this._datasSelector);
if(datasElts.length !== realFields.length) if(datasElts.length !== realFields.length)
parseErrors.push({ row:i, message:errors.parserMeetErrors}); parseErrors.push({ row:i, message:errors.parserNumberOfFieldsFail});
// Les chaînes vides sont par contre acceptées ici. // Les chaînes vides sont par contre acceptées ici.
let dataObject: {[index: string]: string} = {} let dataObject: {[index: string]: string} = {}
for(let j=0; j < datasElts.length && j < realFields.length; j++) for(let j=0; j < datasElts.length && j < realFields.length; j++)
@ -130,9 +126,9 @@ export class ParserForHTML implements Parsers
if(Object.keys(dataObject).length !== 0) if(Object.keys(dataObject).length !== 0)
datas.push(dataObject) datas.push(dataObject)
else else
parseErrors.push({ row:i, message:errors.parserMeetErrors}); parseErrors.push({ row:i, message: errors.parserLineWithoutDatas});
} }
parser._parseResults = this._parseResults =
{ {
datas: datas, datas: datas,
errors: parseErrors, errors: parseErrors,

View File

@ -21,9 +21,10 @@ describe("Tests du parseur HTML", () =>
it("Doit avoir créé une instance du parseur.", () => it("Doit avoir créé une instance du parseur.", () =>
{ {
expect(parser).toBeInstanceOf(Parser); expect(parser).toBeInstanceOf(Parser);
expect(parser.datasRemoteSource.url).toEqual("");
}); });
it("Ne doit pas accepter des valeurs vides pour les sélecteurs CSS.", () => it("Ne doit pas accepter de valeurs vides pour les sélecteurs CSS.", () =>
{ {
expect(() => { return parser.fieldsSelector=""; }).toThrowError(errors.parserSelectorsIsEmpty); expect(() => { return parser.fieldsSelector=""; }).toThrowError(errors.parserSelectorsIsEmpty);
expect(() => { return parser.rowsSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty); expect(() => { return parser.rowsSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty);
@ -47,30 +48,42 @@ describe("Tests du parseur HTML", () =>
const doc=parserDOM.parseFromString(htmlString, "text/html"); const doc=parserDOM.parseFromString(htmlString, "text/html");
expect(parser.document2Parse).toEqual(doc); expect(parser.document2Parse).toEqual(doc);
}); });
it("Si des options de connexion distante sont fournies, elles doivent être utilisées.", async () =>
{
spyOn(window, "fetch").and.callThrough();
parser.setRemoteSource({ url:"http://localhost:9876/datas/datas.html", headers: [{ key:"token", value:"1234" }, { key:"userName", value:"Toto" }], withCredentials:true });
await parser.parse();
const headers=new Headers();
headers.append("token", "1234");
headers.append("userName", "Toto");
expect(window.fetch).toHaveBeenCalledWith("http://localhost:9876/datas/datas.html", { method: "GET", headers: headers, credentials: "include" });
});
}); });
describe("Extraction des données du document.", () => 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 () => 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 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; datasElt.innerHTML=fixtures.htmlParserDatas;
await expectAsync(parser.parse()).not.toBeRejectedWith(new Error(errors.parserElementsNotFound+"table > thead > tr > th")); 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é 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")); 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 () => it("Les noms de champ vides ou en doublon doivent être ignorés et les erreurs reportées.", async () =>
{ {
datasElt.innerHTML="<ul><li>Champ1</li><li> </li><li>Champ2</li><li>Champ2</li></ul>"; datasElt.innerHTML="<ul><li>Champ1</li><li> </li><li>Champ2</li><li>Champ2</li></ul>";
parser.fieldsSelector="ul > li"; parser.fieldsSelector="ul > li";
await parser.parse(); await parser.parse();
expect(parser.parseResults.fields).toEqual(["Champ1","Champ2"]); expect(parser.parseResults.fields).toEqual(["Champ1","Champ2"]);
expect(parser.parseResults.errors).toEqual([{ row:-1, message: errors.parserFieldNameFail}, { row:-1, message: errors.parserFieldNameFail}]);
}); });
it("Au moins un nom de champ valide doit avoir été trouvé.", async () => it("Doit générer une erreur, si aucun nom de champ valide n'est trouvé dans le document.", async () =>
{ {
datasElt.innerHTML="<ul><li></li><li> </li></ul>"; datasElt.innerHTML="<ul><li> </li><li> </li></ul>";
parser.fieldsSelector="ul > li"; parser.fieldsSelector="ul > li";
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserFieldsNotFound)); await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserFieldsNotFound));
}); });
@ -87,13 +100,13 @@ describe("Tests du parseur HTML", () =>
datasElt.innerHTML=fixtures.htmlParserDatasBadFields; // un "td" manquant en ligne 0 et un en trop en ligne 1 datasElt.innerHTML=fixtures.htmlParserDatasBadFields; // un "td" manquant en ligne 0 et un en trop en ligne 1
await parser.parse(); 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.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[0]).toEqual({row:0,message:errors.parserNumberOfFieldsFail});
expect(parser.parseResults.errors[1]).toEqual({row:1,message:errors.parserMeetErrors}); expect(parser.parseResults.errors[1]).toEqual({row:1,message:errors.parserNumberOfFieldsFail});
}); });
it("Le fait qu'aucune donnée ne soit trouvée ne doit pas générer une erreur.", async () => 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>" datasElt.innerHTML="<table><thead><tr><th>Champ1</th><th>Champ2</th></tr></thead></table>";
await expectAsync(parser.parse()).toBeResolved(); await expectAsync(parser.parse()).toBeResolved();
}); });
@ -102,7 +115,7 @@ describe("Tests du parseur HTML", () =>
datasElt.innerHTML=fixtures.htmlParserDatasEmptyLine; // avant dernière ligne sans "td", "Mots-clé" vide pour la 1ière ligne. datasElt.innerHTML=fixtures.htmlParserDatasEmptyLine; // avant dernière ligne sans "td", "Mots-clé" vide pour la 1ière ligne.
await parser.parse(); 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.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}); expect(parser.parseResults.errors[1]).toEqual({row:3,message:errors.parserLineWithoutDatas});// errors[0] = erreur nombre de champs 1ère ligne
}); });
it("Si le code HTML fourni est ok, aucune erreur de lecture ne doit être reportée.", async () => it("Si le code HTML fourni est ok, aucune erreur de lecture ne doit être reportée.", async () =>
@ -112,6 +125,5 @@ describe("Tests du parseur HTML", () =>
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.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); expect(parser.parseResults.errors.length).toEqual(0);
}); });
}); });
}); });