diff --git a/package.json b/package.json
index 4f26023588e22a926d837e55298974cfa15599d4..f612aaad6bbfd547dbbfb8103327938c54a4243e 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
     "angular-bootstrap-md": "^5.0.5",
     "angular2-chartjs": "^0.4.1",
     "core-js": "^2.4.1",
+    "file-saver": "^1.3.8",
     "he": "^1.1.1",
     "jalhyd": "file:../jalhyd/jalhyd-1.0.0.tgz",
     "ngx-md": "^3.1.1",
@@ -36,6 +37,7 @@
     "@angular/cli": "1.5.4",
     "@angular/compiler-cli": "^5.0.0",
     "@angular/language-service": "^5.0.0",
+    "@types/file-saver": "^1.3.0",
     "@types/jasmine": "~2.5.53",
     "@types/jasminewd2": "~2.0.2",
     "@types/node": "~6.0.60",
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 77966eddc04dc1fea960c0e8dfe228f04a40d6e4..53236b7a7b1386ea284e415f6e8314292264fef1 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -34,11 +34,18 @@
         <!-- ATTENTION ! pas de href="#" sous peine de rechargement de la page et réinitialisation de l'appli -->
         <a class="closebtn" (click)="closeNav()">×</a>
         <a (click)="newCalc()">{{uitextSidenavNewCalc}}</a>
+        <a (click)="loadSession()">{{uitextSidenavLoadSession}}</a>
         <a (click)="params()">{{uitextSidenavParams}}</a>
       </div>
     </div>
   </div>
 
+  <!-- chargement des calculettes -->
+  <div loadCalcDialogAnchor></div>
+
+  <!-- sauvegarde des calculettes -->
+  <div saveCalcDialogAnchor></div>
+
   <!-- règle gradée des colonnes Bootstrap -->
   <div *ngIf="ruler" class="container-fluid">
     <div class="row">
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 8f89ea11909c54281672d5c7456741564d0cb393..95e2860536191fdf34dd7513b0b79781c52a92aa 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { Component, ApplicationRef, OnInit, OnDestroy } from '@angular/core';
+import { Component, ApplicationRef, OnInit, OnDestroy, ViewChild, ComponentRef } from '@angular/core';
 //import { MdDialog } from '@angular/material';
 import { Router, ActivatedRoute } from '@angular/router';
 
@@ -16,6 +16,10 @@ import { ApplicationSetupService } from './services/app-setup/app-setup.service'
 import { HttpService } from './services/http/http.service';
 import { HelpService } from './services/help/help.service';
 import { HelpComponent } from './components/help/help.component';
+import { LoadCalcDialogAnchorDirective } from './components/load-calculator/load-calculator-anchor.directive';
+import { LoadCalculatorComponent } from './components/load-calculator/load-calculator.component';
+import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive';
+import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component';
 
 
 @Component({
@@ -26,6 +30,11 @@ import { HelpComponent } from './components/help/help.component';
 export class AppComponent implements OnInit, OnDestroy, Observer {
   private _displayErrorDialog: boolean = false;
 
+  /**
+   * liste des calculettes. Forme des objets :
+   * "title": nom de la calculette
+   * "uid": id unique du formulaire
+   */
   private _calculators: any[] = [];
 
   /**
@@ -34,6 +43,12 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
    */
   private _currentFormId: number;
 
+  @ViewChild(LoadCalcDialogAnchorDirective)
+  private loadCalcDialogAnchor: LoadCalcDialogAnchorDirective;
+
+  @ViewChild(SaveCalcDialogAnchorDirective)
+  private saveCalcDialogAnchor: SaveCalcDialogAnchorDirective;
+
   /**
    * composant actuellement affiché par l'élément <router-outlet>
    */
@@ -84,6 +99,10 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     return this.intlService.localizeText("INFO_SETUP_TITLE");
   }
 
+  private get uitextSidenavLoadSession() {
+    return "Charger une session";
+  }
+
   /**
    * abonnement au service d'erreurs
    */
@@ -147,6 +166,10 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
           }, 1);
           break;
 
+        case "saveForm":
+          this.saveForm(data["form"]);
+          break;
+
         case "closeForm":
           const form: FormulaireDefinition = data["form"];
           this.closeCalculator(form);
@@ -183,6 +206,52 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this._calculators[formIndex]["title"] = title;
   }
 
+  /**
+   * sauvegarde du/des formulaires
+   * @param form formulaire à sélectionner par défaut dans la liste
+   */
+  private saveForm(form: FormulaireDefinition) {
+    // création du dialogue de sélection des formulaires à sauver
+    const compRef: ComponentRef<SaveCalculatorComponent> = this.saveCalcDialogAnchor.createDialog();
+
+    // création de la liste des formulaires
+    let list = [];
+    for (const c of this._calculators) {
+      const uid = +c["uid"];
+      list.push({
+        "selected": uid === form.uid,
+        "title": c["title"],
+        "uid": uid
+      })
+    }
+
+    // passage de la liste, récupération d'une Promise pour traiter le résultat
+    const prom: Promise<any[]> = compRef.instance.run(list);
+    prom.then(list => {
+      let name = compRef.instance.filename;
+
+      // ajout extension ".json"
+      const re = /.+\.json/;
+      const match = re.exec(name.toLowerCase());
+      if (match === null)
+        name = name + ".json";
+
+      this.saveSession(list, name)
+    });
+  }
+
+  private saveSession(calcList: any[], filename) {
+    let elems = [];
+    for (const c of calcList)
+      if (c.selected) {
+        console.log(c.title);
+        const form: FormulaireDefinition = this.formulaireService.getFormulaireFromId(c.uid);
+        elems.push(form.JSONserialise());
+      }
+    let session = { "session": { "elements": elems } };
+    this.formulaireService.saveSession(session, filename);
+  }
+
   private closeCalculator(form: FormulaireDefinition) {
     const formId: number = form.uid;
 
@@ -274,6 +343,19 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this.toList();
   }
 
+  private loadSession() {
+    this.closeNav();
+
+    // création du dialogue de sélection des formulaires à sauver
+    const compRef: ComponentRef<LoadCalculatorComponent> = this.loadCalcDialogAnchor.createDialog();
+
+    const prom: Promise<any[]> = compRef.instance.run();
+    prom.then(list => {
+      this.formulaireService.loadSession(compRef.instance.selectedFile, compRef.instance.calculators);
+      compRef.destroy();
+    });
+  }
+
   private params() {
     this.closeNav();
     this.router.navigate(['/setup']);
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 56f5e83b5d9644fb82341936df0e3f39ea183943..e084fe592034151af1d011caaf7dd1d531a7abc8 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -49,6 +49,10 @@ import { VerticalResultElementComponent } from './components/result-element/vert
 import { LogEntryComponent } from './components/log-entry/log-entry.component';
 import { HelpService } from './services/help/help.service';
 import { HelpComponent } from './components/help/help.component';
+import { LoadCalculatorComponent } from './components/load-calculator/load-calculator.component';
+import { LoadCalcDialogAnchorDirective } from './components/load-calculator/load-calculator-anchor.directive';
+import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component';
+import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive';
 
 const appRoutes: Routes = [
   { path: 'list', component: CalculatorListComponent },
@@ -94,9 +98,12 @@ const appRoutes: Routes = [
     CalcCanvasComponent, SectionCanvasComponent,
     HorizontalResultElementComponent, VerticalResultElementComponent,
     FixedResultsComponent, VarResultsComponent,
-    HelpComponent
+    HelpComponent,
+    LoadCalculatorComponent, LoadCalcDialogAnchorDirective,
+    SaveCalculatorComponent, SaveCalcDialogAnchorDirective
   ],
   // entryComponents: [AlertDialog],
+  entryComponents: [LoadCalculatorComponent, SaveCalculatorComponent],
   providers: [ // services
     ParamService, InternationalisationService, HttpService, FormulaireService, ApplicationSetupService, HelpService
   ],
diff --git a/src/app/calculators/regime-uniforme/regime-uniforme.config.json b/src/app/calculators/regime-uniforme/regime-uniforme.config.json
index 5710ce3e1b1c62bce4c8402e11a398c034d1274c..7286a71d854c26253d79f644a1ee23f176901b13 100644
--- a/src/app/calculators/regime-uniforme/regime-uniforme.config.json
+++ b/src/app/calculators/regime-uniforme/regime-uniforme.config.json
@@ -2,7 +2,6 @@
     {
         "id": "fs_section",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "cal",
         "fields": [
             {
@@ -104,7 +103,6 @@
     {
         "id": "fs_bief",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "cal",
         "fields": [
             {
@@ -127,7 +125,6 @@
     {
         "id": "fs_hydraulique",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "cal",
         "fields": [
             {
@@ -145,7 +142,6 @@
     {
         "id": "fs_param_calc",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
diff --git a/src/app/calculators/remous/remous.config.json b/src/app/calculators/remous/remous.config.json
index f530722a3210a51e2f269a6a41748b85e3314ebf..289a274e1025d7b6fcda32c5556734efe79fba3c 100644
--- a/src/app/calculators/remous/remous.config.json
+++ b/src/app/calculators/remous/remous.config.json
@@ -2,7 +2,6 @@
     {
         "id": "fs_section",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
@@ -104,7 +103,6 @@
     {
         "id": "fs_bief",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
@@ -132,7 +130,6 @@
     {
         "id": "fs_condlim",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
@@ -155,7 +152,6 @@
     {
         "id": "fs_param_calc",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
diff --git a/src/app/calculators/section-param/section-param.config.json b/src/app/calculators/section-param/section-param.config.json
index 88aac4d949f05449c57ae25044a5931fe9b8565f..56ea1dd3cb265ab2e03c10c0fa9cd8689157168e 100644
--- a/src/app/calculators/section-param/section-param.config.json
+++ b/src/app/calculators/section-param/section-param.config.json
@@ -2,7 +2,6 @@
     {
         "id": "fs_section",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "var",
         "fields": [
             {
@@ -104,7 +103,6 @@
     {
         "id": "fs_bief",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "var",
         "fields": [
             {
@@ -127,7 +125,6 @@
     {
         "id": "fs_hydraulique",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "var",
         "fields": [
             {
@@ -145,7 +142,6 @@
     {
         "id": "fs_param_calc",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts
index 91b239baac3176ea758b8a6f94dcbc995e64013f..1a14c08d0c33fe7d7176354f881dcd9e8d6a92f9 100644
--- a/src/app/components/calculator-list/calculator-list.component.ts
+++ b/src/app/components/calculator-list/calculator-list.component.ts
@@ -6,6 +6,8 @@ import { CalculatorType, EnumEx } from "jalhyd";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
 import { ServiceFactory } from "../../services/service-factory";
 import { InternationalisationService } from '../../services/internationalisation/internationalisation.service';
+import { FormulaireParallelStructure } from "../../formulaire/definition/concrete/form-parallel-structures";
+import { FieldsetContainer } from "../../formulaire/fieldset-container";
 
 class ListElement {
     private _label: string;
@@ -41,6 +43,15 @@ export class CalculatorListComponent implements OnInit {
         const p: Promise<FormulaireDefinition> = ServiceFactory.instance.formulaireService.createFormulaire(t);
         p.then(f => {
             this.router.navigate(['/calculator', f.uid]);
+            return f;
+        }).then(f => {
+            // on ajoute un ouvrage après l'ouverture de la calculette "ouvrages parallèles"
+            if (f instanceof FormulaireParallelStructure)
+                for (const e of f.allFormElements)
+                    if (e instanceof FieldsetContainer) {
+                        e.addFromTemplate(0);
+                        break;
+                    }
         });
     }
 
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index baa3ce41893bea13e3479e6365f2cebdf03cddb8..240f408b9337099a717173d56df4b9632e1e30ca 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -73,8 +73,8 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     }
 
     public ngAfterViewInit() {
+        this.onFielsetListChange();
         this._fieldsetComponents.changes.subscribe(_ => this.onFielsetListChange());
-        this.addStructure();
     }
 
     /*
diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html
index 72737bb6f2863c6d65b70550b0dbbedd46bdc785..3eda7ef8cde3bea94aeb91ec360181840c986497 100644
--- a/src/app/components/generic-calculator/calculator.component.html
+++ b/src/app/components/generic-calculator/calculator.component.html
@@ -10,6 +10,11 @@
         <i *ngIf="enableHelpButton" class="fa fa-question-circle fa-3x" style="color:blue" (click)="openHelp()"></i>
     </div>
 
+    <div class="col-1 px-0 mx-0">
+        <!-- bouton de sauvegarde -->
+        <i class="fa fa-save fa-3x" (click)="saveCalculator()"></i>
+    </div>
+
     <div class="col-1 px-0 mx-0">
         <!-- bouton de fermeture -->
         <!-- <button type="button" id="close-button" class="btn btn-primary float-right black" (click)="confirmModal.show()">×</button> -->
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 70abce73479b75d8b8c8ae41db8334ffc6866606..b7b8a07fc9487b2738c8179cf7ee0d1090eaf1bc 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -371,4 +371,8 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     private openHelp() {
         this._formulaire.requestHelp();
     }
+
+    private saveCalculator() {
+        this.formulaireService.saveForm(this._formulaire);
+    }
 }
diff --git a/src/app/components/load-calculator/load-calculator-anchor.directive.ts b/src/app/components/load-calculator/load-calculator-anchor.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f270813f12348a6a9b894f9ba5180d47b73f375f
--- /dev/null
+++ b/src/app/components/load-calculator/load-calculator-anchor.directive.ts
@@ -0,0 +1,27 @@
+import { Directive, ComponentFactoryResolver, ComponentFactory, ComponentRef } from '@angular/core';
+
+import { ViewContainerRef } from '@angular/core';
+import { LoadCalculatorComponent } from './load-calculator.component';
+
+@Directive({
+    selector: '[loadCalcDialogAnchor]'
+})
+export class LoadCalcDialogAnchorDirective {
+    constructor(
+        private viewContainer: ViewContainerRef,
+        private componentFactoryResolver: ComponentFactoryResolver
+    ) { }
+
+    public createDialog(): ComponentRef<LoadCalculatorComponent> {
+        this.viewContainer.clear();
+
+        let compFactory: ComponentFactory<LoadCalculatorComponent> = this.componentFactoryResolver.resolveComponentFactory(LoadCalculatorComponent);
+        let compRef: ComponentRef<LoadCalculatorComponent> = this.viewContainer.createComponent(compFactory);
+
+        // compRef.instance.confirmResult.subscribe(() => {
+        // compRef.destroy();
+        // });
+
+        return compRef;
+    }
+}
diff --git a/src/app/components/load-calculator/load-calculator.component.html b/src/app/components/load-calculator/load-calculator.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..b596cadbf55f04ffd5ee4cf376c706549d00654d
--- /dev/null
+++ b/src/app/components/load-calculator/load-calculator.component.html
@@ -0,0 +1,24 @@
+<div mdbModal #loadDialog="mdb-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" [config]="{backdrop: false, ignoreBackdropClick: true,show:true}">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4>
+            </div>
+            <div class="modal-body">
+                <div>
+                    <input type="file" #fileSelector multiple="false" accept="*.json" (change)="onFileSelect()">
+                </div>
+                <div *ngFor="let c of _calculators">
+                    <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}}
+                </div>
+                <button *ngIf="showSelectButtons" type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button>
+                <button *ngIf="showSelectButtons" type="button" class="btn btn-mdb-color waves-light" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-danger relative waves-light" (click)="loadDialog.hide();cancelLoad()" mdbRippleRadius>{{uitextCancel}}</button>
+                <button type="button" class="btn btn-success waves-light" [disabled]="disableLoadButton" (click)="loadDialog.hide();confirmLoad()"
+                    mdbRippleRadius>{{uitextLoad}}</button>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/components/load-calculator/load-calculator.component.ts b/src/app/components/load-calculator/load-calculator.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c1a3f8b710f1e50228323e7a7c9b8457f1f8a0c7
--- /dev/null
+++ b/src/app/components/load-calculator/load-calculator.component.ts
@@ -0,0 +1,161 @@
+import { Component, EventEmitter, ViewChild } from "@angular/core";
+
+import { ServiceFactory } from "../../services/service-factory";
+import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+
+@Component({
+    selector: 'load-calc',
+    templateUrl: "./load-calculator.component.html"
+})
+export class LoadCalculatorComponent {
+    @ViewChild('fileSelector') fileSelector;
+
+    private _selectFile: File;
+
+    /**
+     * liste des calculettes affichées. Forme des objets :
+     * "title": nom de la calculette
+     * "selected": flag de sélection pour la sauvegarde
+     */
+    private _calculators: any[];
+
+    /**
+     * événement émis lors du clic sur "annuler"/"charger"
+     * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde
+     */
+    private _confirmResult = new EventEmitter();
+
+    // services
+    private intlService: InternationalisationService;
+
+    constructor() {
+        this.intlService = ServiceFactory.instance.internationalisationService;
+    }
+
+    private get uitextDialogTitle() {
+        return "Charger des calculettes";
+    }
+
+    private get uitextCancel() {
+        // return this.intlService.localizeText("INFO_OPTION_NO");
+        return "Annuler";
+    }
+
+    private get uitextLoad() {
+        // return this.intlService.localizeText("INFO_OPTION_NO");
+        return "Charger";
+    }
+
+    private get uitextSelectAll() {
+        return "Toutes";
+    }
+
+    private get uitextDeselectAll() {
+        return "Aucune";
+    }
+
+    /**
+     * calcule l'état du bouton charger
+     */
+    private get disableLoadButton() {
+        // pas de fichier sélectionné -> bouton grisé
+        if (this._selectFile === undefined)
+            return true;
+
+        // au moins une calculette sélectionnée -> dégrisé
+        if (this._calculators !== undefined)
+            for (const c of this._calculators)
+                if (c.selected)
+                    return false;
+
+        // grisé sinon
+        return true;
+    }
+
+    public run(): Promise<any[]> {
+        // promise de gestion de la confirmation/annulation de la sauvegarde
+        return new Promise((resolve, reject) => {
+            this._confirmResult.subscribe((confirm) => {
+                if (confirm) {
+                    resolve(this._calculators);
+                } else {
+                    reject();
+                }
+            });
+        });
+    }
+
+    private get showSelectButtons(): boolean {
+        return this._calculators && this._calculators.length != 0;
+    }
+
+    private getSelectedFile(): File {
+        const files: { [key: string]: File } = this.fileSelector.nativeElement.files;
+        for (const key in files)
+            if (!isNaN(parseInt(key)))
+                return files[key];
+        return undefined;
+    }
+
+    private onFileSelect() {
+        const formService = ServiceFactory.instance.formulaireService;
+        this._selectFile = this.getSelectedFile();
+        if (this._selectFile !== undefined)
+            formService.calculatorInfosFromSessionFile(this._selectFile).then(
+                calcInfos => {
+                    this._calculators = calcInfos;
+                    for (const n of this._calculators)
+                        n["selected"] = true;
+                });
+    }
+
+    public get selectedFile(): File {
+        return this._selectFile;
+    }
+
+    public get calculators(): any[] {
+        return this._calculators;
+    }
+
+    private isSelected(c: any) {
+        return c.selected ? "checked" : undefined;
+    }
+
+    private onCheckCalc(event: any) {
+        for (const c of this._calculators)
+            if (c.uid == +event.target.value) {
+                c.selected = event.target.checked;
+                break;
+            }
+    }
+
+    private selectAll() {
+        for (const c of this._calculators)
+            c.selected = true;
+    }
+
+    private deselectAll() {
+        for (const c of this._calculators)
+            c.selected = false;
+    }
+
+    private set confirmed(b: boolean) {
+        setTimeout(() => {
+            this._confirmResult.next(b);
+        }, 0);
+    }
+
+    /**
+     * appelé quand on clique sur annuler
+     */
+    private cancelLoad() {
+        this.confirmed = false;
+    }
+
+    /**
+     * appelé quand on clique sur charger
+     */
+    private confirmLoad() {
+        this.confirmed = true;
+    }
+}
diff --git a/src/app/components/remous-results/remous-results.component.ts b/src/app/components/remous-results/remous-results.component.ts
index 5f6f261c0fb4c717f29c249e98ef0bde9326b023..07bbca115f0eb052f226c231adda47df5729d129 100644
--- a/src/app/components/remous-results/remous-results.component.ts
+++ b/src/app/components/remous-results/remous-results.component.ts
@@ -225,7 +225,8 @@ export class RemousResultsComponent {
         // le dernier dataset de la liste datasets est dessiné en 1er
 
         this._remousResults.update();
-        this.varResultsComponent.results = this._remousResults.varResults;
+        if (this.varResultsComponent)
+            this.varResultsComponent.results = this._remousResults.varResults;
 
         const penteFond: number = this._remousResults.penteFond;
 
diff --git a/src/app/components/save-calculator/save-calculator-anchor.directive.ts b/src/app/components/save-calculator/save-calculator-anchor.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4317ff3ac192d721ba1bbdc97ff3a719f1384848
--- /dev/null
+++ b/src/app/components/save-calculator/save-calculator-anchor.directive.ts
@@ -0,0 +1,27 @@
+import { Directive, ComponentFactoryResolver, ComponentFactory, ComponentRef } from '@angular/core';
+
+import { ViewContainerRef } from '@angular/core';
+import { SaveCalculatorComponent } from './save-calculator.component';
+
+@Directive({
+    selector: '[saveCalcDialogAnchor]'
+})
+export class SaveCalcDialogAnchorDirective {
+    constructor(
+        private viewContainer: ViewContainerRef,
+        private componentFactoryResolver: ComponentFactoryResolver
+    ) { }
+
+    public createDialog(): ComponentRef<SaveCalculatorComponent> {
+        this.viewContainer.clear();
+
+        let compFactory: ComponentFactory<SaveCalculatorComponent> = this.componentFactoryResolver.resolveComponentFactory(SaveCalculatorComponent);
+        let compRef: ComponentRef<SaveCalculatorComponent> = this.viewContainer.createComponent(compFactory);
+
+        // dialogComponentRef.instance.close.subscribe(() => {
+        //     dialogComponentRef.destroy();
+        // });
+
+        return compRef;
+    }
+}
diff --git a/src/app/components/save-calculator/save-calculator.component.html b/src/app/components/save-calculator/save-calculator.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..cab54103edd0c849cfc77b83bb377ec6a90d3bbd
--- /dev/null
+++ b/src/app/components/save-calculator/save-calculator.component.html
@@ -0,0 +1,32 @@
+<div mdbModal #saveDialog="mdb-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" [config]="{backdrop: false, ignoreBackdropClick: true,show:true}">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4>
+            </div>
+            <div class="modal-body">
+                <!-- liste de calculettes avec check -->
+                <div *ngFor="let c of _calculators">
+                    <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}}
+                </div>
+
+                <!-- bouton "tout sélectionnner" -->
+                <button type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button>
+
+                <!-- bouton "tout désélectionnner" -->
+                <button type="button" class="btn btn-mdb-color waves-light py-10" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
+
+                <!-- nom du fichier -->
+                <div class="md-form form-sm mt-4">
+                    <input mdbActive type="text" id="form1" class="form-control" [(ngModel)]="_filename">
+                    <!-- on utilise [innerHTML] pour que les codes HTML comme &nbsp; soient interprétés correctement -->
+                    <label for="form1" [innerHTML]="_filenameTitle"></label>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-danger relative waves-light" (click)="saveDialog.hide();cancelSave()" mdbRippleRadius>{{uitextCloseDialogNo}}</button>
+                <button type="button" class="btn btn-success waves-light" aria-label="Close " (click)="saveDialog.hide();confirmSave()" mdbRippleRadius>{{uitextCloseDialogYes}}</button>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/components/save-calculator/save-calculator.component.ts b/src/app/components/save-calculator/save-calculator.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e30cd2db078144cdd70ca127f3b5c29fc5861b4
--- /dev/null
+++ b/src/app/components/save-calculator/save-calculator.component.ts
@@ -0,0 +1,118 @@
+import { Component, EventEmitter } from "@angular/core";
+import { ServiceFactory } from "../../services/service-factory";
+import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+
+@Component({
+    selector: 'save-calc',
+    templateUrl: "./save-calculator.component.html"
+})
+export class SaveCalculatorComponent {
+    /**
+     * liste des calculettes affichées. Forme des objets :
+     * "title": nom de la calculette
+     * "selected": flag de sélection pour la sauvegarde
+     * "uid": id unique du formulaire
+     */
+    private _calculators: any[];
+
+    /**
+     * nom du fichier
+     */
+    private _filename: string = "session";
+
+    private _filenameTitle = "Nom de fichier";
+
+    /**
+     * événement émis lors du clic sur "annuler"/"enregister"
+     * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde
+     */
+    private confirmResult = new EventEmitter();
+
+    // services
+    private intlService: InternationalisationService;
+
+    constructor() {
+        this.intlService = ServiceFactory.instance.internationalisationService;
+    }
+
+    private get uitextDialogTitle() {
+        return "Enregister les calculettes";
+    }
+
+    private get uitextCloseDialogYes() {
+        //return this.intlService.localizeText("INFO_OPTION_YES");
+        return "Sauver";
+    }
+
+    private get uitextCloseDialogNo() {
+        // return this.intlService.localizeText("INFO_OPTION_NO");
+        return "Annuler";
+    }
+
+    private get uitextSelectAll() {
+        return "Toutes";
+    }
+
+    private get uitextDeselectAll() {
+        return "Aucune";
+    }
+
+    public get filename(): string {
+        return this._filename;
+    }
+
+    public run(calcList: any[]): Promise<any[]> {
+        this._calculators = calcList;
+
+        // promise de gestion de la confirmation/annulation de la sauvegarde
+        return new Promise((resolve, reject) => {
+            this.confirmResult.subscribe((confirm) => {
+                if (confirm) {
+                    resolve(this._calculators);
+                } else {
+                    reject();
+                }
+            });
+        });
+    }
+
+    private isSelected(c: any) {
+        return c.selected ? "checked" : undefined;
+    }
+
+    private onCheckCalc(event: any) {
+        for (const c of this._calculators)
+            if (c.uid == +event.target.value)
+                c.selected = event.target.checked;
+    }
+
+    private selectAll() {
+        for (const c of this._calculators)
+            c.selected = true;
+    }
+
+    private deselectAll() {
+        for (const c of this._calculators)
+            c.selected = false;
+    }
+
+    private set confirmed(b: boolean) {
+        setTimeout(() => {
+            this.confirmResult.next(b);
+        }, 0);
+    }
+
+    /**
+     * appelé quand on clique sur annuler
+     */
+    private cancelSave() {
+        this.confirmed = false;
+    }
+
+    /**
+     * appelé quand on clique sur sauver
+     */
+    private confirmSave() {
+        this.confirmed = true;
+    }
+}
diff --git a/src/app/formulaire/definition/concrete/form-cond-distri.ts b/src/app/formulaire/definition/concrete/form-cond-distri.ts
index b79c5d50c422c29eb3ed00096c9e2bee525c0774..c9e70be83fe041ed100de2532b4b64ff14f2303e 100644
--- a/src/app/formulaire/definition/concrete/form-cond-distri.ts
+++ b/src/app/formulaire/definition/concrete/form-cond-distri.ts
@@ -16,13 +16,17 @@ export class FormulaireConduiteDistributrice extends FormulaireDefinition {
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.ConduiteDistributrice);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.ConduiteDistributrice, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
index 0f60d8a59e1c38e7ff3b7aedc5a99d803e67a7a9..d67e9bf24054149d46a542a767382dd6d4ede2ab 100644
--- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts
+++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
@@ -21,12 +21,16 @@ export class FormulaireCourbeRemous extends FormulaireDefinition {
     private _resolveMethSelectId: string;
 
     constructor() {
-        super(CalculatorType.CourbeRemous)
+        super();
         this._formSection = new FormDefSection(this);
         this._formResult = new FormResultRemous(this);
         this._formCompute = new FormComputeCourbeRemous(this, this._formSection, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.CourbeRemous, "nodeType": ComputeNodeType.SectionPuissance };
+    }
+
     protected parseOptions(json: {}) {
         this._formSection.parseOptions(json);
 
diff --git a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
index baf385278a86ef377612817a839b6e59f92f5ffb..96d6fad59683bc4d6cc15cd03e1ce2d7eeac1c0d 100644
--- a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
+++ b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
@@ -18,13 +18,17 @@ export class FormulaireLechaptCalmon extends FormulaireDefinition implements Obs
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.LechaptCalmon)
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.LechaptCalmon, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index 66838edba6c3e52e30279889cec8c0936ebf6fe4..64b83a279ce560209b0e082d6d5ec8400af48637 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -1,9 +1,7 @@
-import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub } from "jalhyd";
+import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub, StructureType, LoiDebit, StructureProperties, Props } from "jalhyd";
 
 import { FormulaireDefinition } from "../form-definition";
 import { CalculatorResults } from "../../../results/calculator-results";
-import { ServiceFactory } from "../../../services/service-factory";
-import { ParamService } from "../../../services/param/param.service";
 import { FormDefParamToCalculate } from "../form-def-paramcalc";
 import { FormDefFixedVar } from "../form-def-fixedvar";
 import { FormDefParallelStructures } from "../form-def-parallel-structures";
@@ -33,7 +31,7 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
     private __ouvrageSelectId: string;
 
     constructor() {
-        super(CalculatorType.ParallelStructure)
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
@@ -41,15 +39,20 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         this._formCompute = new FormComputeParallelStructures(this, this._formParallelStruct, this._formResult);
     }
 
-    private createStructNub(templ: FieldsetTemplate): SessionNub {
-        const paramService: ParamService = ServiceFactory.instance.paramService;
+    protected get defaultProperties(): {} {
+        return {
+            "calcType": CalculatorType.ParallelStructure, "nodeType": ComputeNodeType.None,
+            "structureType": StructureType.SeuilRectangulaire, "loiDebit": LoiDebit.Cem88v
+        };
+    }
 
+    private createStructNub(templ: FieldsetTemplate): SessionNub {
         // valeurs par défaut de CalculatorType, ComputeNodeType, StructureType, LoiDebit
         // !!! attention !!! pour l'instant, il doit y avoir cohérence entre ces valeurs et celles du fichier de conf
         // cad valeur par défaut du 1er select (type d'ouvrage), du 2ème (loi de débit).
         // A terme, il faudrait analyser le fichier de conf (dépendances d'existence) pour déterminer automatiquement ces valeurs
         const params = FieldSet.makeDefaultProps(templ.calcTypeFromConfig, templ.defaultNodeTypeFromConfig);
-        return paramService.createSessionNub(params);
+        return this.createSessionNub(params);
     }
 
     /**
@@ -62,11 +65,7 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
     }
 
     private get parallelStructureNub(): ParallelStructure {
-        const params = {
-            "calcType": CalculatorType.ParallelStructure,
-            "nodeType": ComputeNodeType.None
-        };
-        return this.getSessionNub(params).nub as ParallelStructure;
+        return this.currentSessionNub.nub as ParallelStructure;
     }
 
     public createFieldset(parent: FormulaireNode, json: {}, data?: {}): FieldSet {
@@ -192,6 +191,28 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         return n as FieldsetContainer;
     }
 
+    /**
+     * après une modification, détermine si les propriétés sont compatibles entre elles et les ajuste au besoin
+     * @param props propriétés à vérifier
+     * @param name nom de la propriété qui vient de changer
+     * @param val nouvelle valeur de la propriété
+     */
+    private adjustProperties(props: Props, name: string, val: any) {
+        const res: Props = props.clone();
+
+        // si prop=type d'ouvrage, on prend une loi de débit compatible avec (spécifique aux ouvrages //) comme valeur par défaut
+        if (name === "structureType") {
+            if (!StructureProperties.isCompatibleValues(val, res.getPropValue("loiDebit")))
+                res.setPropValue("loiDebit", StructureProperties.findCompatibleLoiDebit(val));
+        }
+        // si prop=loi débit, on prend un type d'ouvrage compatible
+        else if (name === "loiDebit")
+            if (!StructureProperties.isCompatibleValues(res.getPropValue("structureType"), val))
+                res.setPropValue("structureType", StructureProperties.findCompatibleStructure(val));
+
+        return res;
+    }
+
     /**
      * abonnement en tant qu'observateur du FieldsetContainer
      */
@@ -229,8 +250,8 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         else if (sender instanceof FieldSet && data.action == "propertyChange") {
             switch (sender.id) {
                 case "fs_ouvrage":
-                    // this.replaceCurrentSessionNub(sender.properties);
-                    const newNub = this.replaceSessionNub(sender.sessionNub, sender.properties);
+                    const props = this.adjustProperties(sender.properties, data["name"], data["value"]);
+                    const newNub = this.replaceSessionNub(sender.sessionNub, props);
                     sender.setSessionNub(newNub);
                     this.reset();
                     break;
diff --git a/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts b/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts
index bc8d202d131162619850b424d6227acc8e3c5b70..64a5141cdb1703b8f874265783afcd5b9dfff25c 100644
--- a/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts
+++ b/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts
@@ -17,13 +17,17 @@ export class FormulairePasseBassinDimensions extends FormulaireDefinition {
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.PabDimensions);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.PabDimensions, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts b/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts
index 02ed7376af7c340c8337b49a367c75bf8f8e13e3..31d2a86390c4ffa51a2822bfbabee50653ad66d5 100644
--- a/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts
+++ b/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts
@@ -17,13 +17,17 @@ export class FormulairePasseBassinPuissance extends FormulaireDefinition {
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.PabPuissance);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.PabPuissance, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index b4f322431ebc8cb74cd06e96855f68aa19b11555..50e7aeff7b5e5cc3f8f0eca81db18a7c88969f78 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -20,7 +20,7 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.RegimeUniforme)
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formSection = new FormDefSection(this);
@@ -28,6 +28,10 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.RegimeUniforme, "nodeType": ComputeNodeType.SectionCercle };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-section-parametree.ts b/src/app/formulaire/definition/concrete/form-section-parametree.ts
index 63f636138d3f22505f32dbfd08574c8b8e252ddc..1f14c0f662e254bc5e3d81823e5e943f4362827d 100644
--- a/src/app/formulaire/definition/concrete/form-section-parametree.ts
+++ b/src/app/formulaire/definition/concrete/form-section-parametree.ts
@@ -22,13 +22,17 @@ export class FormulaireSectionParametree extends FormulaireDefinition {
     private _formSectionResult: FormResultSection;
 
     constructor() {
-        super(CalculatorType.SectionParametree);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formSection = new FormDefSection(this);
         this._formSectionResult = new FormResultSection(this, this._formSection);
         this._formCompute = new FormComputeSectionParametree(this, this._formSection, this._formSectionResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.SectionParametree, "nodeType": ComputeNodeType.SectionCercle };
+    }
+
     protected parseOptions(json: {}) {
         this._formSection.parseOptions(json);
     }
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 37fbddfd41332f178a88efcf12efc6e7b8cc5fef..1a43403007d74910984da7d1ad9fdb08868d2e56 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -20,11 +20,6 @@ import { FieldsetTemplate } from "../fieldset-template";
  * classe de base pour tous les formulaires
  */
 export abstract class FormulaireDefinition extends FormulaireNode implements Observer {
-    /**
-     * type de calculette
-     */
-    private _calcType: CalculatorType;
-
     /**
      * nom de la calculette
      */
@@ -47,15 +42,19 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
 
     protected _paramService: ParamService;
 
-    constructor(calcType: CalculatorType) {
+    constructor() {
         super(undefined);
-        this._calcType = calcType;
         this._paramService = ServiceFactory.instance.paramService;
-        this.initSessionNub();
     }
 
     public get calculatorType(): CalculatorType {
-        return this._calcType;
+        const props = this._currentSessionNub === undefined ? this.defaultProperties : this._currentSessionNub.properties.props;
+        return props["calcType"];
+    }
+
+    public get nodeType(): ComputeNodeType {
+        const props = this._currentSessionNub === undefined ? this.defaultProperties : this._currentSessionNub.properties.props;
+        return props["nodeType"];
     }
 
     public get calculatorName() {
@@ -70,15 +69,17 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return this._jsonConfig;
     }
 
-    protected initSessionNub() {
-        this._currentSessionNub = this.createSessionNub({ "calcType": this._calcType, "nodeType": ComputeNodeType.None });
+    protected abstract get defaultProperties(): {};
+
+    public initSessionNub(props?: {}) {
+        this._currentSessionNub = this.createSessionNub(props === undefined ? this.defaultProperties : props);
     }
 
     public get currentSessionNub(): SessionNub {
         return this._currentSessionNub;
     }
 
-    protected findNub(params: Props | {}) {
+    private findNub(params: Props | {}) {
         return this._paramService.findSessionNub(params);
     }
 
@@ -160,7 +161,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
 
     private parse_fieldset(json: {}) {
         const fs = this.createFieldset(this, json);
-        fs.parseConfig(json, { "parentForm": this });
+        fs.parseConfig(json);
         this.afterParseFieldset(fs);
     }
 
@@ -435,6 +436,74 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return new TopFormulaireElementIterator(this);
     }
 
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.calculatorName;
+        res["uid"] = this.uid;
+        res["props"] = this._currentSessionNub.properties.props;
+        res["elements"] = this.serialiseKids();
+        return { "form": res };
+    }
+
+    private deserialiseFieldset(elements: {}): FieldSet {
+        const res: FieldSet = this.getFormulaireNodeById(elements["id"]) as FieldSet;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseFieldsetContainer(elements: {}): FieldsetContainer {
+        const res: FieldsetContainer = this.getFormulaireNodeById(elements["id"]) as FieldsetContainer;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid form object '${element}'`);
+
+        switch (keys[0]) {
+            case "fieldset":
+                this.deserialiseFieldset(element[keys[0]]);
+                break;
+
+            case "fieldset_container":
+                this.deserialiseFieldsetContainer(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in form object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    this._calculatorName = elements[k];
+                    break;
+
+                case "uid":
+                case "props":
+                    break;
+
+                case "elements":
+                    for (const e of elements[k])
+                        this.deserialiseElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in form object`);
+            }
+        }
+    }
+
     //  interface Observer
 
     public update(sender: any, data: any) {
diff --git a/src/app/formulaire/fieldset-container.ts b/src/app/formulaire/fieldset-container.ts
index a354d2a7d9ca17355f08fd16d03ae576c932386a..1f384a140395d38231d64f8a7bb3e4a439e4a615 100644
--- a/src/app/formulaire/fieldset-container.ts
+++ b/src/app/formulaire/fieldset-container.ts
@@ -28,6 +28,16 @@ export class FieldsetContainer extends FormulaireElement {
         return this._templates[index];
     }
 
+    private getTemplateIndex(id: string): number {
+        let i = 0;
+        for (const t of this._templates) {
+            if (t.config["id"] == id)
+                return i;
+            i++;
+        }
+        throw new Error(`template ${id} non trouvé`);
+    }
+
     public addFieldset(fs: FieldSet) {
         this.fieldsets.push(fs);
     }
@@ -68,7 +78,7 @@ export class FieldsetContainer extends FormulaireElement {
      * @param templateIndex indice du template dans la liste
      * @param after insère le nouveau FieldSet après cette position, à la fin sinon
      */
-    public addFromTemplate(templateIndex: number, after?: number) {
+    public addFromTemplate(templateIndex: number, after?: number): FieldSet {
         const templ: FieldsetTemplate = this._templates[templateIndex];
 
         const inst: FieldSet = templ.instantiateTemplate(this, after);
@@ -80,6 +90,8 @@ export class FieldsetContainer extends FormulaireElement {
             "action": "newFieldset",
             "fieldset": inst
         }, this);
+
+        return inst;
     }
 
     public get fieldsets(): FieldSet[] {
@@ -105,4 +117,61 @@ export class FieldsetContainer extends FormulaireElement {
 
         super.updateLocalisation(loc);
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["elements"] = this.serialiseKids();
+        return { "fieldset_container": res };
+    }
+
+    private deserialiseFieldset(elements: {}): FieldSet {
+        const ind = this.getTemplateIndex(elements["id"]);
+        const res: FieldSet = this.addFromTemplate(ind);
+        const props = elements["props"];
+        for (const k in props)
+            if (k !== "calcType" && k !== "nodeType")
+                res.setPropValue(k, props[k]);
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid fieldset object '${element}'`);
+
+        switch (keys[0]) {
+            case "fieldset":
+                this.deserialiseFieldset(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in fieldset object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    this._confId = elements[k];
+                    break;
+
+                case "elements":
+                    for (const e of elements[k])
+                        this.deserialiseElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in form object`);
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/fieldset-template.ts b/src/app/formulaire/fieldset-template.ts
index a5d61834921dcdeb5408047af79db76c29386f53..ce3fcf353158e5e5afb82accafa73bfeda2b1d08 100644
--- a/src/app/formulaire/fieldset-template.ts
+++ b/src/app/formulaire/fieldset-template.ts
@@ -46,7 +46,7 @@ export class FieldsetTemplate {
     public instantiateTemplate(cont: FieldsetContainer, after: number): FieldSet {
         const parentForm = cont.parent as FormulaireDefinition;
         const res = parentForm.createFieldset(cont, this._jsonConfig, { "template": this, "after": after });
-        res.parseConfig(this._jsonConfig, { "parentForm": parentForm });
+        res.parseConfig(this._jsonConfig);
         parentForm.afterParseFieldset(res);
         return res;
     }
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 434cf111595689ac695eeaf9b219e070910f9c87..fefcb06c40b79cb44a24b99752aab5f3e4cf97a7 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -1,4 +1,4 @@
-import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureType, Props, SessionNub, StructureProperties, Observer } from "jalhyd";
+import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureType, Props, SessionNub, Observer } from "jalhyd";
 
 import { FormulaireElement } from "./formulaire-element";
 import { Dependency } from "./dependency/dependency";
@@ -15,11 +15,6 @@ import { FieldsetContainer } from "./fieldset-container";
 import { FormulaireNode } from "./formulaire-node";
 
 export class FieldSet extends FormulaireElement implements Observer {
-    /**
-     * formulaire parent
-     */
-    private _parentForm: FormulaireDefinition;
-
     /**
      * SessionNub associé
      */
@@ -45,12 +40,23 @@ export class FieldSet extends FormulaireElement implements Observer {
         this._props = new Props();
     }
 
+    /**
+     * formulaire parent
+     */
+    private get parentForm(): FormulaireDefinition {
+        let res = this.parent;
+        while (!(res instanceof FormulaireDefinition))
+            res = res.parent;
+        return res as FormulaireDefinition;
+
+    }
     public get sessionNub(): SessionNub {
         return this._sessionNub;
     }
 
     public setSessionNub(sn: SessionNub, update: boolean = true) {
         this._sessionNub = sn;
+        this._props.setProps(sn.properties, this);
         if (update)
             this.updateFields();
     }
@@ -107,22 +113,12 @@ export class FieldSet extends FormulaireElement implements Observer {
         return this._props;
     }
 
-    public getPropValue(key: string): any {
+    private getPropValue(key: string): any {
         return this._props.getPropValue(key);
     }
 
-    public setPropValue(key: string, val: any) {
-        if (this._props.setPropValue(key, val, this)) {
-            // si prop=type d'ouvrage, on prend une loi de débit compatible avec (spécifique aux ouvrages //) comme valeur par défaut
-            if (key === "structureType") {
-                if (!StructureProperties.isCompatibleValues(val, this.getPropValue("loiDebit")))
-                    this.setPropValue("loiDebit", StructureProperties.findCompatibleLoiDebit(val));
-            }
-            // si prop=loi débit, on prend un type d'ouvrage compatible
-            else if (key === "loiDebit")
-                if (!StructureProperties.isCompatibleValues(this.getPropValue("structureType"), val))
-                    this.setPropValue("structureType", StructureProperties.findCompatibleStructure(val));
-        }
+    public setPropValue(key: string, val: any): boolean {
+        return this._props.setPropValue(key, val, this);
     }
 
     /**
@@ -279,31 +275,21 @@ export class FieldSet extends FormulaireElement implements Observer {
 
         // fin MAJ selects
 
-        this.applyDependencies(this._parentForm);
+        this.applyDependencies(this.parentForm);
     }
 
     public parseConfig(json: {}, data?: {}) {
         this._jsonConfig = json;
-        this._parentForm = data["parentForm"];
 
         this._confId = json["id"];
 
         const ct: string = json["calcType"];
-        const calc_type: CalculatorType = ct == undefined ? this._parentForm.calculatorType : CalculatorType[ct];
+        const calc_type: CalculatorType = ct == undefined ? this.parentForm.calculatorType : CalculatorType[ct];
         this.setPropValue("calcType", calc_type);
 
-        const nt: string = json["nodeType"];
-        const node_type: ComputeNodeType = nt == undefined ? ComputeNodeType.None : ComputeNodeType[nt];
-
         const dnt: string = json["defaultNodeType"];
-        const default_node_type: ComputeNodeType = dnt == undefined ? ComputeNodeType.None : ComputeNodeType[dnt];
-
-        if (nt !== undefined && dnt !== undefined)
-            throw new Error("les champs 'nodeType' et 'defaultNodeType' ne doivent pas être définis en même temps")
-
-        const ntype = dnt !== undefined ? default_node_type : node_type;
-
-        this.setPropValue("nodeType", ntype);
+        const node_type: ComputeNodeType = dnt == undefined ? this.parentForm.nodeType : ComputeNodeType[dnt];
+        this.setPropValue("nodeType", node_type);
 
         const st: string = json["defaultStructType"];
         if (st)
@@ -383,19 +369,85 @@ export class FieldSet extends FormulaireElement implements Observer {
         if (data.action)
             switch (data.action) {
                 case "select":
-                    if (data.value.id.indexOf("select_section_") != -1)  // sections paramétrées
+                    if (sender.id === "select_section")  // sections paramétrées
                         this.setPropValue("nodeType", data.value.value);
-                    if (data.value.id.indexOf("select_ouvrage") != -1) // ouvrages parallèles
+                    if (sender.id === "select_ouvrage") // ouvrages parallèles
                         this.setPropValue("structureType", data.value.value);
-                    else if (data.value.id.indexOf("select_loidebit1") != -1) // ouvrages parallèles
+                    else if (sender.id === "select_loidebit1") // ouvrages parallèles
                         this.setPropValue("loiDebit", data.value.value);
-                    else if (data.value.id.indexOf("select_loidebit2") != -1) // ouvrages parallèles
+                    else if (sender.id === "select_loidebit2") // ouvrages parallèles
                         this.setPropValue("loiDebit", data.value.value);
-                    else if (data.value.id.indexOf("select_resolution") != -1) // courbes de remous, méthode de résolution
+                    else if (sender.id === "select_resolution") // courbes de remous, méthode de résolution
                         this.setPropValue("methodeResolution", data.value.value);
-                    else if (data.value.id.indexOf("select_target") != -1) // courbes de remous, variable à calculer
+                    else if (sender.id === "select_target") // courbes de remous, variable à calculer
                         this.setPropValue("varCalc", data.value.value);
                     break;
             }
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["props"] = this._props.props;
+        res["elements"] = this.serialiseKids();
+        return { "fieldset": res };
+    }
+
+    private deserialiseParam(elements: {}): NgParameter {
+        const res: NgParameter = this.getFormulaireNodeById(elements["id"]) as NgParameter;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseSelect(elements: {}): SelectField {
+        const res: SelectField = this.getFormulaireNodeById(elements["id"]) as SelectField;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid fieldset object '${element}'`);
+
+        switch (keys[0]) {
+            case "param":
+                this.deserialiseParam(element[keys[0]]);
+                break;
+
+            case "select":
+                this.deserialiseSelect(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in fieldset object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    break;
+
+                case "props":
+                    this._props = new Props(elements[k]);
+                    break;
+
+                case "elements":
+                    for (const e of elements[k])
+                        this.deserialiseElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in form object`);
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/formulaire-node.ts b/src/app/formulaire/formulaire-node.ts
index 814d47c88f0734d4a1940a527ba12d81ece7b1f7..aca8ea0c153d692e0330f4f3f614fde0de569a62 100644
--- a/src/app/formulaire/formulaire-node.ts
+++ b/src/app/formulaire/formulaire-node.ts
@@ -143,4 +143,18 @@ export abstract class FormulaireNode implements IObservable {
     notifyObservers(data: any, sender?: any) {
         this._observable.notifyObservers(data, sender);
     }
+
+    protected serialiseKids() {
+        const elems = [];
+        for (const k of this.kids)
+            elems.push(k.JSONserialise());
+        return elems;
+    }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        return {};
+    }
 }
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 671730429dd4413c273548c9832142561144c8f1..552594bc42c3db2db734a7d87c08b1c3fa49f523 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -320,4 +320,78 @@ export class NgParameter extends InputField {
             super.updateLocalisation(loc, key);
         }
     }
+
+    private paramValuesJSON(): any {
+        let res = {};
+        res["mode"] = ParamValueMode[this._paramValues.valueMode];
+        switch (this._paramValues.valueMode) {
+            case ParamValueMode.SINGLE:
+                res["value"] = this._paramValues.singleValue;
+                break;
+
+            case ParamValueMode.MINMAX:
+                res["min"] = this._paramValues.min;
+                res["max"] = this._paramValues.max;
+                res["step"] = this._paramValues.step;
+                break;
+
+            case ParamValueMode.LISTE:
+                res["values"] = this._paramValues.valueList;
+                break;
+        }
+        return res;
+    }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["values"] = this.paramValuesJSON();
+        return { "param": res };
+    }
+
+    private deserialiseValues(json: {}) {
+        const m: ParamValueMode = (<any>ParamValueMode)[json["mode"]];
+        switch (m) {
+            case ParamValueMode.SINGLE:
+                this._paramValues.setValues(+json["value"]);
+                break;
+
+            case ParamValueMode.MINMAX:
+                this._paramValues.setValues(+json["min"], +json["max"], +json["step"]);
+                break;
+
+            case ParamValueMode.LISTE:
+                this._paramValues.setValues(json["values"]);
+                break;
+
+            case ParamValueMode.CALCUL:
+                this._paramValues.valueMode = ParamValueMode.CALCUL;
+                break;
+
+            default:
+                throw new Error(`session file : invalid value mode '${json["mode"]}' in param object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    break;
+
+                case "values":
+                    this.deserialiseValues(elements[k]);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in param object`);
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/select-entry.ts b/src/app/formulaire/select-entry.ts
index 8da49428e9fa9e06ee76f4ceae143cb5137208a3..dc552ff9f6a52b09ba4f0625fa0f15f6781cc0bc 100644
--- a/src/app/formulaire/select-entry.ts
+++ b/src/app/formulaire/select-entry.ts
@@ -27,4 +27,14 @@ export class SelectEntry {
     get value(): any {
         return this._value;
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise() {
+        let res = {};
+        res["id"] = this.id;
+        res["value"] = this._value;
+        return res;
+    }
 }
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index d6366564c38290fa786ff2951a43ce229bf7f254..916fee39c058dff6a2c0a6723517f2a470a0f7d7 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -117,4 +117,39 @@ export class SelectField extends Field {
             this.addEntry(e);
         }
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["selected_id"] = this._selectedEntry.id;
+        return { "select": res };
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    this._confId = elements[k];
+                    break;
+
+                case "selected_id":
+                    const sel = elements[k];
+                    for (const e of this._entries)
+                        if (e.id === sel) {
+                            this._selectedEntry = e;
+                            break;
+                        }
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in select object`);
+            }
+        }
+    }
 }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 50e6ec6f71dc0ceb6b03282e8c46cdf64e5eabb3..5984c3f9d0e22f1188115f91d312d87721ae8e9b 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -3,6 +3,7 @@ import { Response } from "@angular/http";
 import { Observable as rxObservable } from "rxjs/Observable";
 import "rxjs/add/operator/toPromise";
 import { decode } from "he";
+import { saveAs } from "file-saver"
 
 import { CalculatorType, EnumEx, Observable } from "jalhyd";
 
@@ -146,7 +147,7 @@ export class FormulaireService extends Observable {
         return this._httpService.httpGetRequest(undefined, undefined, undefined, f, processData);
     }
 
-    public createFormulaire(ct: CalculatorType): Promise<FormulaireDefinition> {
+    private newFormulaire(ct: CalculatorType, jsonState?: {}): FormulaireDefinition {
         let f: FormulaireDefinition;
         switch (ct) {
             case CalculatorType.ConduiteDistributrice:
@@ -185,10 +186,31 @@ export class FormulaireService extends Observable {
                 throw new Error(`FormulaireService.createFormulaire() : type de calculette ${ct} non pris en charge`)
         }
 
-        f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct) + " (" + f.uid + ")");
+        if (jsonState !== undefined) {
+            const props = jsonState["props"];
+            f.initSessionNub(props);
+        }
+        else
+            f.initSessionNub();
+
+        return f;
+    }
+
+    /**
+     * crée un formulaire d'un type donné
+     * @param ct type de formulaire
+     * @param jsonState 
+     */
+    public createFormulaire(ct: CalculatorType, jsonState?: {}): Promise<FormulaireDefinition> {
+        const f: FormulaireDefinition = this.newFormulaire(ct, jsonState);
+
+        if (jsonState === undefined)
+            f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct) + " (" + f.uid + ")");
         this._formulaires.push(f);
         let prom: Promise<Response> = this.loadConfig(f, ct);
         return prom.then(_ => {
+            if (jsonState !== undefined)
+                f.deserialiseJSON(jsonState);
             return f;
         }).then(f => {
             this.loadUpdateFormulaireLocalisation(f);
@@ -327,4 +349,123 @@ export class FormulaireService extends Observable {
 
         return false;
     }
+
+    private readSingleFile(file: File): Promise<any> {
+        return new Promise<any>((resolve, reject) => {
+            var fr = new FileReader();
+
+            fr.onload = () => {
+                resolve(fr.result);
+            };
+
+            fr.onerror = () => {
+                fr.abort();
+                reject(new Error(`Erreur de lecture du fichier ${file.name}`));
+            };
+
+            fr.readAsText(file);
+        });
+    }
+
+    /**
+     * charge une session en tenant compte des calculettes sélectionnées
+     * @param f fichier session
+     * @param formInfos infos sur les calculettes @see LoadCalculatorComponent._calculators
+     */
+    public loadSession(f: File, formInfos: any[]) {
+        this.readSingleFile(f).then(s => {
+            const session = JSON.parse(s);
+            for (const k in session)
+                switch (k) {
+                    case "session":
+                        this.deserialiseSession(session[k], formInfos);
+                        break;
+
+                    default:
+                        throw new Error(`session file : invalid key '${k}'`);
+                }
+        }).catch(err => {
+            throw err;
+        });
+    }
+
+    public saveSession(session: {}, filename?: string) {
+        const blob = new Blob([JSON.stringify(session)], { type: "text/plain;charset=utf-8" });
+        saveAs(blob, filename);
+    }
+
+    /**
+     * obtient des infos (nom, uid des calculettes) d'un fichier session
+     * @param f fichier session
+     */
+    public calculatorInfosFromSessionFile(f: File): Promise<any[]> {
+        return this.readSingleFile(f).then(s => {
+            const res: any[] = [];
+            const session = JSON.parse(s);
+
+            // liste des noms de calculettes
+            for (const k in session)
+                switch (k) {
+                    case "session":
+                        const sess = session[k];
+                        const elems = sess["elements"];
+                        for (const e of elems)
+                            for (const k in e)
+                                if (k === "form") {
+                                    const form = e[k];
+                                    res.push({ "uid": form["uid"], "title": form["id"] });
+                                }
+                        break;
+
+                    default:
+                        throw new Error(`session file : invalid key '${k}'`);
+                }
+            return res;
+        });
+    }
+
+    public saveForm(f: FormulaireDefinition) {
+        this.notifyObservers({
+            "action": "saveForm",
+            "form": f
+        });
+    }
+
+    private deserialiseForm(elements: {}) {
+        const props = elements["props"];
+        const ct: CalculatorType = props["calcType"];
+        this.createFormulaire(ct, elements);
+    }
+
+    private deserialiseSessionElement(element: {}, formInfos: any[]) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid session object '${element}'`);
+
+        switch (keys[0]) {
+            case "form":
+                const form = element[keys[0]];
+
+                for (const i of formInfos)
+                    if (i["uid"] == form["uid"] && i["selected"])
+                        this.deserialiseForm(form);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in session object`);
+        }
+    }
+
+    private deserialiseSession(elements: {}, formInfos: any[]) {
+        for (const ks in elements)
+            switch (ks) {
+                case "elements":
+                    for (const e of elements[ks])
+                        this.deserialiseSessionElement(e, formInfos);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${ks}' in session object`);
+            }
+    }
 }
diff --git a/src/app/services/internationalisation/internationalisation.service.ts b/src/app/services/internationalisation/internationalisation.service.ts
index f418a88998bd01789e1106247a3002eeebe225da..d5e3486b82e5b5517b97ff7e00e6f4c738895b36 100644
--- a/src/app/services/internationalisation/internationalisation.service.ts
+++ b/src/app/services/internationalisation/internationalisation.service.ts
@@ -192,24 +192,27 @@ export class InternationalisationService extends Observable {
 
                 default:
                     const match = this.parseLabel(o);
-                    if (match)
+                    if (match) {
                         if (match[1] === "ouvrage")
                             res = this.localizeText("INFO_OUVRAGE") + " n°" + (+match[2] + 1);
 
-                    const p = match[3];
-                    switch (p) {
-                        case "Q":
-                            res += " : " + this.localizeText("INFO_GRANDEUR_" + p);
-                            break;
+                        const p = match[3];
+                        switch (p) {
+                            case "Q":
+                                res += " : " + this.localizeText("INFO_GRANDEUR_" + p);
+                                break;
 
-                        case "Q_Mode":
-                            res += " : " + this.localizeText("INFO_TYPE_ECOULEMENT");
-                            break;
+                            case "Q_Mode":
+                                res += " : " + this.localizeText("INFO_TYPE_ECOULEMENT");
+                                break;
 
-                        case "Q_Regime":
-                            res += " : " + this.localizeText("INFO_REGIME");
-                            break;
+                            case "Q_Regime":
+                                res += " : " + this.localizeText("INFO_REGIME");
+                                break;
+                        }
                     }
+                    else
+                        res = o;
                     break;
             }
         }