diff --git a/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts b/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts
index 179a0814d2c02ced9c1e3feba852a44d997124ea..7387d324c87175137b039fb9b8dd25dfb86e31c7 100644
--- a/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts
+++ b/src/app/components/creator/_dialogs/comment-settings/comment-settings.component.ts
@@ -14,6 +14,7 @@ import { CommentBonusTokenMixin } from '../../../../models/comment-bonus-token-m
 import { CommentSettings } from '../../../../models/comment-settings';
 import { CommentSettingsDialog } from '../../../../models/comment-settings-dialog';
 import { ExportCsv } from '../../../../models/export-csv';
+import { Export } from '../../../../models/export';
 
 @Component({
   selector: 'app-comment-settings',
@@ -100,19 +101,15 @@ export class CommentSettingsComponent implements OnInit {
     this.commentService.deleteCommentsByRoomId(this.roomId).subscribe();
   }
 
-  export(delimiter: string, format: string): void {
-    new ExportCsv(
-      this.roomId,
+  exportCSV(): void {
+    const exp: Export = new Export(
+      this.editRoom,
       this.commentService,
       this.bonusTokenService,
       this.translationService,
-      this.notificationService,
-      this.editRoom
-    ).exportAsCsv(delimiter, format);
-  }
-
-  exportCSV(): void {
-      this.export(';', 'csv');
+      'room-page',
+      this.notificationService);
+    exp.exportAsCsv();
   }
 
   closeDialog(): void {
diff --git a/src/app/components/shared/room-list/room-list.component.ts b/src/app/components/shared/room-list/room-list.component.ts
index dce7a3ecd89501033b9ec0a155edc0f309e6da47..bfde2baa8a750f49219c4bd1aa8a18713c6ccbb0 100644
--- a/src/app/components/shared/room-list/room-list.component.ts
+++ b/src/app/components/shared/room-list/room-list.component.ts
@@ -17,6 +17,7 @@ import { RemoveFromHistoryComponent } from '../_dialogs/remove-from-history/remo
 import { MatTableDataSource } from '@angular/material/table';
 import { ExportCsv } from '../../../models/export-csv';
 import { BonusTokenService } from '../../../services/http/bonus-token.service';
+import { Export } from '../../../models/export';
 
 @Component({
   selector: 'app-room-list',
@@ -171,14 +172,14 @@ export class RoomListComponent implements OnInit, OnDestroy {
   }
 
   exportCsv(room: Room) {
-    new ExportCsv(
-      room.id,
+    const exp: Export = new Export(
+      room,
       this.commentService,
       this.bonusTokenService,
       this.translateService,
+      'room-list',
       this.notificationService,
-      room,
-      'room-list'
-    ).exportAsCsv(';', 'csv', this.user);
+      this.user);
+    exp.exportAsCsv();
   }
 }
diff --git a/src/app/models/export.ts b/src/app/models/export.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b45e130bc63168150c845e788052daa0bb896474
--- /dev/null
+++ b/src/app/models/export.ts
@@ -0,0 +1,168 @@
+import { Room } from './room';
+import { CommentService } from '../services/http/comment.service';
+import { BonusTokenService } from '../services/http/bonus-token.service';
+import { CommentBonusTokenMixin } from './comment-bonus-token-mixin';
+import { BonusToken } from './bonus-token';
+import { TranslateService } from '@ngx-translate/core';
+import { User } from './user';
+import { NotificationService } from '../services/util/notification.service';
+
+class ExportMapper<E> {
+
+  private list: Map<string, string>[];
+  private map: Map<string, (e: E) => any>;
+
+  constructor() {
+    this.list = [];
+    this.map = new Map<string, (e: E) => any>();
+  }
+
+  public add(m: (f: (key: string, value: (e: E) => any) => void) => void): void {
+    m((key, value) => this.map.set(key, value));
+  }
+
+  public acceptAll(ar: E[]): void {
+    ar.forEach(elem => {
+      this.accept(elem);
+    });
+  }
+
+  public accept(e: E): void {
+    const row: Map<string, string> = new Map<string, string>();
+    this.keys().forEach(k => {
+      row.set(k, this.map.get(k)(e));
+    });
+    this.list.push(row);
+  }
+
+  public parse(delimiter: string, map?: Map<string, string>): string {
+    if (this.list.length <= 0) { return 'd'; }
+    let parse = '';
+    if (!map) {
+      map = new Map<string, string>();
+      this.keys().forEach(e => map.set(e, e));
+    }
+    this.keys().forEach(e => parse += map.get(e) + delimiter);
+    this.list.forEach(e => {
+      parse += '\r\n';
+      this.keys().forEach(k => {
+        parse += e.get(k) + delimiter;
+      });
+    });
+    return parse;
+  }
+
+  public keys(): string[] {
+    return Array.from(this.map.keys());
+  }
+
+}
+
+export class Export {
+
+  private mapper: ExportMapper<CommentBonusTokenMixin>;
+  private bonusTokenMask: boolean;
+
+  constructor(
+    private room: Room,
+    private commentService: CommentService,
+    private bonusTokenService: BonusTokenService,
+    private translateService: TranslateService,
+    private translationPath: string,
+    private notificationService: NotificationService,
+    private user?: User
+  ) {
+    this.mapper = new ExportMapper<CommentBonusTokenMixin>();
+    this.bonusTokenMask = !this.user || this.user.role >= 2;
+  }
+
+  public exportAsCsv() {
+    this.mapper.add(m => {
+      m('question', e => this.parseBody(e.body));
+      m('timestamp', e => this.parseDate(e.timestamp));
+      m('presented', e => e.read);
+      m('correct/wrong', e => e.correct);
+      m('score', e => e.score);
+      m('answer', e => this.parseBody(e.answer));
+      m('token', e => this.checkUser(e) && e.bonusToken ? e.bonusToken : '');
+      m('token-time', e => this.checkUser(e) ? this.parseDate(e.bonusTimeStamp) : '');
+    });
+    this.createTranslationMap(this.mapper.keys(), translationMap => {
+      this.getCommentBonusTokenMixin(comments => {
+        if (comments.length <= 0) {
+          this.translateService.get(this.translationPath + '.no-comments').subscribe(msg => {
+            this.notificationService.show(msg);
+          });
+        } else {
+          this.mapper.acceptAll(comments);
+          const date = new Date();
+          const dateString = date.toLocaleDateString();
+          const data = this.mapper.parse(';', translationMap);
+          const fileName = this.room.name + '_' + this.room.shortId + dateString + '.csv';
+          this.exportData(data, fileName);
+        }
+      });
+    });
+  }
+
+  private checkUser(e: CommentBonusTokenMixin): boolean {
+    return this.bonusTokenMask || e.creatorId === this.user.id;
+  }
+
+  private parseBody(body: string): string {
+    if (!body) {return ''; }
+    return '"' + body.replace(/[\r\n]/g, ' ').replace(/ +/g, ' ').replace(/"/g, '""') + '"';
+  }
+
+  private parseDate(date: Date): string {
+    if (!date) {return ''; }
+    const d = new Date(date);
+    return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
+  }
+
+  private exportData(data: string, fileName: string) {
+    const myBlob = new Blob([data], { type: `text/csv` });
+    const link = document.createElement('a');
+    link.setAttribute('download', fileName);
+    link.href = window.URL.createObjectURL(myBlob);
+    link.click();
+  }
+
+  private getCommentBonusTokenMixin(comments: (comments: CommentBonusTokenMixin[]) => void) {
+    this.commentService.getAckComments(this.room.id).subscribe(data => {
+      this.bonusTokenService.getTokensByRoomId(this.room.id).subscribe(list => {
+        const c = data.map(comment => {
+          const bt: BonusToken = list.find(e => e.userId === comment.creatorId && comment.id === e.commentId);
+          const commentWithToken: CommentBonusTokenMixin = <CommentBonusTokenMixin>comment;
+          if (bt) {
+            commentWithToken.bonusToken = bt.token;
+            commentWithToken.bonusTimeStamp = bt.timestamp;
+          }
+          return commentWithToken;
+        }).sort(e => e.bonusToken ? -1 : 1);
+        comments(c);
+      });
+    });
+
+  }
+
+  private createTranslationMap(ar: string[], map: ((m: Map<string, string>) => void)): void {
+    const fields = this.addTranslationPath(ar);
+    const tm: Map<string, string> = new Map<string, string>();
+    this.translateService.get(fields).subscribe(msgs => {
+      Object.entries(msgs).forEach((e, i) => {
+        tm.set(ar[i], <string>e[1]);
+      });
+      map(tm);
+    });
+  }
+
+  private addTranslationPath(ar: string[]): string[] {
+    const fields = [];
+    ar.forEach(e => {
+      fields.push(this.translationPath + '.' + e);
+    });
+    return fields;
+  }
+
+}