1 /////////////////////////////////////////////////////////////////////////////
2 // Name: common/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
13 #pragma implementation "mimetype.h"
16 // ============================================================================
18 // ============================================================================
20 // ----------------------------------------------------------------------------
22 // ----------------------------------------------------------------------------
24 // for compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
33 #include "wx/string.h"
37 // Doesn't compile in WIN16 mode
42 #include "wx/dynarray.h"
43 #include "wx/confbase.h"
46 #include "wx/msw/registry.h"
49 #include "wx/textfile.h"
52 #include "wx/mimetype.h"
54 // other standard headers
57 // ----------------------------------------------------------------------------
59 // ----------------------------------------------------------------------------
61 // implementation classes, platform dependent
64 // These classes use Windows registry to retrieve the required information.
66 // Keys used (not all of them are documented, so it might actually stop working
67 // in futur versions of Windows...):
68 // 1. "HKCR\MIME\Database\Content Type" contains subkeys for all known MIME
69 // types, each key has a string value "Extension" which gives (dot preceded)
70 // extension for the files of this MIME type.
72 // 2. "HKCR\.ext" contains
73 // a) unnamed value containing the "filetype"
74 // b) value "Content Type" containing the MIME type
76 // 3. "HKCR\filetype" contains
77 // a) unnamed value containing the description
78 // b) subkey "DefaultIcon" with single unnamed value giving the icon index in
80 // c) shell\open\command and shell\open\print subkeys containing the commands
81 // to open/print the file (the positional parameters are introduced by %1,
82 // %2, ... in these strings, we change them to %s ourselves)
90 // initialize us with our file type name
91 void SetFileType(const wxString
& strFileType
)
92 { m_strFileType
= strFileType
; }
93 void SetExt(const wxString
& ext
)
96 // implement accessor functions
97 bool GetExtensions(wxArrayString
& extensions
);
98 bool GetMimeType(wxString
*mimeType
) const;
99 bool GetIcon(wxIcon
*icon
) const;
100 bool GetDescription(wxString
*desc
) const;
101 bool GetOpenCommand(wxString
*openCmd
,
102 const wxFileType::MessageParameters
&) const
103 { return GetCommand(openCmd
, "open"); }
104 bool GetPrintCommand(wxString
*printCmd
,
105 const wxFileType::MessageParameters
&) const
106 { return GetCommand(printCmd
, "print"); }
110 bool GetCommand(wxString
*command
, const char *verb
) const;
112 wxString m_strFileType
, m_ext
;
115 class wxMimeTypesManagerImpl
118 // nothing to do here, we don't load any data but just go and fetch it from
119 // the registry when asked for
120 wxMimeTypesManagerImpl() { }
122 // implement containing class functions
123 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
124 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
126 // this are NOPs under Windows
127 bool ReadMailcap(const wxString
& filename
, bool fallback
= TRUE
) { }
128 bool ReadMimeTypes(const wxString
& filename
) { }
133 // this class uses both mailcap and mime.types to gather information about file
136 // The information about mailcap file was extracted from metamail(1) sources and
139 // Format of mailcap file: spaces are ignored, each line is either a comment
140 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
141 // A backslash can be used to quote semicolons and newlines (and, in fact,
142 // anything else including itself).
144 // The first field is always the MIME type in the form of type/subtype (see RFC
145 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
146 // "type" which means the same as "type/*", although I'm not sure whether this
149 // The second field is always the command to run. It is subject to
150 // parameter/filename expansion described below.
152 // All the following fields are optional and may not be present at all. If
153 // they're present they may appear in any order, although each of them should
154 // appear only once. The optional fields are the following:
155 // * notes=xxx is an uninterpreted string which is silently ignored
156 // * test=xxx is the command to be used to determine whether this mailcap line
157 // applies to our data or not. The RHS of this field goes through the
158 // parameter/filename expansion (as the 2nd field) and the resulting string
159 // is executed. The line applies only if the command succeeds, i.e. returns 0
161 // * print=xxx is the command to be used to print (and not view) the data of
162 // this type (parameter/filename expansion is done here too)
163 // * edit=xxx is the command to open/edit the data of this type
164 // * needsterminal means that a new console must be created for the viewer
165 // * copiousoutput means that the viewer doesn't interact with the user but
166 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
167 // good example), thus it might be a good idea to use some kind of paging
169 // * textualnewlines means not to perform CR/LF translation (not honored)
170 // * compose and composetyped fields are used to determine the program to be
171 // called to create a new message pert in the specified format (unused).
173 // Parameter/filename xpansion:
174 // * %s is replaced with the (full) file name
175 // * %t is replaced with MIME type/subtype of the entry
176 // * for multipart type only %n is replaced with the nnumber of parts and %F is
177 // replaced by an array of (content-type, temporary file name) pairs for all
178 // message parts (TODO)
179 // * %{parameter} is replaced with the value of parameter taken from
180 // Content-type header line of the message.
182 // FIXME any docs with real descriptions of these files??
184 // There are 2 possible formats for mime.types file, one entry per line (used
185 // for global mime.types) and "expanded" format where an entry takes multiple
186 // lines (used for users mime.types).
188 // For both formats spaces are ignored and lines starting with a '#' are
189 // comments. Each record has one of two following forms:
190 // a) for "brief" format:
191 // <mime type> <space separated list of extensions>
192 // b) for "expanded" format:
193 // type=<mime type> \ desc="<description>" \ exts="ext"
195 // We try to autodetect the format of mime.types: if a non-comment line starts
196 // with "type=" we assume the second format, otherwise the first one.
198 // there may be more than one entry for one and the same mime type, to
199 // choose the right one we have to run the command specified in the test
200 // field on our data.
205 MailCapEntry(const wxString
& openCmd
,
206 const wxString
& printCmd
,
207 const wxString
& testCmd
)
208 : m_openCmd(openCmd
), m_printCmd(printCmd
), m_testCmd(testCmd
)
214 const wxString
& GetOpenCmd() const { return m_openCmd
; }
215 const wxString
& GetPrintCmd() const { return m_printCmd
; }
216 const wxString
& GetTestCmd() const { return m_testCmd
; }
218 MailCapEntry
*GetNext() const { return m_next
; }
221 // prepend this element to the list
222 void Prepend(MailCapEntry
*next
) { m_next
= next
; }
223 // insert into the list at given position
224 void Insert(MailCapEntry
*next
, size_t pos
)
229 for ( cur
= next
; cur
!= NULL
; cur
= cur
->m_next
, n
++ ) {
234 wxASSERT_MSG( n
== pos
, "invalid position in MailCapEntry::Insert" );
236 m_next
= cur
->m_next
;
239 // append this element to the list
240 void Append(MailCapEntry
*next
)
242 wxCHECK_RET( next
!= NULL
, "Append()ing to what?" );
246 for ( cur
= next
; cur
->m_next
!= NULL
; cur
= cur
->m_next
)
251 wxASSERT_MSG( !m_next
, "Append()ing element already in the list?" );
255 wxString m_openCmd
, // command to use to open/view the file
257 m_testCmd
; // only apply this entry if test yields
258 // true (i.e. the command returns 0)
260 MailCapEntry
*m_next
; // in the linked list
263 WX_DEFINE_ARRAY(MailCapEntry
*, ArrayTypeEntries
);
265 class wxMimeTypesManagerImpl
267 friend class wxFileTypeImpl
; // give it access to m_aXXX variables
270 // ctor loads all info into memory for quicker access later on
271 // TODO it would be nice to load them all, but parse on demand only...
272 wxMimeTypesManagerImpl();
274 // implement containing class functions
275 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
276 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
278 bool ReadMailcap(const wxString
& filename
, bool fallback
= FALSE
);
279 bool ReadMimeTypes(const wxString
& filename
);
282 // get the string containing space separated extensions for the given
284 wxString
GetExtension(size_t index
) { return m_aExtensions
[index
]; }
287 wxArrayString m_aTypes
, // MIME types
288 m_aDescriptions
, // descriptions (just some text)
289 m_aExtensions
; // space separated list of extensions
290 ArrayTypeEntries m_aEntries
; // commands and tests for this file type
296 // initialization functions
297 void Init(wxMimeTypesManagerImpl
*manager
, size_t index
)
298 { m_manager
= manager
; m_index
= index
; }
301 bool GetExtensions(wxArrayString
& extensions
);
302 bool GetMimeType(wxString
*mimeType
) const
303 { *mimeType
= m_manager
->m_aTypes
[m_index
]; return TRUE
; }
304 bool GetIcon(wxIcon
* WXUNUSED(icon
)) const
305 { return FALSE
; } // TODO maybe with Gnome/KDE integration...
306 bool GetDescription(wxString
*desc
) const
307 { *desc
= m_manager
->m_aDescriptions
[m_index
]; return TRUE
; }
309 bool GetOpenCommand(wxString
*openCmd
,
310 const wxFileType::MessageParameters
& params
) const
312 return GetExpandedCommand(openCmd
, params
, TRUE
);
315 bool GetPrintCommand(wxString
*printCmd
,
316 const wxFileType::MessageParameters
& params
) const
318 return GetExpandedCommand(printCmd
, params
, FALSE
);
322 // get the entry which passes the test (may return NULL)
323 MailCapEntry
*GetEntry(const wxFileType::MessageParameters
& params
) const;
325 // choose the correct entry to use and expand the command
326 bool GetExpandedCommand(wxString
*expandedCmd
,
327 const wxFileType::MessageParameters
& params
,
330 wxMimeTypesManagerImpl
*m_manager
;
331 size_t m_index
; // in the wxMimeTypesManagerImpl arrays
336 // ============================================================================
337 // implementation of the wrapper classes
338 // ============================================================================
340 // ----------------------------------------------------------------------------
342 // ----------------------------------------------------------------------------
344 wxString
wxFileType::ExpandCommand(const wxString
& command
,
345 const wxFileType::MessageParameters
& params
)
347 bool hasFilename
= FALSE
;
350 for ( const char *pc
= command
.c_str(); *pc
!= '\0'; pc
++ ) {
354 // '%s' expands into file name (quoted because it might
355 // contain spaces) - except if there are already quotes
356 // there because otherwise some programs may get confused
357 // by double double quotes
359 if ( *(pc
- 2) == '"' )
360 str
<< params
.GetFileName();
362 str
<< '"' << params
.GetFileName() << '"';
364 str
<< params
.GetFileName();
369 // '%t' expands into MIME type (quote it too just to be
371 str
<< '\'' << params
.GetMimeType() << '\'';
376 const char *pEnd
= strchr(pc
, '}');
377 if ( pEnd
== NULL
) {
379 wxLogWarning(_("Unmatched '{' in an entry for "
381 params
.GetMimeType().c_str());
385 wxString
param(pc
+ 1, pEnd
- pc
- 1);
386 str
<< '\'' << params
.GetParamValue(param
) << '\'';
394 // TODO %n is the number of parts, %F is an array containing
395 // the names of temp files these parts were written to
396 // and their mime types.
400 wxLogDebug("Unknown field %%%c in command '%s'.",
401 *pc
, command
.c_str());
410 // metamail(1) man page states that if the mailcap entry doesn't have '%s'
411 // the program will accept the data on stdin: so give it to it!
412 if ( !hasFilename
&& !str
.IsEmpty() ) {
413 str
<< " < '" << params
.GetFileName() << '\'';
419 wxFileType::wxFileType()
421 m_impl
= new wxFileTypeImpl
;
424 wxFileType::~wxFileType()
429 bool wxFileType::GetExtensions(wxArrayString
& extensions
)
431 return m_impl
->GetExtensions(extensions
);
434 bool wxFileType::GetMimeType(wxString
*mimeType
) const
436 return m_impl
->GetMimeType(mimeType
);
439 bool wxFileType::GetIcon(wxIcon
*icon
) const
441 return m_impl
->GetIcon(icon
);
444 bool wxFileType::GetDescription(wxString
*desc
) const
446 return m_impl
->GetDescription(desc
);
450 wxFileType::GetOpenCommand(wxString
*openCmd
,
451 const wxFileType::MessageParameters
& params
) const
453 return m_impl
->GetOpenCommand(openCmd
, params
);
457 wxFileType::GetPrintCommand(wxString
*printCmd
,
458 const wxFileType::MessageParameters
& params
) const
460 return m_impl
->GetPrintCommand(printCmd
, params
);
463 // ----------------------------------------------------------------------------
464 // wxMimeTypesManager
465 // ----------------------------------------------------------------------------
467 bool wxMimeTypesManager::IsOfType(const wxString
& mimeType
,
468 const wxString
& wildcard
)
470 wxASSERT_MSG( mimeType
.Find('*') == wxNOT_FOUND
,
471 "first MIME type can't contain wildcards" );
473 // all comparaisons are case insensitive (2nd arg of IsSameAs() is FALSE)
474 if ( wildcard
.BeforeFirst('/').IsSameAs(mimeType
.BeforeFirst('/'), FALSE
) )
476 wxString strSubtype
= wildcard
.AfterFirst('/');
478 if ( strSubtype
== '*' ||
479 strSubtype
.IsSameAs(mimeType
.AfterFirst('/'), FALSE
) )
481 // matches (either exactly or it's a wildcard)
489 wxMimeTypesManager::wxMimeTypesManager()
491 m_impl
= new wxMimeTypesManagerImpl
;
494 wxMimeTypesManager::~wxMimeTypesManager()
500 wxMimeTypesManager::GetFileTypeFromExtension(const wxString
& ext
)
502 return m_impl
->GetFileTypeFromExtension(ext
);
506 wxMimeTypesManager::GetFileTypeFromMimeType(const wxString
& mimeType
)
508 return m_impl
->GetFileTypeFromMimeType(mimeType
);
511 bool wxMimeTypesManager::ReadMailcap(const wxString
& filename
, bool fallback
)
513 return m_impl
->ReadMailcap(filename
, fallback
);
516 bool wxMimeTypesManager::ReadMimeTypes(const wxString
& filename
)
518 return m_impl
->ReadMimeTypes(filename
);
521 // ============================================================================
522 // real (OS specific) implementation
523 // ============================================================================
527 bool wxFileTypeImpl::GetCommand(wxString
*command
, const char *verb
) const
529 // suppress possible error messages
532 strKey
<< m_strFileType
<< "\\shell\\" << verb
<< "\\command";
533 wxRegKey
key(wxRegKey::HKCR
, strKey
);
536 // it's the default value of the key
537 if ( key
.QueryValue("", *command
) ) {
538 // transform it from '%1' to '%s' style format string
539 // NB: we don't make any attempt to verify that the string is valid,
540 // i.e. doesn't contain %2, or second %1 or .... But we do make
541 // sure that we return a string with _exactly_ one '%s'!
542 size_t len
= command
->Len();
543 for ( size_t n
= 0; n
< len
; n
++ ) {
544 if ( command
->GetChar(n
) == '%' &&
545 (n
+ 1 < len
) && command
->GetChar(n
+ 1) == '1' ) {
546 // replace it with '%s'
547 command
->SetChar(n
+ 1, 's');
553 // we didn't find any '%1'!
554 // HACK: append the filename at the end, hope that it will do
561 // no such file type or no value
565 // TODO this function is half implemented
566 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
568 if ( m_ext
.IsEmpty() ) {
569 // the only way to get the list of extensions from the file type is to
570 // scan through all extensions in the registry - too slow...
575 extensions
.Add(m_ext
);
577 // it's a lie too, we don't return _all_ extensions...
582 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
584 // suppress possible error messages
586 wxRegKey
key(wxRegKey::HKCR
, /*m_strFileType*/ "." + m_ext
);
587 if ( key
.Open() && key
.QueryValue("Content Type", *mimeType
) ) {
595 bool wxFileTypeImpl::GetIcon(wxIcon
*icon
) const
598 strIconKey
<< m_strFileType
<< "\\DefaultIcon";
600 // suppress possible error messages
602 wxRegKey
key(wxRegKey::HKCR
, strIconKey
);
606 // it's the default value of the key
607 if ( key
.QueryValue("", strIcon
) ) {
608 // the format is the following: <full path to file>, <icon index>
609 // NB: icon index may be negative as well as positive and the full
610 // path may contain the environment variables inside '%'
611 wxString strFullPath
= strIcon
.BeforeLast(','),
612 strIndex
= strIcon
.AfterLast(',');
614 // index may be omitted, in which case BeforeLast(',') is empty and
615 // AfterLast(',') is the whole string
616 if ( strFullPath
.IsEmpty() ) {
617 strFullPath
= strIndex
;
621 wxString strExpPath
= wxExpandEnvVars(strFullPath
);
622 int nIndex
= atoi(strIndex
);
624 HICON hIcon
= ExtractIcon(GetModuleHandle(NULL
), strExpPath
, nIndex
);
625 switch ( (int)hIcon
) {
626 case 0: // means no icons were found
627 case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/...
628 wxLogDebug("incorrect registry entry '%s': no such icon.",
629 key
.GetName().c_str());
633 icon
->SetHICON((WXHICON
)hIcon
);
639 // no such file type or no value or incorrect icon entry
643 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
645 // suppress possible error messages
647 wxRegKey
key(wxRegKey::HKCR
, m_strFileType
);
650 // it's the default value of the key
651 if ( key
.QueryValue("", *desc
) ) {
659 // extension -> file type
661 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
663 // add the leading point if necessary
665 if ( ext
[0u] != '.' ) {
670 // suppress possible error messages
673 wxString strFileType
;
674 wxRegKey
key(wxRegKey::HKCR
, str
);
676 // it's the default value of the key
677 if ( key
.QueryValue("", strFileType
) ) {
678 // create the new wxFileType object
679 wxFileType
*fileType
= new wxFileType
;
680 fileType
->m_impl
->SetFileType(strFileType
);
681 fileType
->m_impl
->SetExt(ext
);
691 // MIME type -> extension -> file type
693 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
695 // HACK I don't know of any official documentation which mentions this
696 // location, but as a matter of fact IE uses it, so why not we?
697 static const char *szMimeDbase
= "MIME\\Database\\Content Type\\";
699 wxString strKey
= szMimeDbase
;
702 // suppress possible error messages
706 wxRegKey
key(wxRegKey::HKCR
, strKey
);
708 if ( key
.QueryValue("Extension", ext
) ) {
709 return GetFileTypeFromExtension(ext
);
720 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters
& params
) const
723 MailCapEntry
*entry
= m_manager
->m_aEntries
[m_index
];
724 while ( entry
!= NULL
) {
725 // notice that an empty command would always succeed (it's ok)
726 command
= wxFileType::ExpandCommand(entry
->GetTestCmd(), params
);
728 if ( command
.IsEmpty() || (system(command
) == 0) ) {
730 wxLogTrace("Test '%s' for mime type '%s' succeeded.",
731 command
.c_str(), params
.GetMimeType().c_str());
735 wxLogTrace("Test '%s' for mime type '%s' failed.",
736 command
.c_str(), params
.GetMimeType().c_str());
739 entry
= entry
->GetNext();
746 wxFileTypeImpl::GetExpandedCommand(wxString
*expandedCmd
,
747 const wxFileType::MessageParameters
& params
,
750 MailCapEntry
*entry
= GetEntry(params
);
751 if ( entry
== NULL
) {
752 // all tests failed...
756 wxString cmd
= open
? entry
->GetOpenCmd() : entry
->GetPrintCmd();
757 if ( cmd
.IsEmpty() ) {
758 // may happen, especially for "print"
762 *expandedCmd
= wxFileType::ExpandCommand(cmd
, params
);
766 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
768 wxString strExtensions
= m_manager
->GetExtension(m_index
);
771 // one extension in the space or comma delimitid list
773 for ( const char *p
= strExtensions
; ; p
++ ) {
774 if ( *p
== ' ' || *p
== ',' || *p
== '\0' ) {
775 if ( !strExt
.IsEmpty() ) {
776 extensions
.Add(strExt
);
779 //else: repeated spaces (shouldn't happen, but it's not that
780 // important if it does happen)
785 else if ( *p
== '.' ) {
786 // remove the dot from extension (but only if it's the first char)
787 if ( !strExt
.IsEmpty() ) {
790 //else: no, don't append it
800 // read system and user mailcaps (TODO implement mime.types support)
801 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
803 // directories where we look for mailcap and mime.types by default
804 // (taken from metamail(1) sources)
805 static const char *aStandardLocations
[] =
814 // first read the system wide file(s)
815 for ( size_t n
= 0; n
< WXSIZEOF(aStandardLocations
); n
++ ) {
816 wxString dir
= aStandardLocations
[n
];
818 wxString file
= dir
+ "/mailcap";
819 if ( wxFile::Exists(file
) ) {
823 file
= dir
+ "/mime.types";
824 if ( wxFile::Exists(file
) ) {
829 wxString strHome
= getenv("HOME");
831 // and now the users mailcap
832 wxString strUserMailcap
= strHome
+ "/.mailcap";
833 if ( wxFile::Exists(strUserMailcap
) ) {
834 ReadMailcap(strUserMailcap
);
837 // read the users mime.types
838 wxString strUserMimeTypes
= strHome
+ "/.mime.types";
839 if ( wxFile::Exists(strUserMimeTypes
) ) {
840 ReadMimeTypes(strUserMimeTypes
);
845 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
847 size_t count
= m_aExtensions
.GetCount();
848 for ( size_t n
= 0; n
< count
; n
++ ) {
849 wxString extensions
= m_aExtensions
[n
];
850 while ( !extensions
.IsEmpty() ) {
851 wxString field
= extensions
.BeforeFirst(' ');
852 extensions
= extensions
.AfterFirst(' ');
854 // consider extensions as not being case-sensitive
855 if ( field
.IsSameAs(ext
, FALSE
/* no case */) ) {
857 wxFileType
*fileType
= new wxFileType
;
858 fileType
->m_impl
->Init(this, n
);
870 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
872 // mime types are not case-sensitive
873 wxString
mimetype(mimeType
);
874 mimetype
.MakeLower();
876 // first look for an exact match
877 int index
= m_aTypes
.Index(mimetype
);
878 if ( index
== wxNOT_FOUND
) {
879 // then try to find "text/*" as match for "text/plain" (for example)
880 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
881 // the whole string - ok.
882 wxString strCategory
= mimetype
.BeforeFirst('/');
884 size_t nCount
= m_aTypes
.Count();
885 for ( size_t n
= 0; n
< nCount
; n
++ ) {
886 if ( (m_aTypes
[n
].BeforeFirst('/') == strCategory
) &&
887 m_aTypes
[n
].AfterFirst('/') == "*" ) {
894 if ( index
!= wxNOT_FOUND
) {
895 wxFileType
*fileType
= new wxFileType
;
896 fileType
->m_impl
->Init(this, index
);
906 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString
& strFileName
)
908 wxLogTrace("--- Parsing mime.types file '%s' ---", strFileName
.c_str());
910 wxTextFile
file(strFileName
);
914 // the information we extract
915 wxString strMimeType
, strDesc
, strExtensions
;
917 size_t nLineCount
= file
.GetLineCount();
918 const char *pc
= NULL
;
919 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
921 // now we're at the start of the line
922 pc
= file
[nLine
].c_str();
925 // we didn't finish with the previous line yet
930 while ( isspace(*pc
) )
935 // skip the whole line
940 // detect file format
941 const char *pEqualSign
= strchr(pc
, '=');
942 if ( pEqualSign
== NULL
) {
946 // first field is mime type
947 for ( strMimeType
.Empty(); !isspace(*pc
) && *pc
!= '\0'; pc
++ ) {
952 while ( isspace(*pc
) )
955 // take all the rest of the string
965 // the string on the left of '=' is the field name
966 wxString
strLHS(pc
, pEqualSign
- pc
);
969 for ( pc
= pEqualSign
+ 1; isspace(*pc
); pc
++ )
974 // the string is quoted and ends at the matching quote
975 pEnd
= strchr(++pc
, '"');
976 if ( pEnd
== NULL
) {
977 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
979 strFileName
.c_str(), nLine
+ 1);
983 // unquoted string ends at the first space
984 for ( pEnd
= pc
; !isspace(*pEnd
); pEnd
++ )
988 // now we have the RHS (field value)
989 wxString
strRHS(pc
, pEnd
- pc
);
991 // check what follows this entry
992 if ( *pEnd
== '"' ) {
997 for ( pc
= pEnd
; isspace(*pc
); pc
++ )
1000 // if there is something left, it may be either a '\\' to continue
1001 // the line or the next field of the same entry
1002 bool entryEnded
= *pc
== '\0',
1003 nextFieldOnSameLine
= FALSE
;
1004 if ( !entryEnded
) {
1005 nextFieldOnSameLine
= ((*pc
!= '\\') || (pc
[1] != '\0'));
1008 // now see what we got
1009 if ( strLHS
== "type" ) {
1010 strMimeType
= strRHS
;
1012 else if ( strLHS
== "desc" ) {
1015 else if ( strLHS
== "exts" ) {
1016 strExtensions
= strRHS
;
1019 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1020 strFileName
.c_str(), nLine
+ 1, strLHS
.c_str());
1023 if ( !entryEnded
) {
1024 if ( !nextFieldOnSameLine
)
1026 //else: don't reset it
1028 // as we don't reset strMimeType, the next field in this entry
1029 // will be interpreted correctly.
1035 // although it doesn't seem to be covered by RFCs, some programs
1036 // (notably Netscape) create their entries with several comma
1037 // separated extensions (RFC mention the spaces only)
1038 strExtensions
.Replace(",", " ");
1040 // also deal with the leading dot
1041 if ( !strExtensions
.IsEmpty() && strExtensions
[0] == '.' ) {
1042 strExtensions
.erase(0, 1);
1045 int index
= m_aTypes
.Index(strMimeType
);
1046 if ( index
== wxNOT_FOUND
) {
1048 m_aTypes
.Add(strMimeType
);
1049 m_aEntries
.Add(NULL
);
1050 m_aExtensions
.Add(strExtensions
);
1051 m_aDescriptions
.Add(strDesc
);
1054 // modify an existing one
1055 if ( !strDesc
.IsEmpty() ) {
1056 m_aDescriptions
[index
] = strDesc
; // replace old value
1058 m_aExtensions
[index
] += strExtensions
;
1061 // finished with this line
1065 // check our data integriry
1066 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1067 m_aTypes
.Count() == m_aExtensions
.Count() &&
1068 m_aTypes
.Count() == m_aDescriptions
.Count() );
1073 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString
& strFileName
,
1076 wxLogTrace("--- Parsing mailcap file '%s' ---", strFileName
.c_str());
1078 wxTextFile
file(strFileName
);
1082 // see the comments near the end of function for the reason we need these
1083 // variables (search for the next occurence of them)
1084 // indices of MIME types (in m_aTypes) we already found in this file
1085 wxArrayInt aEntryIndices
;
1086 // aLastIndices[n] is the index of last element in
1087 // m_aEntries[aEntryIndices[n]] from this file
1088 wxArrayInt aLastIndices
;
1090 size_t nLineCount
= file
.GetLineCount();
1091 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1092 // now we're at the start of the line
1093 const char *pc
= file
[nLine
].c_str();
1096 while ( isspace(*pc
) )
1099 // comment or empty string?
1100 if ( *pc
== '#' || *pc
== '\0' )
1105 // what field are we currently in? The first 2 are fixed and there may
1106 // be an arbitrary number of other fields -- currently, we are not
1107 // interested in any of them, but we should parse them as well...
1113 } currentToken
= Field_Type
;
1115 // the flags and field values on the current line
1116 bool needsterminal
= FALSE
,
1117 copiousoutput
= FALSE
;
1123 curField
; // accumulator
1124 for ( bool cont
= TRUE
; cont
; pc
++ ) {
1127 // interpret the next character literally (notice that
1128 // backslash can be used for line continuation)
1129 if ( *++pc
== '\0' ) {
1130 // fetch the next line.
1132 // pc currently points to nowhere, but after the next
1133 // pc++ in the for line it will point to the beginning
1134 // of the next line in the file
1135 pc
= file
[++nLine
].c_str() - 1;
1138 // just a normal character
1144 cont
= FALSE
; // end of line reached, exit the loop
1149 // store this field and start looking for the next one
1151 // trim whitespaces from both sides
1152 curField
.Trim(TRUE
).Trim(FALSE
);
1154 switch ( currentToken
) {
1157 if ( strType
.Find('/') == wxNOT_FOUND
) {
1158 // we interpret "type" as "type/*"
1162 currentToken
= Field_OpenCmd
;
1166 strOpenCmd
= curField
;
1168 currentToken
= Field_Other
;
1173 // "good" mailcap entry?
1176 // is this something of the form foo=bar?
1177 const char *pEq
= strchr(curField
, '=');
1178 if ( pEq
!= NULL
) {
1179 wxString lhs
= curField
.BeforeFirst('='),
1180 rhs
= curField
.AfterFirst('=');
1182 lhs
.Trim(TRUE
); // from right
1183 rhs
.Trim(FALSE
); // from left
1185 if ( lhs
== "print" )
1187 else if ( lhs
== "test" )
1189 else if ( lhs
== "description" ) {
1190 // it might be quoted
1191 if ( rhs
[0u] == '"' &&
1192 rhs
.Last() == '"' ) {
1193 strDesc
= wxString(rhs
.c_str() + 1,
1200 else if ( lhs
== "compose" ||
1201 lhs
== "composetyped" ||
1210 // no, it's a simple flag
1211 // TODO support the flags:
1212 // 1. create an xterm for 'needsterminal'
1213 // 2. append "| $PAGER" for 'copiousoutput'
1214 if ( curField
== "needsterminal" )
1215 needsterminal
= TRUE
;
1216 else if ( curField
== "copiousoutput" )
1217 copiousoutput
= TRUE
;
1218 else if ( curField
== "textualnewlines" )
1226 // don't flood the user with error messages
1227 // if we don't understand something in his
1228 // mailcap, but give them in debug mode
1229 // because this might be useful for the
1233 "Mailcap file %s, line %d: unknown "
1234 "field '%s' for the MIME type "
1236 strFileName
.c_str(),
1244 // it already has this value
1245 //currentToken = Field_Other;
1249 wxFAIL_MSG("unknown field type in mailcap");
1252 // next token starts immediately after ';'
1261 // check that we really read something reasonable
1262 if ( currentToken
== Field_Type
|| currentToken
== Field_OpenCmd
) {
1263 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1265 strFileName
.c_str(), nLine
+ 1);
1268 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
,
1272 strType
.MakeLower();
1273 int nIndex
= m_aTypes
.Index(strType
);
1274 if ( nIndex
== wxNOT_FOUND
) {
1276 m_aTypes
.Add(strType
);
1278 m_aEntries
.Add(entry
);
1279 m_aExtensions
.Add("");
1280 m_aDescriptions
.Add(strDesc
);
1283 // modify the existing entry: the entries in one and the same
1284 // file are read in top-to-bottom order, i.e. the entries read
1285 // first should be tried before the entries below. However,
1286 // the files read later should override the settings in the
1287 // files read before (except if fallback is TRUE), thus we
1288 // Insert() the new entry to the list if it has already
1289 // occured in _this_ file, but Prepend() it if it occured in
1290 // some of the previous ones and Append() to it in the
1294 // 'fallback' parameter prevents the entries from this
1295 // file from overriding the other ones - always append
1296 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1298 entry
->Append(entryOld
);
1300 m_aEntries
[nIndex
] = entry
;
1303 int entryIndex
= aEntryIndices
.Index(nIndex
);
1304 if ( entryIndex
== wxNOT_FOUND
) {
1305 // first time in this file
1306 aEntryIndices
.Add(nIndex
);
1307 aLastIndices
.Add(0);
1309 entry
->Prepend(m_aEntries
[nIndex
]);
1310 m_aEntries
[nIndex
] = entry
;
1313 // not the first time in _this_ file
1314 size_t nEntryIndex
= (size_t)entryIndex
;
1315 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1317 entry
->Insert(entryOld
, aLastIndices
[nEntryIndex
]);
1319 m_aEntries
[nIndex
] = entry
;
1321 // the indices were shifted by 1
1322 aLastIndices
[nEntryIndex
]++;
1326 if ( !strDesc
.IsEmpty() ) {
1327 // replace the old one - what else can we do??
1328 m_aDescriptions
[nIndex
] = strDesc
;
1333 // check our data integriry
1334 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1335 m_aTypes
.Count() == m_aExtensions
.Count() &&
1336 m_aTypes
.Count() == m_aDescriptions
.Count() );