forked from p2/quicklook-csv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGeneratePreviewForURL.m
240 lines (201 loc) · 7.99 KB
/
GeneratePreviewForURL.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//
// GeneratePreviewForURL.m
// QuickLookCSV
//
// Created by Pascal Pfiffner on 03.07.2009.
// This sourcecode is released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <QuickLook/QuickLook.h>
#import <Cocoa/Cocoa.h>
#import "CSVDocument.h"
#import "CSVRowObject.h"
#define MAX_ROWS 500
static char* htmlReadableFileEncoding(NSStringEncoding stringEncoding);
static char* humanReadableFileEncoding(NSStringEncoding stringEncoding);
static char* formatFilesize(float bytes);
/**
* Generates a preview for the given file.
*
* This function parses the CSV and generates an HTML string that can be presented by QuickLook.
*/
OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
@autoreleasepool {
NSError *theErr = nil;
NSURL *myURL = (__bridge NSURL *)url;
// Load document data using NSStrings house methods
// For huge files, maybe guess file encoding using `file --brief --mime` and use NSFileHandle? Not for now...
NSStringEncoding stringEncoding;
NSString *fileString = [NSString stringWithContentsOfURL:myURL usedEncoding:&stringEncoding error:&theErr];
// We could not open the file, probably unknown encoding; try ISO-8859-1
if (!fileString) {
stringEncoding = NSISOLatin1StringEncoding;
fileString = [NSString stringWithContentsOfURL:myURL encoding:stringEncoding error:&theErr];
// Still no success, give up
if (!fileString) {
if (nil != theErr) {
NSLog(@"Error opening the file: %@", theErr);
}
return noErr;
}
}
// Parse the data if still interested in the preview
if (false == QLPreviewRequestIsCancelled(preview)) {
CSVDocument *csvDoc = [CSVDocument new];
csvDoc.autoDetectSeparator = YES;
NSUInteger numRowsParsed = [csvDoc numRowsFromCSVString:fileString maxRows:MAX_ROWS error:NULL];
// Create HTML of the data if still interested in the preview
if (false == QLPreviewRequestIsCancelled(preview)) {
NSBundle *myBundle = [NSBundle bundleForClass:[CSVDocument class]];
NSString *cssPath = [myBundle pathForResource:@"Style" ofType:@"css"];
NSString *css = [[NSString alloc] initWithContentsOfFile:cssPath encoding:NSUTF8StringEncoding error:NULL];
NSString *path = [myURL path];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
// compose the html
NSMutableString *html = [[NSMutableString alloc] initWithString:@"<!DOCTYPE html>\n"];
[html appendString:@"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\"><head>\n"];
[html appendFormat:@"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n", htmlReadableFileEncoding(stringEncoding)];
[html appendString:@"<style>\n"];
if (nil != css) {
[html appendString:css];
}
// info
NSString *separatorDesc = [@" " isEqualToString:csvDoc.separator] ? @"Tab" :
([@"," isEqualToString:csvDoc.separator] ? @"Comma" :
([@"|" isEqualToString:csvDoc.separator] ? @"Pipe" :
([@";" isEqualToString:csvDoc.separator] ? @"Semicolon" : csvDoc.separator)));
NSString *numRows = (numRowsParsed > MAX_ROWS) ?
[NSString stringWithFormat:@"%i+", MAX_ROWS] :
[NSString stringWithFormat:@"%lu", (unsigned long)numRowsParsed];
[html appendFormat:@"</style></head><body><div class=\"file_info\"><b>%lu</b> %@, <b>%@</b> %@</div>",
(unsigned long)[csvDoc.columnKeys count],
(1 == [csvDoc.columnKeys count]) ? NSLocalizedString(@"column", nil) : NSLocalizedString(@"columns", nil),
numRows,
(1 == numRowsParsed) ? NSLocalizedString(@"row", nil) : NSLocalizedString(@"rows", nil)
];
[html appendFormat:@"<div class=\"file_info\"><b>%s</b>, %@-%@, %s</div><table>",
formatFilesize([fileAttributes[NSFileSize] floatValue]),
NSLocalizedString(separatorDesc, nil),
NSLocalizedString(@"Separated", nil),
humanReadableFileEncoding(stringEncoding)
];
// add the table rows
BOOL altRow = NO;
for (CSVRowObject *row in csvDoc.rows) {
[html appendFormat:@"<tr%@><td>", altRow ? @" class=\"alt_row\"" : @""];
[html appendString:[row columns:csvDoc.columnKeys combinedByString:@"</td><td>"]];
[html appendString:@"</td></tr>\n"];
altRow = !altRow;
}
[html appendString:@"</table>\n"];
// not all rows were parsed, show hint
if (numRowsParsed > MAX_ROWS) {
NSString *rowsHint = [NSString stringWithFormat:NSLocalizedString(@"Only the first %i rows are being displayed", nil), MAX_ROWS];
[html appendFormat:@"<div class=\"truncated_rows\">%@</div>", rowsHint];
}
[html appendString:@"</html>"];
// feed the HTML
CFDictionaryRef properties = (__bridge CFDictionaryRef)@{};
QLPreviewRequestSetDataRepresentation(preview,
(__bridge CFDataRef)[html dataUsingEncoding:stringEncoding],
kUTTypeHTML,
properties
);
}
}
}
return noErr;
}
void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview)
{
}
#pragma mark - Output Utilities
/**
* To be used for the generated HTML.
*/
static char* htmlReadableFileEncoding(NSStringEncoding stringEncoding)
{
if (NSUTF8StringEncoding == stringEncoding ||
NSUnicodeStringEncoding == stringEncoding) {
return "utf-8";
}
if (NSASCIIStringEncoding == stringEncoding) {
return "ascii";
}
if (NSISOLatin1StringEncoding == stringEncoding) {
return "iso-8859-1";
}
if (NSMacOSRomanStringEncoding == stringEncoding) {
return "x-mac-roman";
}
if (NSUTF16BigEndianStringEncoding == stringEncoding ||
NSUTF16LittleEndianStringEncoding == stringEncoding) {
return "utf-16";
}
if (NSUTF32StringEncoding == stringEncoding ||
NSUTF32BigEndianStringEncoding == stringEncoding ||
NSUTF32LittleEndianStringEncoding == stringEncoding) {
return "utf-32";
}
}if (NSShiftJISStringEncoding == stringEncoding || // CP932
2147483649 == stringEncoding || // Shift JIS (Mac)
2147485224 == stringEncoding || // Shift JIS X0213
2147486209 == stringEncoding || // Shift JIS
2147551745 == stringEncoding) { // Shift_JIS (?)
return "shift_jis";
}}
return "utf-8";
}
static char* humanReadableFileEncoding(NSStringEncoding stringEncoding)
{
if (NSUTF8StringEncoding == stringEncoding ||
NSUnicodeStringEncoding == stringEncoding) {
return "UTF-8";
}
if (NSASCIIStringEncoding == stringEncoding) {
return "ASCII-text";
}
if (NSISOLatin1StringEncoding == stringEncoding) {
return "ISO-8859-1";
}
if (NSMacOSRomanStringEncoding == stringEncoding) {
return "Mac-Roman";
}
if (NSUTF16BigEndianStringEncoding == stringEncoding ||
NSUTF16LittleEndianStringEncoding == stringEncoding) {
return "UTF-16";
}
if (NSUTF32StringEncoding == stringEncoding ||
NSUTF32BigEndianStringEncoding == stringEncoding ||
NSUTF32LittleEndianStringEncoding == stringEncoding) {
return "UTF-32";
}
if (NSShiftJISStringEncoding == stringEncoding || // CP932 (8)
2147483649 == stringEncoding || // Shift_JIS (Mac) NSMacOSJapaneseStringEncoding
2147485224 == stringEncoding || // Shift_JIS X0213 NSShiftJIS_X0213StringEncoding
2147486209 == stringEncoding || // Shift_JIS NSShiftJISStringEncoding
2147551745 == stringEncoding) { // Shift_JIS (?)
return "Shift_JIS";
}
return "UTF-8";
}
static char* formatFilesize(float bytes) {
if (bytes < 1) {
return "";
}
char *format[] = { "%.0f", "%.0f", "%.2f", "%.2f", "%.2f", "%.2f" };
char *unit[] = { "Byte", "KB", "MB", "GB", "TB", "PB" };
int i = 0;
while (bytes > 1000) {
bytes /= 1000; // Since OS X 10.7 (or 10.6?) Apple uses "kilobyte" and no longer "Kilobyte" or "kibibyte"
i++;
}
char formatString[10];
static char result[9]; // longest string can be "999 Byte" or "999.99 GB"
sprintf(formatString, "%s %s", format[i], unit[i]);
sprintf(result, formatString, bytes);
return result;
}