cat-documents-ng 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ng-package.json +10 -0
- package/package.json +5 -11
- package/src/Shared/components/confirmation-dialog/confirmation-dialog.component.html +3 -0
- package/src/Shared/components/confirmation-dialog/confirmation-dialog.component.scss +13 -0
- package/src/Shared/components/confirmation-dialog/confirmation-dialog.component.spec.ts +70 -0
- package/src/Shared/components/confirmation-dialog/confirmation-dialog.component.ts +133 -0
- package/src/Shared/components/table-primary/table-primary.component.html +66 -0
- package/src/Shared/components/table-primary/table-primary.component.scss +227 -0
- package/src/Shared/components/table-primary/table-primary.component.spec.ts +23 -0
- package/src/Shared/components/table-primary/table-primary.component.ts +143 -0
- package/src/Shared/components/table-primary/table-primary.model.ts +21 -0
- package/src/Shared/constant/ERROR.ts +55 -0
- package/src/Shared/constant/PERMISSIONS.ts +17 -0
- package/src/Shared/constant/SHARED.ts +936 -0
- package/{Shared/constant/URLS.d.ts → src/Shared/constant/URLS.ts} +31 -25
- package/src/Shared/services/app-config.service.spec.ts +19 -0
- package/src/Shared/services/app-config.service.ts +73 -0
- package/{Shared/services/global-error.handler.d.ts → src/Shared/services/global-error.handler.ts} +11 -9
- package/src/Shared/services/session.service.spec.ts +16 -0
- package/src/Shared/services/session.service.ts +76 -0
- package/src/Shared/shared.module.ts +25 -0
- package/src/lib/document/components/csv-viewer/csv-viewer.component.ts +1 -0
- package/src/lib/document/components/document-actions/document-actions.component.html +59 -0
- package/src/lib/document/components/document-actions/document-actions.component.scss +362 -0
- package/src/lib/document/components/document-actions/document-actions.component.spec.ts +297 -0
- package/src/lib/document/components/document-actions/document-actions.component.ts +163 -0
- package/src/lib/document/components/document-container/document-container.component.html +36 -0
- package/src/lib/document/components/document-container/document-container.component.scss +144 -0
- package/src/lib/document/components/document-container/document-container.component.spec.ts +110 -0
- package/src/lib/document/components/document-container/document-container.component.ts +363 -0
- package/src/lib/document/components/document-content-viewer/document-content-viewer.component.html +332 -0
- package/src/lib/document/components/document-content-viewer/document-content-viewer.component.scss +1877 -0
- package/src/lib/document/components/document-content-viewer/document-content-viewer.component.spec.ts +258 -0
- package/src/lib/document/components/document-content-viewer/document-content-viewer.component.ts +664 -0
- package/src/lib/document/components/document-history/document-history.component.html +96 -0
- package/src/lib/document/components/document-history/document-history.component.scss +392 -0
- package/src/lib/document/components/document-history/document-history.component.spec.ts +93 -0
- package/src/lib/document/components/document-history/document-history.component.ts +373 -0
- package/src/lib/document/components/document-list/document-list.component.html +46 -0
- package/src/lib/document/components/document-list/document-list.component.scss +513 -0
- package/src/lib/document/components/document-list/document-list.component.spec.ts +486 -0
- package/src/lib/document/components/document-list/document-list.component.ts +682 -0
- package/src/lib/document/components/document-list-item/document-list-item.component.html +36 -0
- package/src/lib/document/components/document-list-item/document-list-item.component.scss +34 -0
- package/src/lib/document/components/document-list-item/document-list-item.component.spec.ts +75 -0
- package/src/lib/document/components/document-list-item/document-list-item.component.ts +40 -0
- package/src/lib/document/components/document-search/document-search.component.html +64 -0
- package/src/lib/document/components/document-search/document-search.component.scss +206 -0
- package/src/lib/document/components/document-search/document-search.component.spec.ts +82 -0
- package/src/lib/document/components/document-search/document-search.component.ts +163 -0
- package/src/lib/document/components/document-status/document-status.component.html +31 -0
- package/src/lib/document/components/document-status/document-status.component.scss +192 -0
- package/src/lib/document/components/document-status/document-status.component.spec.ts +23 -0
- package/src/lib/document/components/document-status/document-status.component.ts +87 -0
- package/src/lib/document/components/document-upload/document-upload.component.html +160 -0
- package/src/lib/document/components/document-upload/document-upload.component.scss +235 -0
- package/src/lib/document/components/document-upload/document-upload.component.spec.ts +95 -0
- package/src/lib/document/components/document-upload/document-upload.component.ts +668 -0
- package/src/lib/document/components/document-viewer/document-viewer.component.html +50 -0
- package/src/lib/document/components/document-viewer/document-viewer.component.scss +187 -0
- package/src/lib/document/components/document-viewer/document-viewer.component.spec.ts +79 -0
- package/src/lib/document/components/document-viewer/document-viewer.component.ts +261 -0
- package/src/lib/document/components/document-zoom-controls/document-zoom-controls.component.html +48 -0
- package/src/lib/document/components/document-zoom-controls/document-zoom-controls.component.scss +320 -0
- package/src/lib/document/components/document-zoom-controls/document-zoom-controls.component.spec.ts +59 -0
- package/src/lib/document/components/document-zoom-controls/document-zoom-controls.component.ts +150 -0
- package/src/lib/document/components/documents-menu/documents-menu.component.html +44 -0
- package/src/lib/document/components/documents-menu/documents-menu.component.scss +363 -0
- package/src/lib/document/components/documents-menu/documents-menu.component.spec.ts +23 -0
- package/src/lib/document/components/documents-menu/documents-menu.component.ts +316 -0
- package/src/lib/document/components/folder-block/folder-block.component.html +46 -0
- package/src/lib/document/components/folder-block/folder-block.component.scss +9 -0
- package/src/lib/document/components/folder-block/folder-block.component.spec.ts +70 -0
- package/{lib/document/components/folder-block/folder-block.component.d.ts → src/lib/document/components/folder-block/folder-block.component.ts} +28 -12
- package/src/lib/document/components/folder-container/folder-container.component.html +56 -0
- package/src/lib/document/components/folder-container/folder-container.component.scss +20 -0
- package/src/lib/document/components/folder-container/folder-container.component.spec.ts +27 -0
- package/src/lib/document/components/folder-container/folder-container.component.ts +328 -0
- package/src/lib/document/components/linked-document/linked-document.component.html +23 -0
- package/src/lib/document/components/linked-document/linked-document.component.scss +10 -0
- package/src/lib/document/components/linked-document/linked-document.component.spec.ts +61 -0
- package/src/lib/document/components/linked-document/linked-document.component.ts +49 -0
- package/src/lib/document/components/request-document/request-document.component.html +86 -0
- package/src/lib/document/components/request-document/request-document.component.scss +16 -0
- package/src/lib/document/components/request-document/request-document.component.ts +278 -0
- package/src/lib/document/components/sidebar/sidebar.component.html +75 -0
- package/src/lib/document/components/sidebar/sidebar.component.scss +157 -0
- package/src/lib/document/components/sidebar/sidebar.component.spec.ts +114 -0
- package/src/lib/document/components/sidebar/sidebar.component.ts +223 -0
- package/src/lib/document/components/user-list/user-list.component.html +33 -0
- package/src/lib/document/components/user-list/user-list.component.scss +118 -0
- package/src/lib/document/components/user-list/user-list.component.spec.ts +23 -0
- package/src/lib/document/components/user-list/user-list.component.ts +181 -0
- package/src/lib/document/constant/DOCUMENT_HISTORY.ts +52 -0
- package/src/lib/document/directives/document.directive.ts +32 -0
- package/src/lib/document/directives/permission.directive.spec.ts +0 -0
- package/src/lib/document/directives/permission.directive.ts +72 -0
- package/src/lib/document/document.module.ts +351 -0
- package/{lib/document/models/document-alert.model.d.ts → src/lib/document/models/document-alert.model.ts} +11 -4
- package/src/lib/document/models/document-category.model.ts +30 -0
- package/src/lib/document/models/document-history.model.ts +109 -0
- package/src/lib/document/models/document-list-response.model.ts +37 -0
- package/src/lib/document/models/document-type.model.ts +44 -0
- package/src/lib/document/models/document.model.ts +53 -0
- package/{lib/document/models/folder.model.d.ts → src/lib/document/models/folder.model.ts} +10 -4
- package/src/lib/document/models/status-data.model.ts +31 -0
- package/src/lib/document/models/uploaded-file-response.model.ts +7 -0
- package/src/lib/document/models/user-list.model.ts +10 -0
- package/src/lib/document/services/csv-parser.service.spec.ts +97 -0
- package/src/lib/document/services/csv-parser.service.ts +303 -0
- package/src/lib/document/services/document-actions.service.ts +125 -0
- package/src/lib/document/services/document-content-type.service.ts +193 -0
- package/src/lib/document/services/document-history-style.service.ts +138 -0
- package/src/lib/document/services/document-history.service.ts +129 -0
- package/src/lib/document/services/document-http.service.spec.ts +119 -0
- package/src/lib/document/services/document-http.service.ts +497 -0
- package/src/lib/document/services/document-list.service.ts +195 -0
- package/src/lib/document/services/document-menu.service.ts +277 -0
- package/src/lib/document/services/document-scroll.service.ts +138 -0
- package/src/lib/document/services/document-severity.service.ts +98 -0
- package/src/lib/document/services/document-table-builder.service.ts +82 -0
- package/src/lib/document/services/document-upload-business.service.ts +326 -0
- package/src/lib/document/services/document-upload-data.service.ts +82 -0
- package/src/lib/document/services/document-upload-form.service.ts +149 -0
- package/src/lib/document/services/document-upload.service.spec.ts +99 -0
- package/src/lib/document/services/document-upload.service.ts +209 -0
- package/src/lib/document/services/document-viewer.service.ts +279 -0
- package/src/lib/document/services/document-zoom.service.spec.ts +56 -0
- package/src/lib/document/services/document-zoom.service.ts +164 -0
- package/src/lib/document/services/document.service.ts +356 -0
- package/src/lib/document/services/eml-parser.service.ts +444 -0
- package/src/lib/document/services/excel-parser.service.spec.ts +66 -0
- package/src/lib/document/services/excel-parser.service.ts +483 -0
- package/src/lib/document/services/file-format.service.spec.ts +16 -0
- package/src/lib/document/services/file-format.service.ts +63 -0
- package/src/lib/document/services/status-calculator.service.ts +44 -0
- package/src/lib/document/services/user-list.service.ts +77 -0
- package/src/lib/document/state/document.query.ts +378 -0
- package/{lib/document/state/document.service.d.ts → src/lib/document/state/document.service.ts} +46 -15
- package/src/lib/document/state/document.state.ts +100 -0
- package/src/lib/document/state/document.store.ts +200 -0
- package/{public-api.d.ts → src/public-api.ts} +4 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
- package/Shared/components/confirmation-dialog/confirmation-dialog.component.d.ts +0 -44
- package/Shared/components/table-primary/table-primary.component.d.ts +0 -31
- package/Shared/components/table-primary/table-primary.model.d.ts +0 -19
- package/Shared/constant/ERROR.d.ts +0 -52
- package/Shared/constant/SHARED.d.ts +0 -546
- package/Shared/services/app-config.service.d.ts +0 -51
- package/Shared/services/session.service.d.ts +0 -46
- package/Shared/shared.module.d.ts +0 -14
- package/fesm2022/cat-documents-ng.mjs +0 -11392
- package/fesm2022/cat-documents-ng.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/lib/document/components/document-actions/document-actions.component.d.ts +0 -78
- package/lib/document/components/document-container/document-container.component.d.ts +0 -162
- package/lib/document/components/document-content-viewer/document-content-viewer.component.d.ts +0 -291
- package/lib/document/components/document-history/document-history.component.d.ts +0 -160
- package/lib/document/components/document-list/document-list.component.d.ts +0 -299
- package/lib/document/components/document-list-item/document-list-item.component.d.ts +0 -28
- package/lib/document/components/document-search/document-search.component.d.ts +0 -77
- package/lib/document/components/document-status/document-status.component.d.ts +0 -24
- package/lib/document/components/document-upload/document-upload.component.d.ts +0 -321
- package/lib/document/components/document-viewer/document-viewer.component.d.ts +0 -137
- package/lib/document/components/document-zoom-controls/document-zoom-controls.component.d.ts +0 -33
- package/lib/document/components/documents-menu/documents-menu.component.d.ts +0 -110
- package/lib/document/components/folder-container/folder-container.component.d.ts +0 -162
- package/lib/document/components/linked-document/linked-document.component.d.ts +0 -39
- package/lib/document/components/request-document/request-document.component.d.ts +0 -69
- package/lib/document/components/sidebar/sidebar.component.d.ts +0 -109
- package/lib/document/components/user-list/user-list.component.d.ts +0 -34
- package/lib/document/constant/DOCUMENT_HISTORY.d.ts +0 -41
- package/lib/document/directives/document.directive.d.ts +0 -20
- package/lib/document/directives/permission.directive.d.ts +0 -38
- package/lib/document/document.module.d.ts +0 -60
- package/lib/document/models/document-category.model.d.ts +0 -24
- package/lib/document/models/document-history.model.d.ts +0 -94
- package/lib/document/models/document-list-response.model.d.ts +0 -33
- package/lib/document/models/document-type.model.d.ts +0 -37
- package/lib/document/models/document.model.d.ts +0 -44
- package/lib/document/models/status-data.model.d.ts +0 -27
- package/lib/document/models/uploaded-file-response.model.d.ts +0 -7
- package/lib/document/models/user-list.model.d.ts +0 -8
- package/lib/document/services/csv-parser.service.d.ts +0 -88
- package/lib/document/services/document-actions.service.d.ts +0 -48
- package/lib/document/services/document-content-type.service.d.ts +0 -85
- package/lib/document/services/document-history-style.service.d.ts +0 -34
- package/lib/document/services/document-history.service.d.ts +0 -42
- package/lib/document/services/document-http.service.d.ts +0 -179
- package/lib/document/services/document-list.service.d.ts +0 -74
- package/lib/document/services/document-menu.service.d.ts +0 -122
- package/lib/document/services/document-scroll.service.d.ts +0 -55
- package/lib/document/services/document-table-builder.service.d.ts +0 -38
- package/lib/document/services/document-upload-business.service.d.ts +0 -107
- package/lib/document/services/document-upload-data.service.d.ts +0 -40
- package/lib/document/services/document-upload-form.service.d.ts +0 -41
- package/lib/document/services/document-upload.service.d.ts +0 -99
- package/lib/document/services/document-viewer.service.d.ts +0 -97
- package/lib/document/services/document-zoom.service.d.ts +0 -81
- package/lib/document/services/document.service.d.ts +0 -161
- package/lib/document/services/eml-parser.service.d.ts +0 -116
- package/lib/document/services/excel-parser.service.d.ts +0 -169
- package/lib/document/services/file-format.service.d.ts +0 -34
- package/lib/document/services/status-calculator.service.d.ts +0 -20
- package/lib/document/services/user-list.service.d.ts +0 -29
- package/lib/document/state/document.query.d.ts +0 -243
- package/lib/document/state/document.state.d.ts +0 -61
- package/lib/document/state/document.store.d.ts +0 -56
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
3
|
+
import { Observable, throwError } from 'rxjs';
|
|
4
|
+
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
5
|
+
import { catchError, map } from 'rxjs/operators';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface for parsed email data
|
|
9
|
+
*/
|
|
10
|
+
export interface ParsedEmailData {
|
|
11
|
+
from: string;
|
|
12
|
+
to: string;
|
|
13
|
+
cc?: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
date: string;
|
|
16
|
+
body: string;
|
|
17
|
+
isHtml: boolean;
|
|
18
|
+
attachments?: EmailAttachment[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Interface for email attachments
|
|
23
|
+
*/
|
|
24
|
+
export interface EmailAttachment {
|
|
25
|
+
filename: string;
|
|
26
|
+
contentType: string;
|
|
27
|
+
size: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Service for parsing EML (email message) files
|
|
32
|
+
* Handles both plain text and HTML email content
|
|
33
|
+
*/
|
|
34
|
+
@Injectable({
|
|
35
|
+
providedIn: 'root'
|
|
36
|
+
})
|
|
37
|
+
export class EmlParserService {
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private sanitizer: DomSanitizer,
|
|
41
|
+
private http: HttpClient
|
|
42
|
+
) { }
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parses EML file content
|
|
46
|
+
* @param emlContent - Raw EML file content as string
|
|
47
|
+
* @returns Parsed email data
|
|
48
|
+
*/
|
|
49
|
+
parseEmlData(emlContent: string): ParsedEmailData | null {
|
|
50
|
+
try {
|
|
51
|
+
const lines = emlContent.split(/\r?\n/);
|
|
52
|
+
const headers = this.parseHeaders(lines);
|
|
53
|
+
const body = this.parseBody(lines);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
from: this.cleanEmailAddress(headers['from'] || 'Unknown'),
|
|
57
|
+
to: this.cleanEmailAddress(headers['to'] || 'Unknown'),
|
|
58
|
+
cc: headers['cc'] ? this.cleanEmailAddress(headers['cc']) : undefined,
|
|
59
|
+
subject: this.decodeHeader(headers['subject'] || 'No Subject'),
|
|
60
|
+
date: this.formatDate(headers['date'] || ''),
|
|
61
|
+
body: body.content,
|
|
62
|
+
isHtml: body.isHtml,
|
|
63
|
+
attachments: this.parseAttachments(emlContent)
|
|
64
|
+
};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error parsing EML file:', error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parses email headers from EML content
|
|
73
|
+
* @param lines - Lines of the EML file
|
|
74
|
+
* @returns Object containing parsed headers
|
|
75
|
+
*/
|
|
76
|
+
private parseHeaders(lines: string[]): { [key: string]: string } {
|
|
77
|
+
const headers: { [key: string]: string } = {};
|
|
78
|
+
let currentHeader = '';
|
|
79
|
+
let currentValue = '';
|
|
80
|
+
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
// Empty line indicates end of headers
|
|
83
|
+
if (line.trim() === '') {
|
|
84
|
+
if (currentHeader) {
|
|
85
|
+
headers[currentHeader.toLowerCase()] = currentValue.trim();
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if line is a new header (starts without whitespace and contains ':')
|
|
91
|
+
if (line.match(/^[\w-]+:/) && !line.startsWith(' ') && !line.startsWith('\t')) {
|
|
92
|
+
// Save previous header if exists
|
|
93
|
+
if (currentHeader) {
|
|
94
|
+
headers[currentHeader.toLowerCase()] = currentValue.trim();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Parse new header
|
|
98
|
+
const colonIndex = line.indexOf(':');
|
|
99
|
+
currentHeader = line.substring(0, colonIndex).trim();
|
|
100
|
+
currentValue = line.substring(colonIndex + 1).trim();
|
|
101
|
+
} else if (currentHeader && (line.startsWith(' ') || line.startsWith('\t'))) {
|
|
102
|
+
// Continuation of previous header (folded header)
|
|
103
|
+
currentValue += ' ' + line.trim();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return headers;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parses the email body from EML content
|
|
112
|
+
* @param lines - Lines of the EML file
|
|
113
|
+
* @returns Object containing body content and format information
|
|
114
|
+
*/
|
|
115
|
+
private parseBody(lines: string[]): { content: string, isHtml: boolean } {
|
|
116
|
+
let inBody = false;
|
|
117
|
+
let bodyLines: string[] = [];
|
|
118
|
+
let contentType = 'text/plain';
|
|
119
|
+
let boundary = '';
|
|
120
|
+
let isBase64 = false;
|
|
121
|
+
let isQuotedPrintable = false;
|
|
122
|
+
|
|
123
|
+
// Find Content-Type and boundary
|
|
124
|
+
for (let i = 0; i < lines.length; i++) {
|
|
125
|
+
const line = lines[i];
|
|
126
|
+
|
|
127
|
+
if (line.toLowerCase().startsWith('content-type:')) {
|
|
128
|
+
contentType = line.toLowerCase().includes('text/html') ? 'text/html' : 'text/plain';
|
|
129
|
+
|
|
130
|
+
// Check for multipart boundary
|
|
131
|
+
const boundaryMatch = line.match(/boundary="?([^";\s]+)"?/i);
|
|
132
|
+
if (boundaryMatch) {
|
|
133
|
+
boundary = boundaryMatch[1];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (line.toLowerCase().startsWith('content-transfer-encoding:')) {
|
|
138
|
+
isBase64 = line.toLowerCase().includes('base64');
|
|
139
|
+
isQuotedPrintable = line.toLowerCase().includes('quoted-printable');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Empty line marks start of body
|
|
143
|
+
if (line.trim() === '' && !inBody) {
|
|
144
|
+
inBody = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (inBody) {
|
|
149
|
+
bodyLines.push(line);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If multipart, extract the relevant part
|
|
154
|
+
if (boundary) {
|
|
155
|
+
bodyLines = this.extractMultipartBody(bodyLines.join('\n'), boundary, contentType);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let bodyContent = bodyLines.join('\n').trim();
|
|
159
|
+
|
|
160
|
+
// Decode body if necessary
|
|
161
|
+
if (isBase64) {
|
|
162
|
+
try {
|
|
163
|
+
bodyContent = atob(bodyContent.replace(/\s/g, ''));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.warn('Failed to decode base64 content');
|
|
166
|
+
}
|
|
167
|
+
} else if (isQuotedPrintable) {
|
|
168
|
+
bodyContent = this.decodeQuotedPrintable(bodyContent);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Clean up HTML if present
|
|
172
|
+
if (contentType === 'text/html') {
|
|
173
|
+
bodyContent = this.sanitizeHtmlContent(bodyContent);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
content: bodyContent || 'No content available',
|
|
178
|
+
isHtml: contentType === 'text/html'
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Extracts body from multipart email
|
|
184
|
+
* @param content - Email content
|
|
185
|
+
* @param boundary - Multipart boundary string
|
|
186
|
+
* @param preferredType - Preferred content type
|
|
187
|
+
* @returns Array of body lines
|
|
188
|
+
*/
|
|
189
|
+
private extractMultipartBody(content: string, boundary: string, preferredType: string): string[] {
|
|
190
|
+
const parts = content.split('--' + boundary);
|
|
191
|
+
|
|
192
|
+
for (const part of parts) {
|
|
193
|
+
const lines = part.split(/\r?\n/);
|
|
194
|
+
let partContentType = 'text/plain';
|
|
195
|
+
let isBase64 = false;
|
|
196
|
+
let isQuotedPrintable = false;
|
|
197
|
+
let inPartBody = false;
|
|
198
|
+
const partBodyLines: string[] = [];
|
|
199
|
+
|
|
200
|
+
for (let i = 0; i < lines.length; i++) {
|
|
201
|
+
const line = lines[i];
|
|
202
|
+
|
|
203
|
+
if (line.toLowerCase().startsWith('content-type:')) {
|
|
204
|
+
partContentType = line.toLowerCase().includes('text/html') ? 'text/html' : 'text/plain';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (line.toLowerCase().startsWith('content-transfer-encoding:')) {
|
|
208
|
+
isBase64 = line.toLowerCase().includes('base64');
|
|
209
|
+
isQuotedPrintable = line.toLowerCase().includes('quoted-printable');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (line.trim() === '' && !inPartBody) {
|
|
213
|
+
inPartBody = true;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (inPartBody) {
|
|
218
|
+
partBodyLines.push(line);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If this part matches our preferred content type, use it
|
|
223
|
+
if (partContentType === preferredType && partBodyLines.length > 0) {
|
|
224
|
+
let bodyContent = partBodyLines.join('\n');
|
|
225
|
+
|
|
226
|
+
if (isBase64) {
|
|
227
|
+
try {
|
|
228
|
+
bodyContent = atob(bodyContent.replace(/\s/g, ''));
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.warn('Failed to decode base64 part');
|
|
231
|
+
}
|
|
232
|
+
} else if (isQuotedPrintable) {
|
|
233
|
+
bodyContent = this.decodeQuotedPrintable(bodyContent);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return [bodyContent];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Fallback: return first non-empty part
|
|
241
|
+
for (const part of parts) {
|
|
242
|
+
const lines = part.split(/\r?\n/);
|
|
243
|
+
const bodyLines = lines.filter(line => line.trim() !== '').slice(3); // Skip headers
|
|
244
|
+
if (bodyLines.length > 0) {
|
|
245
|
+
return bodyLines;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Decodes quoted-printable encoded content
|
|
254
|
+
* @param content - Quoted-printable encoded string
|
|
255
|
+
* @returns Decoded string
|
|
256
|
+
*/
|
|
257
|
+
private decodeQuotedPrintable(content: string): string {
|
|
258
|
+
return content
|
|
259
|
+
.replace(/=\r?\n/g, '') // Remove soft line breaks
|
|
260
|
+
.replace(/=([0-9A-F]{2})/gi, (match, hex) => {
|
|
261
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Sanitizes HTML content for safe display
|
|
267
|
+
* @param html - Raw HTML content
|
|
268
|
+
* @returns Sanitized HTML
|
|
269
|
+
*/
|
|
270
|
+
private sanitizeHtmlContent(html: string): string {
|
|
271
|
+
// Basic sanitization - remove script tags and dangerous attributes
|
|
272
|
+
let sanitized = html
|
|
273
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
274
|
+
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
|
|
275
|
+
.replace(/javascript:/gi, '');
|
|
276
|
+
|
|
277
|
+
return sanitized;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Parses attachment information from EML content
|
|
282
|
+
* @param emlContent - Raw EML content
|
|
283
|
+
* @returns Array of attachment information
|
|
284
|
+
*/
|
|
285
|
+
private parseAttachments(emlContent: string): EmailAttachment[] {
|
|
286
|
+
const attachments: EmailAttachment[] = [];
|
|
287
|
+
const attachmentRegex = /Content-Disposition:\s*attachment[^;]*;\s*filename="?([^"\r\n]+)"?/gi;
|
|
288
|
+
let match;
|
|
289
|
+
|
|
290
|
+
while ((match = attachmentRegex.exec(emlContent)) !== null) {
|
|
291
|
+
attachments.push({
|
|
292
|
+
filename: match[1],
|
|
293
|
+
contentType: 'application/octet-stream',
|
|
294
|
+
size: 0
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return attachments;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Cleans and formats email addresses
|
|
303
|
+
* @param email - Raw email address string
|
|
304
|
+
* @returns Cleaned email address
|
|
305
|
+
*/
|
|
306
|
+
private cleanEmailAddress(email: string): string {
|
|
307
|
+
// Remove quotes and extra whitespace
|
|
308
|
+
let cleaned = email.replace(/['"]/g, '').trim();
|
|
309
|
+
|
|
310
|
+
// Extract email from format: "Name <email@example.com>"
|
|
311
|
+
const emailMatch = cleaned.match(/<([^>]+)>/);
|
|
312
|
+
if (emailMatch) {
|
|
313
|
+
const name = cleaned.substring(0, cleaned.indexOf('<')).trim();
|
|
314
|
+
const address = emailMatch[1];
|
|
315
|
+
return name ? `${name} <${address}>` : address;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return cleaned;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Decodes encoded email headers (RFC 2047)
|
|
323
|
+
* @param header - Encoded header string
|
|
324
|
+
* @returns Decoded header string
|
|
325
|
+
*/
|
|
326
|
+
private decodeHeader(header: string): string {
|
|
327
|
+
// Decode RFC 2047 encoded words (=?charset?encoding?text?=)
|
|
328
|
+
return header.replace(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/gi, (match, charset, encoding, text) => {
|
|
329
|
+
try {
|
|
330
|
+
if (encoding.toUpperCase() === 'B') {
|
|
331
|
+
// Base64 encoding
|
|
332
|
+
return atob(text);
|
|
333
|
+
} else if (encoding.toUpperCase() === 'Q') {
|
|
334
|
+
// Quoted-printable encoding
|
|
335
|
+
return this.decodeQuotedPrintable(text.replace(/_/g, ' '));
|
|
336
|
+
}
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.warn('Failed to decode header:', match);
|
|
339
|
+
}
|
|
340
|
+
return match;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Formats email date to readable format
|
|
346
|
+
* @param dateStr - Raw date string from email
|
|
347
|
+
* @returns Formatted date string
|
|
348
|
+
*/
|
|
349
|
+
private formatDate(dateStr: string): string {
|
|
350
|
+
try {
|
|
351
|
+
const date = new Date(dateStr);
|
|
352
|
+
if (isNaN(date.getTime())) {
|
|
353
|
+
return dateStr || 'Unknown date';
|
|
354
|
+
}
|
|
355
|
+
return date.toLocaleString('en-GB', {
|
|
356
|
+
day: '2-digit',
|
|
357
|
+
month: 'short',
|
|
358
|
+
year: 'numeric',
|
|
359
|
+
hour: '2-digit',
|
|
360
|
+
minute: '2-digit'
|
|
361
|
+
});
|
|
362
|
+
} catch (e) {
|
|
363
|
+
return dateStr || 'Unknown date';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Sanitizes HTML for Angular display
|
|
369
|
+
* @param html - HTML string
|
|
370
|
+
* @returns SafeHtml for Angular templates
|
|
371
|
+
*/
|
|
372
|
+
getSafeHtml(html: string): SafeHtml {
|
|
373
|
+
return this.sanitizer.sanitize(1, html) || '';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Downloads and parses an EML file from a URL
|
|
378
|
+
* @param documentUrl - URL of the EML file
|
|
379
|
+
* @returns Observable of parsed email data
|
|
380
|
+
*/
|
|
381
|
+
loadAndParseEmail(documentUrl: string): Observable<ParsedEmailData> {
|
|
382
|
+
console.log('EmlParserService: Loading email from URL:', documentUrl);
|
|
383
|
+
|
|
384
|
+
return this.downloadEmailFile(documentUrl).pipe(
|
|
385
|
+
map((emlContent: string) => {
|
|
386
|
+
console.log('EmlParserService: Email downloaded, size:', emlContent?.length || 0, 'bytes');
|
|
387
|
+
|
|
388
|
+
if (!emlContent || emlContent.trim().length === 0) {
|
|
389
|
+
throw new Error('Email file is empty or could not be loaded');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const parsedData = this.parseEmlData(emlContent);
|
|
393
|
+
if (!parsedData) {
|
|
394
|
+
throw new Error('Failed to parse email data');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
console.log('EmlParserService: Email parsed successfully:', parsedData);
|
|
398
|
+
return parsedData;
|
|
399
|
+
}),
|
|
400
|
+
catchError((error) => {
|
|
401
|
+
console.error('EmlParserService: Error loading/parsing email:', error);
|
|
402
|
+
return throwError(() => new Error(error.message || 'Failed to load or parse email file'));
|
|
403
|
+
})
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Downloads an email file with appropriate authentication handling
|
|
409
|
+
* @param documentUrl - URL of the email file
|
|
410
|
+
* @returns Observable of file content as text
|
|
411
|
+
*/
|
|
412
|
+
private downloadEmailFile(documentUrl: string): Observable<string> {
|
|
413
|
+
// Check if it's Azure Blob Storage URL
|
|
414
|
+
const isAzureBlobStorage = documentUrl.includes('.blob.core.windows.net');
|
|
415
|
+
|
|
416
|
+
if (isAzureBlobStorage) {
|
|
417
|
+
// For Azure Blob Storage, don't send Authorization header
|
|
418
|
+
// The URL should contain a SAS token or the blob should be publicly accessible
|
|
419
|
+
return this.http.get(documentUrl, {
|
|
420
|
+
responseType: 'text'
|
|
421
|
+
}).pipe(
|
|
422
|
+
catchError((error) => {
|
|
423
|
+
console.error('Error downloading email from blob storage:', error);
|
|
424
|
+
return throwError(() => new Error('Failed to download email file. Please check if the file URL is valid and accessible.'));
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
} else {
|
|
428
|
+
// For other storage types, use Bearer token authentication
|
|
429
|
+
const Authorization = `Bearer d8764575-d713-4e31-aeb0-29ea31057461`;
|
|
430
|
+
return this.http.get(documentUrl, {
|
|
431
|
+
headers: new HttpHeaders({
|
|
432
|
+
'Authorization': Authorization
|
|
433
|
+
}),
|
|
434
|
+
responseType: 'text'
|
|
435
|
+
}).pipe(
|
|
436
|
+
catchError((error) => {
|
|
437
|
+
console.error('Error downloading email file:', error);
|
|
438
|
+
return throwError(() => new Error('Failed to download email file. Please check authentication.'));
|
|
439
|
+
})
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { ExcelParserService } from './excel-parser.service';
|
|
3
|
+
|
|
4
|
+
describe('ExcelParserService', () => {
|
|
5
|
+
let service: ExcelParserService;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
TestBed.configureTestingModule({});
|
|
9
|
+
service = TestBed.inject(ExcelParserService);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should be created', () => {
|
|
13
|
+
expect(service).toBeTruthy();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should convert RGB to hex correctly', () => {
|
|
17
|
+
// Test 6-character format
|
|
18
|
+
expect(service['rgbToHex']('ED7D31')).toBe('#ed7d31');
|
|
19
|
+
|
|
20
|
+
// Test 8-character format
|
|
21
|
+
expect(service['rgbToHex']('FFFF0000')).toBe('#ff0000');
|
|
22
|
+
|
|
23
|
+
// Test invalid input
|
|
24
|
+
expect(service['rgbToHex']('')).toBe('');
|
|
25
|
+
expect(service['rgbToHex']('12345')).toBe('');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should convert theme colors correctly', () => {
|
|
29
|
+
expect(service['themeToHex'](0)).toBe('#000000'); // Black
|
|
30
|
+
expect(service['themeToHex'](1)).toBe('#FFFFFF'); // White
|
|
31
|
+
expect(service['themeToHex'](2)).toBe('#FF0000'); // Red
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should convert indexed colors correctly', () => {
|
|
35
|
+
expect(service['indexedToHex'](0)).toBe('#000000'); // Black
|
|
36
|
+
expect(service['indexedToHex'](1)).toBe('#FFFFFF'); // White
|
|
37
|
+
expect(service['indexedToHex'](64)).toBe('#ED7D31'); // Orange
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should ensure data consistency', () => {
|
|
41
|
+
const testData = [
|
|
42
|
+
{ cells: [{ value: 'A1' }, { value: 'B1' }] },
|
|
43
|
+
{ cells: [{ value: 'A2' }] }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const result = service.ensureDataConsistency(testData);
|
|
47
|
+
|
|
48
|
+
expect(result.length).toBe(2);
|
|
49
|
+
expect(result[0].cells.length).toBe(3); // Padded to min 3 columns
|
|
50
|
+
expect(result[1].cells.length).toBe(3); // Padded to min 3 columns
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should convert to legacy format', () => {
|
|
54
|
+
const testData = [
|
|
55
|
+
{ cells: [{ value: 'A1' }, { value: 'B1' }] },
|
|
56
|
+
{ cells: [{ value: 'A2' }, { value: 'B2' }] }
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const result = service.convertToLegacyFormat(testData);
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual([
|
|
62
|
+
['A1', 'B1'],
|
|
63
|
+
['A2', 'B2']
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
});
|