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