Exporting comments in CSV
This commit is contained in:
parent
1c3d60fe39
commit
8337f8928c
@ -94,15 +94,11 @@ export class CsvExportService {
|
||||
let csvContent = []; // Holds all lines as arrays with each column-value
|
||||
// initial array of usable text separators. The first character not used
|
||||
// in any text data or as column separator will be used as text separator
|
||||
let tsList = ['"', "'", '`', '/', '\\', ';', '.'];
|
||||
|
||||
if (lineSeparator === columnSeparator) {
|
||||
throw new Error('lineseparator and columnseparator must differ from each other');
|
||||
}
|
||||
|
||||
tsList = this.checkCsvTextSafety(lineSeparator, tsList);
|
||||
tsList = this.checkCsvTextSafety(columnSeparator, tsList);
|
||||
|
||||
// create header data
|
||||
const header = columns.map(column => {
|
||||
let label: string;
|
||||
@ -112,15 +108,14 @@ export class CsvExportService {
|
||||
label = column.label;
|
||||
}
|
||||
label = this.capitalizeTranslate(label);
|
||||
tsList = this.checkCsvTextSafety(label, tsList);
|
||||
return label;
|
||||
});
|
||||
csvContent.push(header);
|
||||
|
||||
// create lines
|
||||
csvContent = csvContent.concat(
|
||||
models.map(model => {
|
||||
return columns.map(column => {
|
||||
models.map(model =>
|
||||
columns.map(column => {
|
||||
let value: string;
|
||||
|
||||
if (isPropertyDefinition(column)) {
|
||||
@ -139,21 +134,13 @@ export class CsvExportService {
|
||||
} else if (isMapDefinition(column)) {
|
||||
value = column.map(model);
|
||||
}
|
||||
tsList = this.checkCsvTextSafety(value, tsList);
|
||||
|
||||
return value;
|
||||
});
|
||||
return this.checkCsvTextSafety(value);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// assemble lines, putting text separator in place
|
||||
if (!tsList.length) {
|
||||
throw new Error('no usable text separator left for valid csv text');
|
||||
}
|
||||
const csvContentAsString: string = csvContent
|
||||
.map(line => {
|
||||
return line.map(entry => tsList[0] + entry + tsList[0]).join(columnSeparator);
|
||||
})
|
||||
.map((line: string[]) => line.join(columnSeparator))
|
||||
.join(lineSeparator);
|
||||
const filetype = `text/csv;charset=${encoding}`;
|
||||
if (encoding === 'iso-8859-15') {
|
||||
@ -164,20 +151,17 @@ export class CsvExportService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given input contains any of the characters defined in a list
|
||||
* used for textseparators. The list is then returned without the 'special'
|
||||
* characters, as they may not be used as text separator in this csv.
|
||||
* Ensures, that the given string has escaped double quotes
|
||||
* and no linebreak. The string itself will also be escaped by `double quotes`.
|
||||
*
|
||||
* @param input any input to be sent to CSV
|
||||
* @param tsList The list of special characters to check.
|
||||
* @returns the cleaned CSV String list
|
||||
* @param {string} input any input to be sent to CSV
|
||||
* @returns {string} the cleaned string.
|
||||
*/
|
||||
public checkCsvTextSafety(input: string, tsList: string[]): string[] {
|
||||
if (input === null || input === undefined) {
|
||||
return tsList;
|
||||
public checkCsvTextSafety(input: string): string {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return tsList.filter(char => input.indexOf(char) < 0);
|
||||
return '"' + input.replace(/"/g, '""').replace(/(\r\n\t|\n|\r\t)/gm, '') + '"';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,21 +177,22 @@ export class CsvExportService {
|
||||
|
||||
public dummyCSVExport(header: string[], rows: (string | number | boolean | null)[][], filename: string): void {
|
||||
const separator = this.config.instant<string>('general_csv_separator');
|
||||
const tsList = this.checkCsvTextSafety(separator, ['"', "'", '`', '/', '\\', ';', '.']);
|
||||
const headerRow = [header.map(item => this.translate.instant(item)).join(separator)];
|
||||
const content = rows.map(row =>
|
||||
row
|
||||
.map(item => {
|
||||
if (item === null) {
|
||||
return '';
|
||||
let value = '';
|
||||
if (!item) {
|
||||
value = '';
|
||||
}
|
||||
if (typeof item === 'string') {
|
||||
return `${tsList[0]}${item}${tsList[0]}`;
|
||||
if (typeof item === 'number') {
|
||||
value = item.toString(10);
|
||||
} else if (typeof item === 'boolean') {
|
||||
return item ? '1' : '0';
|
||||
value = item ? '1' : '0';
|
||||
} else {
|
||||
return `${item}`;
|
||||
value = item;
|
||||
}
|
||||
return this.checkCsvTextSafety(value);
|
||||
})
|
||||
.join(separator)
|
||||
);
|
||||
|
@ -14,5 +14,6 @@ export function reconvertChars(text: string): string {
|
||||
.replace(/ü/g, 'ü')
|
||||
.replace(/Ü/g, 'Ü')
|
||||
.replace(/å|å/g, 'å')
|
||||
.replace(/Å|Å/g, 'Å');
|
||||
.replace(/Å|Å/g, 'Å')
|
||||
.replace(/ß|ß/g, 'ß');
|
||||
}
|
||||
|
@ -199,11 +199,11 @@
|
||||
>
|
||||
warning
|
||||
</mat-icon>
|
||||
<span *ngFor="let submitter of entry.newEntry.csvSubmitters">
|
||||
<div *ngFor="let submitter of entry.newEntry.csvSubmitters">
|
||||
{{ submitter.name }}
|
||||
<mat-icon class="newBadge" color="accent" inline *ngIf="!submitter.id">add</mat-icon>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
@ -148,13 +148,6 @@ export class MotionExportDialogComponent implements OnInit {
|
||||
this.enableControl('content');
|
||||
}
|
||||
|
||||
// At the moment the csv can't export comments.
|
||||
if (format === FileFormat.CSV) {
|
||||
this.disableControl('comments');
|
||||
} else {
|
||||
this.enableControl('comments');
|
||||
}
|
||||
|
||||
if (format === FileFormat.CSV || format === FileFormat.XLSX) {
|
||||
this.disableControl('lnMode');
|
||||
this.disableControl('crMode');
|
||||
|
@ -320,11 +320,18 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
|
||||
}
|
||||
}
|
||||
} else if (exportInfo.format === FileFormat.CSV) {
|
||||
this.motionCsvExport.exportMotionList(
|
||||
data,
|
||||
[...exportInfo.content, ...exportInfo.metaInfo],
|
||||
exportInfo.crMode
|
||||
);
|
||||
const content = [];
|
||||
const comments = [];
|
||||
if (exportInfo.content) {
|
||||
content.push(...exportInfo.content);
|
||||
}
|
||||
if (exportInfo.metaInfo) {
|
||||
content.push(...exportInfo.metaInfo);
|
||||
}
|
||||
if (exportInfo.comments) {
|
||||
comments.push(...exportInfo.comments);
|
||||
}
|
||||
this.motionCsvExport.exportMotionList(data, content, comments, exportInfo.crMode);
|
||||
} else if (exportInfo.format === FileFormat.XLSX) {
|
||||
this.motionXlsxExport.exportMotionList(data, exportInfo.metaInfo, exportInfo.comments);
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ import { ChangeRecommendationRepositoryService } from 'app/core/repositories/mot
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
|
||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
|
||||
import { reconvertChars } from 'app/shared/utils/reconvert-chars';
|
||||
import { stripHtmlTags } from 'app/shared/utils/strip-html-tags';
|
||||
|
||||
/**
|
||||
* Exports CSVs for motions. Collect all CSV types here to have them in one place.
|
||||
@ -38,7 +41,8 @@ export class MotionCsvExportService {
|
||||
private configService: ConfigService,
|
||||
private linenumberingService: LinenumberingService,
|
||||
private changeRecoRepo: ChangeRecommendationRepositoryService,
|
||||
private motionRepo: MotionRepositoryService
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private commentRepo: MotionCommentSectionRepositoryService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -83,7 +87,12 @@ export class MotionCsvExportService {
|
||||
* @param contentToExport content properties to export
|
||||
* @param crMode
|
||||
*/
|
||||
public exportMotionList(motions: ViewMotion[], contentToExport: string[], crMode?: ChangeRecoMode): void {
|
||||
public exportMotionList(
|
||||
motions: ViewMotion[],
|
||||
contentToExport: string[],
|
||||
comments: number[],
|
||||
crMode?: ChangeRecoMode
|
||||
): void {
|
||||
if (!crMode) {
|
||||
crMode = this.configService.instant('motions_recommendation_text_mode');
|
||||
}
|
||||
@ -116,6 +125,18 @@ export class MotionCsvExportService {
|
||||
return { property: option } as CsvColumnDefinitionProperty<ViewMotion>;
|
||||
}
|
||||
});
|
||||
exportProperties.push(
|
||||
...comments.map(commentId => ({
|
||||
label: this.commentRepo.getViewModel(commentId).getTitle(),
|
||||
map: (motion: ViewMotion) => {
|
||||
const viewComment = this.commentRepo.getViewModel(commentId);
|
||||
const motionComment = motion.getCommentForSection(viewComment);
|
||||
return motionComment && motionComment.comment
|
||||
? reconvertChars(stripHtmlTags(motionComment.comment))
|
||||
: '';
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
this.csvExport.export(motions, exportProperties, this.translate.instant('Motions') + '.csv');
|
||||
}
|
||||
@ -147,29 +168,29 @@ export class MotionCsvExportService {
|
||||
public exportDummyMotion(): void {
|
||||
const headerRow = [
|
||||
'Identifier',
|
||||
'Submitters',
|
||||
'Title',
|
||||
'Text',
|
||||
'Reason',
|
||||
'Submitters',
|
||||
'Category',
|
||||
'Tags',
|
||||
'Origin',
|
||||
'Motion block'
|
||||
'Motion block',
|
||||
'Origin'
|
||||
];
|
||||
const rows = [
|
||||
[
|
||||
'A1',
|
||||
'Submitter A',
|
||||
'Title 1',
|
||||
'Text 1',
|
||||
'Reason 1',
|
||||
'Submitter A',
|
||||
'Category A',
|
||||
'Tag 1, Tag 2',
|
||||
'Last Year Conference A',
|
||||
'Block A'
|
||||
'Block A',
|
||||
'Last Year Conference A'
|
||||
],
|
||||
['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', null, null, 'Block A'],
|
||||
[null, 'Title 3', 'Text 3', null, null, null, null, null, null]
|
||||
['B1', 'Submitter B', 'Title 2', 'Text 2', 'Reason 2', 'Category B', null, 'Block A', 'Origin B'],
|
||||
['C2', null, 'Title 3', 'Text 3', null, null, null, null, null]
|
||||
];
|
||||
this.csvExport.dummyCSVExport(headerRow, rows, `${this.translate.instant('motions-example')}.csv`);
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
||||
}
|
||||
const submitterArray = submitterlist.split(','); // TODO fails with 'full name'
|
||||
for (const submitter of submitterArray) {
|
||||
const existingSubmitters = this.userRepo.getUsersByName(submitter);
|
||||
const existingSubmitters = this.userRepo.getUsersByName(submitter.trim());
|
||||
if (!existingSubmitters.length) {
|
||||
if (!this.newSubmitters.find(listedSubmitter => listedSubmitter.name === submitter)) {
|
||||
this.newSubmitters.push({ name: submitter });
|
||||
|
Loading…
Reference in New Issue
Block a user