]> git.saurik.com Git - wxWidgets.git/blame - src/unix/mimetype.cpp
Added chapter on collection and container classes to contents
[wxWidgets.git] / src / unix / mimetype.cpp
CommitLineData
b13d92d1 1/////////////////////////////////////////////////////////////////////////////
7dc3cc31 2// Name: unix/mimetype.cpp
b13d92d1
VZ
3// Purpose: classes and functions to manage MIME types
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 23.09.98
7// RCS-ID: $Id$
8// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9// Licence: wxWindows license (part of wxExtra library)
10/////////////////////////////////////////////////////////////////////////////
11
f6bcfd97
BP
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20#ifdef __GNUG__
21 #pragma implementation "mimetype.h"
b13d92d1
VZ
22#endif
23
b13d92d1
VZ
24// for compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
ce4169a4 28 #pragma hdrstop
b13d92d1
VZ
29#endif
30
b13d92d1 31#ifndef WX_PRECOMP
ce4169a4
RR
32 #include "wx/defs.h"
33#endif
34
f6bcfd97 35#if wxUSE_FILE && wxUSE_TEXTFILE
ce4169a4
RR
36
37#ifndef WX_PRECOMP
38 #include "wx/string.h"
b12915c1
VZ
39 #if wxUSE_GUI
40 #include "wx/icon.h"
41 #endif
b13d92d1
VZ
42#endif //WX_PRECOMP
43
3d05544e 44
b13d92d1 45#include "wx/log.h"
ce4169a4 46#include "wx/file.h"
b13d92d1
VZ
47#include "wx/intl.h"
48#include "wx/dynarray.h"
2432b92d 49#include "wx/confbase.h"
b13d92d1 50
7dc3cc31
VS
51#include "wx/ffile.h"
52#include "wx/textfile.h"
53#include "wx/dir.h"
54#include "wx/utils.h"
55#include "wx/tokenzr.h"
b13d92d1 56
7dc3cc31 57#include "wx/unix/mimetype.h"
b13d92d1
VZ
58
59// other standard headers
60#include <ctype.h>
61
b12915c1
VZ
62// in case we're compiling in non-GUI mode
63class WXDLLEXPORT wxIcon;
64
f6bcfd97
BP
65// ----------------------------------------------------------------------------
66// constants
67// ----------------------------------------------------------------------------
68
69// MIME code tracing mask
70#define TRACE_MIME _T("mime")
71
72// ----------------------------------------------------------------------------
73// private functions
74// ----------------------------------------------------------------------------
75
76// there are some fields which we don't understand but for which we don't give
77// warnings as we know that they're not important - this function is used to
78// test for them
79static bool IsKnownUnimportantField(const wxString& field);
80
b13d92d1
VZ
81// ----------------------------------------------------------------------------
82// private classes
83// ----------------------------------------------------------------------------
84
b13d92d1
VZ
85
86// this class uses both mailcap and mime.types to gather information about file
87// types.
88//
89// The information about mailcap file was extracted from metamail(1) sources and
90// documentation.
91//
92// Format of mailcap file: spaces are ignored, each line is either a comment
93// (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
94// A backslash can be used to quote semicolons and newlines (and, in fact,
95// anything else including itself).
96//
97// The first field is always the MIME type in the form of type/subtype (see RFC
98// 822) where subtype may be '*' meaning "any". Following metamail, we accept
99// "type" which means the same as "type/*", although I'm not sure whether this
100// is standard.
101//
102// The second field is always the command to run. It is subject to
103// parameter/filename expansion described below.
104//
105// All the following fields are optional and may not be present at all. If
106// they're present they may appear in any order, although each of them should
107// appear only once. The optional fields are the following:
108// * notes=xxx is an uninterpreted string which is silently ignored
109// * test=xxx is the command to be used to determine whether this mailcap line
110// applies to our data or not. The RHS of this field goes through the
111// parameter/filename expansion (as the 2nd field) and the resulting string
112// is executed. The line applies only if the command succeeds, i.e. returns 0
113// exit code.
114// * print=xxx is the command to be used to print (and not view) the data of
115// this type (parameter/filename expansion is done here too)
116// * edit=xxx is the command to open/edit the data of this type
117// * needsterminal means that a new console must be created for the viewer
118// * copiousoutput means that the viewer doesn't interact with the user but
119// produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
120// good example), thus it might be a good idea to use some kind of paging
121// mechanism.
122// * textualnewlines means not to perform CR/LF translation (not honored)
123// * compose and composetyped fields are used to determine the program to be
124// called to create a new message pert in the specified format (unused).
125//
ec4768ef 126// Parameter/filename xpansion:
b13d92d1
VZ
127// * %s is replaced with the (full) file name
128// * %t is replaced with MIME type/subtype of the entry
129// * for multipart type only %n is replaced with the nnumber of parts and %F is
130// replaced by an array of (content-type, temporary file name) pairs for all
131// message parts (TODO)
132// * %{parameter} is replaced with the value of parameter taken from
133// Content-type header line of the message.
134//
135// FIXME any docs with real descriptions of these files??
136//
137// There are 2 possible formats for mime.types file, one entry per line (used
138// for global mime.types) and "expanded" format where an entry takes multiple
139// lines (used for users mime.types).
140//
141// For both formats spaces are ignored and lines starting with a '#' are
142// comments. Each record has one of two following forms:
143// a) for "brief" format:
144// <mime type> <space separated list of extensions>
ec4768ef 145// b) for "expanded" format:
b13d92d1
VZ
146// type=<mime type> \ desc="<description>" \ exts="ext"
147//
148// We try to autodetect the format of mime.types: if a non-comment line starts
149// with "type=" we assume the second format, otherwise the first one.
150
151// there may be more than one entry for one and the same mime type, to
152// choose the right one we have to run the command specified in the test
153// field on our data.
154class MailCapEntry
155{
156public:
157 // ctor
158 MailCapEntry(const wxString& openCmd,
159 const wxString& printCmd,
160 const wxString& testCmd)
161 : m_openCmd(openCmd), m_printCmd(printCmd), m_testCmd(testCmd)
162 {
163 m_next = NULL;
164 }
cf471cab
VS
165
166 ~MailCapEntry()
167 {
168 if (m_next) delete m_next;
169 }
b13d92d1
VZ
170
171 // accessors
172 const wxString& GetOpenCmd() const { return m_openCmd; }
173 const wxString& GetPrintCmd() const { return m_printCmd; }
174 const wxString& GetTestCmd() const { return m_testCmd; }
175
176 MailCapEntry *GetNext() const { return m_next; }
177
178 // operations
179 // prepend this element to the list
180 void Prepend(MailCapEntry *next) { m_next = next; }
cc385968
VZ
181 // insert into the list at given position
182 void Insert(MailCapEntry *next, size_t pos)
183 {
184 // FIXME slooow...
185 MailCapEntry *cur;
186 size_t n = 0;
187 for ( cur = next; cur != NULL; cur = cur->m_next, n++ ) {
188 if ( n == pos )
189 break;
190 }
191
223d09f6 192 wxASSERT_MSG( n == pos, wxT("invalid position in MailCapEntry::Insert") );
cc385968
VZ
193
194 m_next = cur->m_next;
195 cur->m_next = this;
196 }
197 // append this element to the list
b13d92d1
VZ
198 void Append(MailCapEntry *next)
199 {
223d09f6 200 wxCHECK_RET( next != NULL, wxT("Append()ing to what?") );
cc385968 201
b13d92d1
VZ
202 // FIXME slooow...
203 MailCapEntry *cur;
204 for ( cur = next; cur->m_next != NULL; cur = cur->m_next )
205 ;
206
207 cur->m_next = this;
208
223d09f6 209 wxASSERT_MSG( !m_next, wxT("Append()ing element already in the list?") );
b13d92d1
VZ
210 }
211
212private:
213 wxString m_openCmd, // command to use to open/view the file
214 m_printCmd, // print
215 m_testCmd; // only apply this entry if test yields
216 // true (i.e. the command returns 0)
217
218 MailCapEntry *m_next; // in the linked list
219};
220
b13d92d1 221
b9517a0a
VZ
222// the base class which may be used to find an icon for the MIME type
223class wxMimeTypeIconHandler
224{
225public:
226 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon) = 0;
5bd3a2da
VZ
227
228 // this function fills manager with MIME types information gathered
cdf339c9
VS
229 // (as side effect) when searching for icons. This may be particularly
230 // useful if mime.types is incomplete (e.g. RedHat distributions).
231 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager) = 0;
b9517a0a
VZ
232};
233
b9517a0a
VZ
234
235// the icon handler which uses GNOME MIME database
236class wxGNOMEIconHandler : public wxMimeTypeIconHandler
237{
238public:
239 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
4d2976ad 240 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
5bd3a2da 241
b9517a0a
VZ
242private:
243 void Init();
244 void LoadIconsFromKeyFile(const wxString& filename);
245 void LoadKeyFilesFromDir(const wxString& dirbase);
246
4d2976ad
VS
247 void LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager);
248 void LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager);
249
b9517a0a
VZ
250 static bool m_inited;
251
252 static wxSortedArrayString ms_mimetypes;
253 static wxArrayString ms_icons;
254};
255
256// the icon handler which uses KDE MIME database
257class wxKDEIconHandler : public wxMimeTypeIconHandler
258{
259public:
260 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
cdf339c9 261 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
b9517a0a
VZ
262
263private:
264 void LoadLinksForMimeSubtype(const wxString& dirbase,
265 const wxString& subdir,
cdf339c9
VS
266 const wxString& filename,
267 const wxArrayString& icondirs);
b9517a0a 268 void LoadLinksForMimeType(const wxString& dirbase,
cdf339c9
VS
269 const wxString& subdir,
270 const wxArrayString& icondirs);
5bd3a2da 271 void LoadLinkFilesFromDir(const wxString& dirbase,
cdf339c9 272 const wxArrayString& icondirs);
b9517a0a
VZ
273 void Init();
274
275 static bool m_inited;
276
277 static wxSortedArrayString ms_mimetypes;
278 static wxArrayString ms_icons;
cdf339c9
VS
279
280 static wxArrayString ms_infoTypes;
281 static wxArrayString ms_infoDescriptions;
282 static wxArrayString ms_infoExtensions;
b9517a0a
VZ
283};
284
b13d92d1 285
b9517a0a
VZ
286
287// ----------------------------------------------------------------------------
288// various statics
289// ----------------------------------------------------------------------------
290
291static wxGNOMEIconHandler gs_iconHandlerGNOME;
292static wxKDEIconHandler gs_iconHandlerKDE;
293
294bool wxGNOMEIconHandler::m_inited = FALSE;
295wxSortedArrayString wxGNOMEIconHandler::ms_mimetypes;
296wxArrayString wxGNOMEIconHandler::ms_icons;
297
298bool wxKDEIconHandler::m_inited = FALSE;
299wxSortedArrayString wxKDEIconHandler::ms_mimetypes;
300wxArrayString wxKDEIconHandler::ms_icons;
301
cdf339c9
VS
302wxArrayString wxKDEIconHandler::ms_infoTypes;
303wxArrayString wxKDEIconHandler::ms_infoDescriptions;
304wxArrayString wxKDEIconHandler::ms_infoExtensions;
305
306
b9517a0a
VZ
307ArrayIconHandlers wxMimeTypesManagerImpl::ms_iconHandlers;
308
309// ----------------------------------------------------------------------------
310// wxGNOMEIconHandler
311// ----------------------------------------------------------------------------
312
313// GNOME stores the info we're interested in in several locations:
314// 1. xxx.keys files under /usr/share/mime-info
315// 2. xxx.keys files under ~/.gnome/mime-info
316//
317// The format of xxx.keys file is the following:
318//
319// mimetype/subtype:
320// field=value
321//
322// with blank lines separating the entries and indented lines starting with
323// TABs. We're interested in the field icon-filename whose value is the path
324// containing the icon.
325
326void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString& filename)
327{
328 wxTextFile textfile(filename);
329 if ( !textfile.Open() )
330 return;
331
332 // values for the entry being parsed
333 wxString curMimeType, curIconFile;
334
335 const wxChar *pc;
336 size_t nLineCount = textfile.GetLineCount();
337 for ( size_t nLine = 0; ; nLine++ )
338 {
339 if ( nLine < nLineCount )
340 {
341 pc = textfile[nLine].c_str();
342 if ( *pc == _T('#') )
343 {
344 // skip comments
345 continue;
346 }
347 }
348 else
349 {
350 // so that we will fall into the "if" below
351 pc = NULL;
352 }
353
354 if ( !pc || !*pc )
355 {
356 // end of the entry
357 if ( !!curMimeType && !!curIconFile )
358 {
359 // do we already know this mimetype?
360 int i = ms_mimetypes.Index(curMimeType);
361 if ( i == wxNOT_FOUND )
362 {
363 // add a new entry
364 size_t n = ms_mimetypes.Add(curMimeType);
365 ms_icons.Insert(curIconFile, n);
366 }
367 else
368 {
369 // replace the existing one (this means that the directories
370 // should be searched in order of increased priority!)
371 ms_icons[(size_t)i] = curIconFile;
372 }
373 }
374
375 if ( !pc )
376 {
377 // the end - this can only happen if nLine == nLineCount
378 break;
379 }
380
381 curIconFile.Empty();
382
383 continue;
384 }
385
386 // what do we have here?
387 if ( *pc == _T('\t') )
388 {
389 // this is a field=value ling
390 pc++; // skip leading TAB
391
392 static const int lenField = 13; // strlen("icon-filename")
393 if ( wxStrncmp(pc, _T("icon-filename"), lenField) == 0 )
394 {
395 // skip '=' which follows and take everything left until the end
396 // of line
397 curIconFile = pc + lenField + 1;
398 }
399 //else: some other field, we don't care
400 }
401 else
402 {
403 // this is the start of the new section
404 curMimeType.Empty();
405
406 while ( *pc != _T(':') && *pc != _T('\0') )
407 {
408 curMimeType += *pc++;
409 }
4d2976ad
VS
410 }
411 }
412}
413
414void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString& dirbase)
415{
416 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
417 _T("base directory shouldn't end with a slash") );
418
419 wxString dirname = dirbase;
420 dirname << _T("/mime-info");
421
422 if ( !wxDir::Exists(dirname) )
423 return;
424
425 wxDir dir(dirname);
426 if ( !dir.IsOpened() )
427 return;
428
429 // we will concatenate it with filename to get the full path below
430 dirname += _T('/');
431
432 wxString filename;
433 bool cont = dir.GetFirst(&filename, _T("*.keys"), wxDIR_FILES);
434 while ( cont )
435 {
436 LoadIconsFromKeyFile(dirname + filename);
437
438 cont = dir.GetNext(&filename);
439 }
440}
441
442
443void wxGNOMEIconHandler::LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager)
444{
445 wxTextFile textfile(filename);
446 if ( !textfile.Open() )
447 return;
448
449 // values for the entry being parsed
450 wxString curMimeType, curExtList;
451
452 const wxChar *pc;
453 size_t nLineCount = textfile.GetLineCount();
454 for ( size_t nLine = 0; ; nLine++ )
455 {
456 if ( nLine < nLineCount )
457 {
458 pc = textfile[nLine].c_str();
459 if ( *pc == _T('#') )
460 {
461 // skip comments
462 continue;
463 }
464 }
465 else
466 {
467 // so that we will fall into the "if" below
468 pc = NULL;
469 }
b9517a0a 470
4d2976ad
VS
471 if ( !pc || !*pc )
472 {
473 // end of the entry
474 if ( !!curMimeType && !!curExtList )
b9517a0a 475 {
4d2976ad
VS
476 manager -> AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
477 }
b9517a0a 478
4d2976ad
VS
479 if ( !pc )
480 {
481 // the end - this can only happen if nLine == nLineCount
b9517a0a
VZ
482 break;
483 }
4d2976ad
VS
484
485 curExtList.Empty();
486
487 continue;
488 }
489
490 // what do we have here?
491 if ( *pc == _T('\t') )
492 {
493 // this is a field=value ling
494 pc++; // skip leading TAB
495
496 static const int lenField = 4; // strlen("ext:")
497 if ( wxStrncmp(pc, _T("ext:"), lenField) == 0 )
498 {
499 // skip ' ' which follows and take everything left until the end
500 // of line
501 curExtList = pc + lenField + 1;
502 }
503 //else: some other field, we don't care
504 }
505 else
506 {
507 // this is the start of the new section
508 curMimeType.Empty();
509
510 while ( *pc != _T(':') && *pc != _T('\0') )
511 {
512 curMimeType += *pc++;
513 }
b9517a0a
VZ
514 }
515 }
516}
517
4d2976ad
VS
518
519void wxGNOMEIconHandler::LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager)
b9517a0a
VZ
520{
521 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
522 _T("base directory shouldn't end with a slash") );
523
524 wxString dirname = dirbase;
525 dirname << _T("/mime-info");
526
527 if ( !wxDir::Exists(dirname) )
528 return;
529
530 wxDir dir(dirname);
531 if ( !dir.IsOpened() )
532 return;
533
534 // we will concatenate it with filename to get the full path below
535 dirname += _T('/');
536
537 wxString filename;
4d2976ad 538 bool cont = dir.GetFirst(&filename, _T("*.mime"), wxDIR_FILES);
b9517a0a
VZ
539 while ( cont )
540 {
4d2976ad 541 LoadMimeTypesFromMimeFile(dirname + filename, manager);
b9517a0a
VZ
542
543 cont = dir.GetNext(&filename);
544 }
545}
546
4d2976ad 547
b9517a0a
VZ
548void wxGNOMEIconHandler::Init()
549{
550 wxArrayString dirs;
551 dirs.Add(_T("/usr/share"));
be0a33fb 552 dirs.Add(_T("/usr/local/share"));
fb5ddb4c 553
af159c44
RR
554 wxString gnomedir;
555 wxGetHomeDir( &gnomedir );
556 gnomedir += _T("/.gnome");
557 dirs.Add( gnomedir );
b9517a0a
VZ
558
559 size_t nDirs = dirs.GetCount();
560 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
561 {
562 LoadKeyFilesFromDir(dirs[nDir]);
563 }
564
565 m_inited = TRUE;
566}
567
4d2976ad
VS
568
569void wxGNOMEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
570{
571 if ( !m_inited )
572 {
573 Init();
574 }
575
576 wxArrayString dirs;
577 dirs.Add(_T("/usr/share"));
578 dirs.Add(_T("/usr/local/share"));
579
580 wxString gnomedir;
581 wxGetHomeDir( &gnomedir );
582 gnomedir += _T("/.gnome");
583 dirs.Add( gnomedir );
584
585 size_t nDirs = dirs.GetCount();
586 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
587 {
588 LoadMimeFilesFromDir(dirs[nDir], manager);
589 }
590}
591
6dc6fda6
VZ
592#if wxUSE_GUI
593 #define WXUNUSED_UNLESS_GUI(p) p
594#else
595 #define WXUNUSED_UNLESS_GUI(p)
596#endif
4d2976ad 597
6dc6fda6
VZ
598bool wxGNOMEIconHandler::GetIcon(const wxString& mimetype,
599 wxIcon * WXUNUSED_UNLESS_GUI(icon))
b9517a0a
VZ
600{
601 if ( !m_inited )
602 {
603 Init();
604 }
605
606 int index = ms_mimetypes.Index(mimetype);
607 if ( index == wxNOT_FOUND )
608 return FALSE;
609
610 wxString iconname = ms_icons[(size_t)index];
611
612#if wxUSE_GUI
be0a33fb
VS
613 wxLogNull nolog;
614 wxIcon icn;
615 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
616 icn = wxIcon(iconname);
617 else
618 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
6dc6fda6
VZ
619 if ( !icn.Ok() )
620 return FALSE;
621
622 if ( icon )
623 *icon = icn;
b9517a0a
VZ
624#else
625 // helpful for testing in console mode
626 wxLogDebug(_T("Found GNOME icon for '%s': '%s'\n"),
627 mimetype.c_str(), iconname.c_str());
628#endif
629
630 return TRUE;
631}
632
633// ----------------------------------------------------------------------------
634// wxKDEIconHandler
635// ----------------------------------------------------------------------------
636
637// KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
638// may be found in either of the following locations
639//
509a6196 640// 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
b9517a0a
VZ
641// 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
642//
643// The format of a .kdelnk file is almost the same as the one used by
644// wxFileConfig, i.e. there are groups, comments and entries. The icon is the
645// value for the entry "Type"
646
647void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString& dirbase,
648 const wxString& subdir,
cdf339c9
VS
649 const wxString& filename,
650 const wxArrayString& icondirs)
b9517a0a
VZ
651{
652 wxFFile file(dirbase + filename);
653 if ( !file.IsOpened() )
654 return;
655
cdf339c9
VS
656 // construct mimetype from the directory name and the basename of the
657 // file (it always has .kdelnk extension)
658 wxString mimetype;
659 mimetype << subdir << _T('/') << filename.BeforeLast(_T('.'));
660
b9517a0a
VZ
661 // these files are small, slurp the entire file at once
662 wxString text;
663 if ( !file.ReadAll(&text) )
664 return;
665
cdf339c9
VS
666 int pos;
667 const wxChar *pc;
668
669 // before trying to find an icon, grab mimetype information
670 // (because BFU's machine would hardly have well-edited mime.types but (s)he might
671 // have edited it in control panel...)
672
673 wxString mime_extension, mime_desc;
674
5bd3a2da 675 pos = wxNOT_FOUND;
cdf339c9
VS
676 if (wxGetLocale() != NULL)
677 mime_desc = _T("Comment[") + wxGetLocale()->GetName() + _T("]=");
678 if (pos == wxNOT_FOUND) mime_desc = _T("Comment=");
679 pos = text.Find(mime_desc);
680 if (pos == wxNOT_FOUND) mime_desc = wxEmptyString;
681 else
682 {
683 pc = text.c_str() + pos + mime_desc.Length();
684 mime_desc = wxEmptyString;
685 while ( *pc && *pc != _T('\n') ) mime_desc += *pc++;
686 }
687
688 pos = text.Find(_T("Patterns="));
689 if (pos != wxNOT_FOUND)
690 {
691 wxString exts;
692 pc = text.c_str() + pos + 9;
693 while ( *pc && *pc != _T('\n') ) exts += *pc++;
694 wxStringTokenizer tokenizer(exts, _T(";"));
695 wxString e;
5bd3a2da 696
cdf339c9
VS
697 while (tokenizer.HasMoreTokens())
698 {
699 e = tokenizer.GetNextToken();
700 if (e.Left(2) != _T("*.")) continue; // don't support too difficult patterns
701 mime_extension << e.Mid(2);
702 mime_extension << _T(' ');
703 }
704 mime_extension.RemoveLast();
705 }
5bd3a2da 706
cdf339c9
VS
707 ms_infoTypes.Add(mimetype);
708 ms_infoDescriptions.Add(mime_desc);
709 ms_infoExtensions.Add(mime_extension);
710
711 // ok, now we can take care of icon:
712
713 pos = text.Find(_T("Icon="));
b9517a0a
VZ
714 if ( pos == wxNOT_FOUND )
715 {
716 // no icon info
717 return;
718 }
719
720 wxString icon;
721
cdf339c9 722 pc = text.c_str() + pos + 5; // 5 == strlen("Icon=")
b9517a0a
VZ
723 while ( *pc && *pc != _T('\n') )
724 {
725 icon += *pc++;
726 }
727
728 if ( !!icon )
729 {
cdf339c9
VS
730 // we must check if the file exists because it may be stored
731 // in many locations, at least ~/.kde and $KDEDIR
732 size_t nDir, nDirs = icondirs.GetCount();
733 for ( nDir = 0; nDir < nDirs; nDir++ )
734 if (wxFileExists(icondirs[nDir] + icon))
735 {
736 icon.Prepend(icondirs[nDir]);
737 break;
738 }
739 if (nDir == nDirs) return; //does not exist
b9517a0a
VZ
740
741 // do we already have this MIME type?
742 int i = ms_mimetypes.Index(mimetype);
743 if ( i == wxNOT_FOUND )
744 {
745 // add it
746 size_t n = ms_mimetypes.Add(mimetype);
747 ms_icons.Insert(icon, n);
748 }
749 else
750 {
751 // replace the old value
752 ms_icons[(size_t)i] = icon;
753 }
754 }
755}
756
757void wxKDEIconHandler::LoadLinksForMimeType(const wxString& dirbase,
cdf339c9
VS
758 const wxString& subdir,
759 const wxArrayString& icondirs)
b9517a0a
VZ
760{
761 wxString dirname = dirbase;
762 dirname += subdir;
763 wxDir dir(dirname);
764 if ( !dir.IsOpened() )
765 return;
766
767 dirname += _T('/');
768
769 wxString filename;
770 bool cont = dir.GetFirst(&filename, _T("*.kdelnk"), wxDIR_FILES);
771 while ( cont )
772 {
cdf339c9 773 LoadLinksForMimeSubtype(dirname, subdir, filename, icondirs);
b9517a0a
VZ
774
775 cont = dir.GetNext(&filename);
776 }
777}
778
cdf339c9
VS
779void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString& dirbase,
780 const wxArrayString& icondirs)
b9517a0a
VZ
781{
782 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
783 _T("base directory shouldn't end with a slash") );
784
785 wxString dirname = dirbase;
786 dirname << _T("/mimelnk");
787
788 if ( !wxDir::Exists(dirname) )
789 return;
790
791 wxDir dir(dirname);
792 if ( !dir.IsOpened() )
793 return;
794
795 // we will concatenate it with dir name to get the full path below
796 dirname += _T('/');
797
798 wxString subdir;
799 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
800 while ( cont )
801 {
cdf339c9 802 LoadLinksForMimeType(dirname, subdir, icondirs);
b9517a0a
VZ
803
804 cont = dir.GetNext(&subdir);
805 }
806}
807
808void wxKDEIconHandler::Init()
809{
810 wxArrayString dirs;
cdf339c9
VS
811 wxArrayString icondirs;
812
813 // settings in ~/.kde have maximal priority
814 dirs.Add(wxGetHomeDir() + _T("/.kde/share"));
815 icondirs.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
509a6196
VZ
816
817 // the variable KDEDIR is set when KDE is running
818 const char *kdedir = getenv("KDEDIR");
819 if ( kdedir )
820 {
821 dirs.Add(wxString(kdedir) + _T("/share"));
cdf339c9 822 icondirs.Add(wxString(kdedir) + _T("/share/icons/"));
509a6196
VZ
823 }
824 else
825 {
826 // try to guess KDEDIR
827 dirs.Add(_T("/usr/share"));
828 dirs.Add(_T("/opt/kde/share"));
cdf339c9 829 icondirs.Add(_T("/usr/share/icons/"));
13e3b45a 830 icondirs.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
cdf339c9 831 icondirs.Add(_T("/opt/kde/share/icons/"));
509a6196
VZ
832 }
833
b9517a0a
VZ
834 size_t nDirs = dirs.GetCount();
835 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
836 {
cdf339c9 837 LoadLinkFilesFromDir(dirs[nDir], icondirs);
b9517a0a
VZ
838 }
839
840 m_inited = TRUE;
841}
842
6dc6fda6
VZ
843bool wxKDEIconHandler::GetIcon(const wxString& mimetype,
844 wxIcon * WXUNUSED_UNLESS_GUI(icon))
b9517a0a
VZ
845{
846 if ( !m_inited )
847 {
848 Init();
849 }
850
851 int index = ms_mimetypes.Index(mimetype);
852 if ( index == wxNOT_FOUND )
853 return FALSE;
854
855 wxString iconname = ms_icons[(size_t)index];
856
857#if wxUSE_GUI
be0a33fb
VS
858 wxLogNull nolog;
859 wxIcon icn;
860 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
861 icn = wxIcon(iconname);
862 else
863 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
6dc6fda6
VZ
864
865 if ( !icn.Ok() )
866 return FALSE;
867
868 if ( icon )
869 *icon = icn;
b9517a0a
VZ
870#else
871 // helpful for testing in console mode
872 wxLogDebug(_T("Found KDE icon for '%s': '%s'\n"),
873 mimetype.c_str(), iconname.c_str());
874#endif
875
876 return TRUE;
877}
878
cdf339c9
VS
879
880void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
881{
882 if ( !m_inited ) Init();
5bd3a2da 883
cdf339c9
VS
884 size_t cnt = ms_infoTypes.GetCount();
885 for (unsigned i = 0; i < cnt; i++)
886 manager -> AddMimeTypeInfo(ms_infoTypes[i], ms_infoExtensions[i], ms_infoDescriptions[i]);
887}
888
889
b9517a0a
VZ
890// ----------------------------------------------------------------------------
891// wxFileTypeImpl (Unix)
892// ----------------------------------------------------------------------------
893
b13d92d1
VZ
894MailCapEntry *
895wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const
896{
897 wxString command;
4d2976ad 898 MailCapEntry *entry = m_manager->m_aEntries[m_index[0]];
b13d92d1 899 while ( entry != NULL ) {
cc385968 900 // notice that an empty command would always succeed (it's ok)
b13d92d1
VZ
901 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
902
f6bcfd97
BP
903 // suppress the command output
904 if ( !command.IsEmpty() )
905 {
906 if ( wxSystem(command) == 0 ) {
907 // ok, passed
908 wxLogTrace(TRACE_MIME,
909 wxT("Test '%s' for mime type '%s' succeeded."),
910 command.c_str(), params.GetMimeType().c_str());
911 break;
912 }
913 else {
914 wxLogTrace(TRACE_MIME,
915 wxT("Test '%s' for mime type '%s' failed."),
916 command.c_str(), params.GetMimeType().c_str());
917 }
b13d92d1
VZ
918 }
919
920 entry = entry->GetNext();
921 }
922
923 return entry;
924}
925
b9517a0a
VZ
926bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
927{
4d2976ad
VS
928 wxArrayString mimetypes;
929 GetMimeTypes(mimetypes);
b9517a0a
VZ
930
931 ArrayIconHandlers& handlers = m_manager->GetIconHandlers();
932 size_t count = handlers.GetCount();
4d2976ad 933 size_t counttypes = mimetypes.GetCount();
b9517a0a
VZ
934 for ( size_t n = 0; n < count; n++ )
935 {
4d2976ad
VS
936 for ( size_t n2 = 0; n2 < counttypes; n2++ )
937 {
938 if ( handlers[n]->GetIcon(mimetypes[n2], icon) )
939 return TRUE;
940 }
b9517a0a
VZ
941 }
942
943 return FALSE;
944}
945
4d2976ad
VS
946
947bool
948wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
949{
950 mimeTypes.Clear();
951 for (size_t i = 0; i < m_index.GetCount(); i++)
952 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
953 return TRUE;
954}
955
956
b13d92d1
VZ
957bool
958wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
959 const wxFileType::MessageParameters& params,
960 bool open) const
961{
962 MailCapEntry *entry = GetEntry(params);
963 if ( entry == NULL ) {
964 // all tests failed...
965 return FALSE;
966 }
967
968 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
969 if ( cmd.IsEmpty() ) {
970 // may happen, especially for "print"
971 return FALSE;
972 }
973
974 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
975 return TRUE;
976}
977
978bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
979{
4d2976ad 980 wxString strExtensions = m_manager->GetExtension(m_index[0]);
b13d92d1
VZ
981 extensions.Empty();
982
983 // one extension in the space or comma delimitid list
984 wxString strExt;
50920146 985 for ( const wxChar *p = strExtensions; ; p++ ) {
223d09f6 986 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) {
b13d92d1
VZ
987 if ( !strExt.IsEmpty() ) {
988 extensions.Add(strExt);
989 strExt.Empty();
990 }
991 //else: repeated spaces (shouldn't happen, but it's not that
992 // important if it does happen)
993
223d09f6 994 if ( *p == wxT('\0') )
b13d92d1
VZ
995 break;
996 }
223d09f6 997 else if ( *p == wxT('.') ) {
b13d92d1
VZ
998 // remove the dot from extension (but only if it's the first char)
999 if ( !strExt.IsEmpty() ) {
223d09f6 1000 strExt += wxT('.');
b13d92d1
VZ
1001 }
1002 //else: no, don't append it
1003 }
1004 else {
1005 strExt += *p;
1006 }
1007 }
1008
1009 return TRUE;
1010}
1011
b9517a0a
VZ
1012// ----------------------------------------------------------------------------
1013// wxMimeTypesManagerImpl (Unix)
1014// ----------------------------------------------------------------------------
1015
1016/* static */
1017ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers()
1018{
1019 if ( ms_iconHandlers.GetCount() == 0 )
1020 {
be0a33fb 1021 ms_iconHandlers.Add(&gs_iconHandlerGNOME);
4d2976ad 1022 ms_iconHandlers.Add(&gs_iconHandlerKDE);
b9517a0a
VZ
1023 }
1024
1025 return ms_iconHandlers;
1026}
1027
b13d92d1
VZ
1028// read system and user mailcaps (TODO implement mime.types support)
1029wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1030{
1031 // directories where we look for mailcap and mime.types by default
1032 // (taken from metamail(1) sources)
50920146 1033 static const wxChar *aStandardLocations[] =
b13d92d1 1034 {
223d09f6
KB
1035 wxT("/etc"),
1036 wxT("/usr/etc"),
1037 wxT("/usr/local/etc"),
1038 wxT("/etc/mail"),
1039 wxT("/usr/public/lib")
b13d92d1
VZ
1040 };
1041
1042 // first read the system wide file(s)
5bd3a2da
VZ
1043 size_t n;
1044 for ( n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
b13d92d1
VZ
1045 wxString dir = aStandardLocations[n];
1046
223d09f6 1047 wxString file = dir + wxT("/mailcap");
b13d92d1
VZ
1048 if ( wxFile::Exists(file) ) {
1049 ReadMailcap(file);
1050 }
1051
223d09f6 1052 file = dir + wxT("/mime.types");
b13d92d1
VZ
1053 if ( wxFile::Exists(file) ) {
1054 ReadMimeTypes(file);
1055 }
1056 }
1057
223d09f6 1058 wxString strHome = wxGetenv(wxT("HOME"));
b13d92d1
VZ
1059
1060 // and now the users mailcap
223d09f6 1061 wxString strUserMailcap = strHome + wxT("/.mailcap");
b13d92d1
VZ
1062 if ( wxFile::Exists(strUserMailcap) ) {
1063 ReadMailcap(strUserMailcap);
1064 }
1065
1066 // read the users mime.types
223d09f6 1067 wxString strUserMimeTypes = strHome + wxT("/.mime.types");
b13d92d1
VZ
1068 if ( wxFile::Exists(strUserMimeTypes) ) {
1069 ReadMimeTypes(strUserMimeTypes);
1070 }
4d2976ad
VS
1071
1072 // read KDE/GNOME tables
1073 ArrayIconHandlers& handlers = GetIconHandlers();
1074 size_t count = handlers.GetCount();
1075 for ( size_t hn = 0; hn < count; hn++ )
1076 handlers[hn]->GetMimeInfoRecords(this);
b13d92d1
VZ
1077}
1078
cf471cab
VS
1079
1080wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1081{
1082 size_t cnt = m_aEntries.GetCount();
1083 for (size_t i = 0; i < cnt; i++) delete m_aEntries[i];
1084}
1085
1086
b13d92d1
VZ
1087wxFileType *
1088wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1089{
4d2976ad 1090 wxFileType *fileType = NULL;
1ee17e1c
VZ
1091 size_t count = m_aExtensions.GetCount();
1092 for ( size_t n = 0; n < count; n++ ) {
1093 wxString extensions = m_aExtensions[n];
1094 while ( !extensions.IsEmpty() ) {
223d09f6
KB
1095 wxString field = extensions.BeforeFirst(wxT(' '));
1096 extensions = extensions.AfterFirst(wxT(' '));
1ee17e1c
VZ
1097
1098 // consider extensions as not being case-sensitive
ec4768ef 1099 if ( field.IsSameAs(ext, FALSE /* no case */) ) {
1ee17e1c 1100 // found
4d2976ad 1101 if (fileType == NULL) fileType = new wxFileType;
1ee17e1c 1102 fileType->m_impl->Init(this, n);
4d2976ad 1103 // adds this mime type to _list_ of mime types with this extension
1ee17e1c
VZ
1104 }
1105 }
1106 }
b13d92d1 1107
4d2976ad 1108 return fileType;
b13d92d1
VZ
1109}
1110
1111wxFileType *
1112wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1113{
1114 // mime types are not case-sensitive
1115 wxString mimetype(mimeType);
1116 mimetype.MakeLower();
1117
1118 // first look for an exact match
1119 int index = m_aTypes.Index(mimetype);
3c67202d 1120 if ( index == wxNOT_FOUND ) {
b13d92d1 1121 // then try to find "text/*" as match for "text/plain" (for example)
3c67202d
VZ
1122 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1123 // the whole string - ok.
223d09f6 1124 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
b13d92d1
VZ
1125
1126 size_t nCount = m_aTypes.Count();
1127 for ( size_t n = 0; n < nCount; n++ ) {
223d09f6
KB
1128 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1129 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) {
b13d92d1
VZ
1130 index = n;
1131 break;
1132 }
1133 }
1134 }
1135
3c67202d 1136 if ( index != wxNOT_FOUND ) {
b13d92d1
VZ
1137 wxFileType *fileType = new wxFileType;
1138 fileType->m_impl->Init(this, index);
1139
1140 return fileType;
1141 }
1142 else {
1143 // not found...
1144 return NULL;
1145 }
1146}
1147
8e124873
VZ
1148void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1149{
3f1aaa16 1150 wxString extensions;
8e124873
VZ
1151 const wxArrayString& exts = filetype.GetExtensions();
1152 size_t nExts = exts.GetCount();
1153 for ( size_t nExt = 0; nExt < nExts; nExt++ ) {
1154 if ( nExt > 0 ) {
223d09f6 1155 extensions += wxT(' ');
8e124873
VZ
1156 }
1157 extensions += exts[nExt];
1158 }
1159
1160 AddMimeTypeInfo(filetype.GetMimeType(),
1161 extensions,
1162 filetype.GetDescription());
1163
1164 AddMailcapInfo(filetype.GetMimeType(),
1165 filetype.GetOpenCommand(),
1166 filetype.GetPrintCommand(),
223d09f6 1167 wxT(""),
8e124873
VZ
1168 filetype.GetDescription());
1169}
1170
1171void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1172 const wxString& strExtensions,
1173 const wxString& strDesc)
1174{
1175 int index = m_aTypes.Index(strMimeType);
1176 if ( index == wxNOT_FOUND ) {
1177 // add a new entry
1178 m_aTypes.Add(strMimeType);
1179 m_aEntries.Add(NULL);
1180 m_aExtensions.Add(strExtensions);
1181 m_aDescriptions.Add(strDesc);
1182 }
1183 else {
1184 // modify an existing one
1185 if ( !strDesc.IsEmpty() ) {
1186 m_aDescriptions[index] = strDesc; // replace old value
1187 }
c15d098c 1188 m_aExtensions[index] += ' ' + strExtensions;
8e124873
VZ
1189 }
1190}
1191
1192void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
1193 const wxString& strOpenCmd,
1194 const wxString& strPrintCmd,
1195 const wxString& strTest,
1196 const wxString& strDesc)
1197{
1198 MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest);
1199
1200 int nIndex = m_aTypes.Index(strType);
1201 if ( nIndex == wxNOT_FOUND ) {
1202 // new file type
1203 m_aTypes.Add(strType);
1204
1205 m_aEntries.Add(entry);
223d09f6 1206 m_aExtensions.Add(wxT(""));
8e124873
VZ
1207 m_aDescriptions.Add(strDesc);
1208 }
1209 else {
1210 // always append the entry in the tail of the list - info added with
1211 // this function can only come from AddFallbacks()
1212 MailCapEntry *entryOld = m_aEntries[nIndex];
1213 if ( entryOld )
1214 entry->Append(entryOld);
1215 else
1216 m_aEntries[nIndex] = entry;
1217 }
1218}
1219
cc385968 1220bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
b13d92d1 1221{
f6bcfd97
BP
1222 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
1223 strFileName.c_str());
b13d92d1
VZ
1224
1225 wxTextFile file(strFileName);
1226 if ( !file.Open() )
cc385968 1227 return FALSE;
b13d92d1
VZ
1228
1229 // the information we extract
1230 wxString strMimeType, strDesc, strExtensions;
1231
1232 size_t nLineCount = file.GetLineCount();
50920146 1233 const wxChar *pc = NULL;
b13d92d1 1234 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
22b4634c
VZ
1235 if ( pc == NULL ) {
1236 // now we're at the start of the line
1237 pc = file[nLine].c_str();
1238 }
1239 else {
1240 // we didn't finish with the previous line yet
1241 nLine--;
1242 }
b13d92d1
VZ
1243
1244 // skip whitespace
50920146 1245 while ( wxIsspace(*pc) )
b13d92d1
VZ
1246 pc++;
1247
54acce90
VZ
1248 // comment or blank line?
1249 if ( *pc == wxT('#') || !*pc ) {
22b4634c
VZ
1250 // skip the whole line
1251 pc = NULL;
b13d92d1 1252 continue;
22b4634c 1253 }
b13d92d1
VZ
1254
1255 // detect file format
223d09f6 1256 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
b13d92d1
VZ
1257 if ( pEqualSign == NULL ) {
1258 // brief format
1259 // ------------
1260
1261 // first field is mime type
223d09f6 1262 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) {
b13d92d1
VZ
1263 strMimeType += *pc;
1264 }
1265
1266 // skip whitespace
50920146 1267 while ( wxIsspace(*pc) )
b13d92d1
VZ
1268 pc++;
1269
1270 // take all the rest of the string
1271 strExtensions = pc;
1272
1273 // no description...
1274 strDesc.Empty();
1275 }
1276 else {
1277 // expanded format
1278 // ---------------
1279
1280 // the string on the left of '=' is the field name
1281 wxString strLHS(pc, pEqualSign - pc);
1282
1283 // eat whitespace
50920146 1284 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
b13d92d1
VZ
1285 ;
1286
50920146 1287 const wxChar *pEnd;
223d09f6 1288 if ( *pc == wxT('"') ) {
b13d92d1 1289 // the string is quoted and ends at the matching quote
223d09f6 1290 pEnd = wxStrchr(++pc, wxT('"'));
b13d92d1
VZ
1291 if ( pEnd == NULL ) {
1292 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1293 "quoted string."),
1294 strFileName.c_str(), nLine + 1);
1295 }
1296 }
1297 else {
8862e11b
VZ
1298 // unquoted string ends at the first space or at the end of
1299 // line
1300 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
b13d92d1
VZ
1301 ;
1302 }
1303
1304 // now we have the RHS (field value)
1305 wxString strRHS(pc, pEnd - pc);
1306
22b4634c 1307 // check what follows this entry
223d09f6 1308 if ( *pEnd == wxT('"') ) {
b13d92d1
VZ
1309 // skip this quote
1310 pEnd++;
1311 }
1312
50920146 1313 for ( pc = pEnd; wxIsspace(*pc); pc++ )
b13d92d1
VZ
1314 ;
1315
22b4634c
VZ
1316 // if there is something left, it may be either a '\\' to continue
1317 // the line or the next field of the same entry
223d09f6 1318 bool entryEnded = *pc == wxT('\0'),
22b4634c
VZ
1319 nextFieldOnSameLine = FALSE;
1320 if ( !entryEnded ) {
223d09f6 1321 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
b13d92d1 1322 }
b13d92d1
VZ
1323
1324 // now see what we got
223d09f6 1325 if ( strLHS == wxT("type") ) {
b13d92d1
VZ
1326 strMimeType = strRHS;
1327 }
223d09f6 1328 else if ( strLHS == wxT("desc") ) {
b13d92d1
VZ
1329 strDesc = strRHS;
1330 }
223d09f6 1331 else if ( strLHS == wxT("exts") ) {
b13d92d1
VZ
1332 strExtensions = strRHS;
1333 }
1334 else {
1335 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1336 strFileName.c_str(), nLine + 1, strLHS.c_str());
1337 }
1338
1339 if ( !entryEnded ) {
22b4634c
VZ
1340 if ( !nextFieldOnSameLine )
1341 pc = NULL;
1342 //else: don't reset it
1343
1344 // as we don't reset strMimeType, the next field in this entry
b13d92d1 1345 // will be interpreted correctly.
22b4634c 1346
b13d92d1
VZ
1347 continue;
1348 }
1349 }
1350
a1d8eaf7
VZ
1351 // although it doesn't seem to be covered by RFCs, some programs
1352 // (notably Netscape) create their entries with several comma
1353 // separated extensions (RFC mention the spaces only)
223d09f6 1354 strExtensions.Replace(wxT(","), wxT(" "));
a1d8eaf7
VZ
1355
1356 // also deal with the leading dot
1b986aef
VZ
1357 if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') )
1358 {
a1d8eaf7
VZ
1359 strExtensions.erase(0, 1);
1360 }
1361
8e124873 1362 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
22b4634c
VZ
1363
1364 // finished with this line
1365 pc = NULL;
b13d92d1
VZ
1366 }
1367
1368 // check our data integriry
1369 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1370 m_aTypes.Count() == m_aExtensions.Count() &&
1371 m_aTypes.Count() == m_aDescriptions.Count() );
cc385968
VZ
1372
1373 return TRUE;
b13d92d1
VZ
1374}
1375
cc385968
VZ
1376bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
1377 bool fallback)
b13d92d1 1378{
f6bcfd97
BP
1379 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
1380 strFileName.c_str());
b13d92d1
VZ
1381
1382 wxTextFile file(strFileName);
1383 if ( !file.Open() )
cc385968 1384 return FALSE;
b13d92d1 1385
cc385968
VZ
1386 // see the comments near the end of function for the reason we need these
1387 // variables (search for the next occurence of them)
1388 // indices of MIME types (in m_aTypes) we already found in this file
b13d92d1 1389 wxArrayInt aEntryIndices;
cc385968
VZ
1390 // aLastIndices[n] is the index of last element in
1391 // m_aEntries[aEntryIndices[n]] from this file
1392 wxArrayInt aLastIndices;
b13d92d1
VZ
1393
1394 size_t nLineCount = file.GetLineCount();
1395 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1396 // now we're at the start of the line
50920146 1397 const wxChar *pc = file[nLine].c_str();
b13d92d1
VZ
1398
1399 // skip whitespace
50920146 1400 while ( wxIsspace(*pc) )
b13d92d1
VZ
1401 pc++;
1402
1403 // comment or empty string?
223d09f6 1404 if ( *pc == wxT('#') || *pc == wxT('\0') )
b13d92d1
VZ
1405 continue;
1406
1407 // no, do parse
1408
1409 // what field are we currently in? The first 2 are fixed and there may
1410 // be an arbitrary number of other fields -- currently, we are not
1411 // interested in any of them, but we should parse them as well...
1412 enum
1413 {
1414 Field_Type,
1415 Field_OpenCmd,
1416 Field_Other
1417 } currentToken = Field_Type;
1418
1419 // the flags and field values on the current line
8fc613f1
RR
1420 bool needsterminal = FALSE,
1421 copiousoutput = FALSE;
b13d92d1
VZ
1422 wxString strType,
1423 strOpenCmd,
1424 strPrintCmd,
1425 strTest,
1426 strDesc,
1427 curField; // accumulator
1428 for ( bool cont = TRUE; cont; pc++ ) {
1429 switch ( *pc ) {
223d09f6 1430 case wxT('\\'):
b13d92d1
VZ
1431 // interpret the next character literally (notice that
1432 // backslash can be used for line continuation)
223d09f6 1433 if ( *++pc == wxT('\0') ) {
b13d92d1
VZ
1434 // fetch the next line.
1435
1436 // pc currently points to nowhere, but after the next
1437 // pc++ in the for line it will point to the beginning
1438 // of the next line in the file
1439 pc = file[++nLine].c_str() - 1;
1440 }
1441 else {
1442 // just a normal character
1443 curField += *pc;
1444 }
1445 break;
1446
223d09f6 1447 case wxT('\0'):
b13d92d1
VZ
1448 cont = FALSE; // end of line reached, exit the loop
1449
1450 // fall through
1451
223d09f6 1452 case wxT(';'):
b13d92d1
VZ
1453 // store this field and start looking for the next one
1454
1455 // trim whitespaces from both sides
1456 curField.Trim(TRUE).Trim(FALSE);
1457
1458 switch ( currentToken ) {
1459 case Field_Type:
1460 strType = curField;
223d09f6 1461 if ( strType.Find(wxT('/')) == wxNOT_FOUND ) {
b13d92d1 1462 // we interpret "type" as "type/*"
223d09f6 1463 strType += wxT("/*");
b13d92d1
VZ
1464 }
1465
1466 currentToken = Field_OpenCmd;
1467 break;
1468
1469 case Field_OpenCmd:
1470 strOpenCmd = curField;
1471
1472 currentToken = Field_Other;
1473 break;
1474
1475 case Field_Other:
1476 {
1477 // "good" mailcap entry?
1478 bool ok = TRUE;
1479
1480 // is this something of the form foo=bar?
223d09f6 1481 const wxChar *pEq = wxStrchr(curField, wxT('='));
b13d92d1 1482 if ( pEq != NULL ) {
223d09f6
KB
1483 wxString lhs = curField.BeforeFirst(wxT('=')),
1484 rhs = curField.AfterFirst(wxT('='));
b13d92d1
VZ
1485
1486 lhs.Trim(TRUE); // from right
1487 rhs.Trim(FALSE); // from left
1488
223d09f6 1489 if ( lhs == wxT("print") )
b13d92d1 1490 strPrintCmd = rhs;
223d09f6 1491 else if ( lhs == wxT("test") )
b13d92d1 1492 strTest = rhs;
223d09f6 1493 else if ( lhs == wxT("description") ) {
b13d92d1 1494 // it might be quoted
223d09f6
KB
1495 if ( rhs[0u] == wxT('"') &&
1496 rhs.Last() == wxT('"') ) {
b13d92d1
VZ
1497 strDesc = wxString(rhs.c_str() + 1,
1498 rhs.Len() - 2);
1499 }
1500 else {
1501 strDesc = rhs;
1502 }
1503 }
223d09f6
KB
1504 else if ( lhs == wxT("compose") ||
1505 lhs == wxT("composetyped") ||
1506 lhs == wxT("notes") ||
1507 lhs == wxT("edit") )
b13d92d1
VZ
1508 ; // ignore
1509 else
1510 ok = FALSE;
1511
1512 }
1513 else {
1514 // no, it's a simple flag
1515 // TODO support the flags:
1516 // 1. create an xterm for 'needsterminal'
1517 // 2. append "| $PAGER" for 'copiousoutput'
223d09f6 1518 if ( curField == wxT("needsterminal") )
b13d92d1 1519 needsterminal = TRUE;
223d09f6 1520 else if ( curField == wxT("copiousoutput") )
b13d92d1 1521 copiousoutput = TRUE;
223d09f6 1522 else if ( curField == wxT("textualnewlines") )
b13d92d1
VZ
1523 ; // ignore
1524 else
1525 ok = FALSE;
1526 }
1527
1528 if ( !ok )
1529 {
f6bcfd97 1530 if ( !IsKnownUnimportantField(curField) )
54acce90
VZ
1531 {
1532 // don't flood the user with error
1533 // messages if we don't understand
1534 // something in his mailcap, but give
1535 // them in debug mode because this might
1536 // be useful for the programmer
1537 wxLogDebug
1538 (
1539 wxT("Mailcap file %s, line %d: "
1540 "unknown field '%s' for the "
1541 "MIME type '%s' ignored."),
1542 strFileName.c_str(),
1543 nLine + 1,
1544 curField.c_str(),
1545 strType.c_str()
1546 );
1547 }
b13d92d1
VZ
1548 }
1549 }
1550
1551 // it already has this value
1552 //currentToken = Field_Other;
1553 break;
1554
1555 default:
223d09f6 1556 wxFAIL_MSG(wxT("unknown field type in mailcap"));
b13d92d1
VZ
1557 }
1558
1559 // next token starts immediately after ';'
1560 curField.Empty();
1561 break;
1562
1563 default:
1564 curField += *pc;
1565 }
1566 }
1567
1568 // check that we really read something reasonable
1569 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1570 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1571 "ignored."),
1572 strFileName.c_str(), nLine + 1);
1573 }
1574 else {
1575 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1576 strPrintCmd,
1577 strTest);
1578
8e124873
VZ
1579 // NB: because of complications below (we must get entries priority
1580 // right), we can't use AddMailcapInfo() here, unfortunately.
b13d92d1
VZ
1581 strType.MakeLower();
1582 int nIndex = m_aTypes.Index(strType);
3c67202d 1583 if ( nIndex == wxNOT_FOUND ) {
b13d92d1
VZ
1584 // new file type
1585 m_aTypes.Add(strType);
1586
1587 m_aEntries.Add(entry);
223d09f6 1588 m_aExtensions.Add(wxT(""));
b13d92d1
VZ
1589 m_aDescriptions.Add(strDesc);
1590 }
1591 else {
cc385968
VZ
1592 // modify the existing entry: the entries in one and the same
1593 // file are read in top-to-bottom order, i.e. the entries read
1594 // first should be tried before the entries below. However,
1595 // the files read later should override the settings in the
1596 // files read before (except if fallback is TRUE), thus we
1597 // Insert() the new entry to the list if it has already
1598 // occured in _this_ file, but Prepend() it if it occured in
1599 // some of the previous ones and Append() to it in the
1600 // fallback case
1601
1602 if ( fallback ) {
1603 // 'fallback' parameter prevents the entries from this
1604 // file from overriding the other ones - always append
1605 MailCapEntry *entryOld = m_aEntries[nIndex];
1606 if ( entryOld )
1607 entry->Append(entryOld);
1608 else
1609 m_aEntries[nIndex] = entry;
b13d92d1
VZ
1610 }
1611 else {
cc385968
VZ
1612 int entryIndex = aEntryIndices.Index(nIndex);
1613 if ( entryIndex == wxNOT_FOUND ) {
1614 // first time in this file
1615 aEntryIndices.Add(nIndex);
1616 aLastIndices.Add(0);
1617
1618 entry->Prepend(m_aEntries[nIndex]);
1619 m_aEntries[nIndex] = entry;
1620 }
1621 else {
1622 // not the first time in _this_ file
1623 size_t nEntryIndex = (size_t)entryIndex;
1624 MailCapEntry *entryOld = m_aEntries[nIndex];
1625 if ( entryOld )
1626 entry->Insert(entryOld, aLastIndices[nEntryIndex]);
1627 else
1628 m_aEntries[nIndex] = entry;
1629
1630 // the indices were shifted by 1
1631 aLastIndices[nEntryIndex]++;
1632 }
b13d92d1
VZ
1633 }
1634
1635 if ( !strDesc.IsEmpty() ) {
cc385968 1636 // replace the old one - what else can we do??
b13d92d1
VZ
1637 m_aDescriptions[nIndex] = strDesc;
1638 }
1639 }
1640 }
1641
1642 // check our data integriry
1643 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1644 m_aTypes.Count() == m_aExtensions.Count() &&
1645 m_aTypes.Count() == m_aDescriptions.Count() );
1646 }
cc385968
VZ
1647
1648 return TRUE;
b13d92d1
VZ
1649}
1650
696e1ea0 1651size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1b986aef 1652{
54acce90
VZ
1653 mimetypes.Empty();
1654
1655 wxString type;
1656 size_t count = m_aTypes.GetCount();
1657 for ( size_t n = 0; n < count; n++ )
1658 {
1659 // don't return template types from here (i.e. anything containg '*')
1660 type = m_aTypes[n];
1661 if ( type.Find(_T('*')) == wxNOT_FOUND )
1662 {
1663 mimetypes.Add(type);
1664 }
1665 }
1b986aef 1666
54acce90 1667 return mimetypes.GetCount();
1b986aef
VZ
1668}
1669
f6bcfd97
BP
1670// ----------------------------------------------------------------------------
1671// private functions
1672// ----------------------------------------------------------------------------
1673
1674static bool IsKnownUnimportantField(const wxString& fieldAll)
1675{
1676 static const wxChar *knownFields[] =
1677 {
1678 _T("x-mozilla-flags"),
1679 _T("nametemplate"),
1680 _T("textualnewlines"),
1681 };
1682
1683 wxString field = fieldAll.BeforeFirst(_T('='));
1684 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
1685 {
1686 if ( field.CmpNoCase(knownFields[n]) == 0 )
1687 return TRUE;
1688 }
1689
1690 return FALSE;
1691}
1692
8e124873 1693#endif
ce4169a4 1694 // wxUSE_FILE && wxUSE_TEXTFILE
b13d92d1 1695