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