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