3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
8 * \author Ivan Schreter
9 * \author Dirk Niggemann
10 * \author Asger Alstrup
11 * \author Lars Gullik Bjønnes
12 * \author Jean-Marc Lasgouttes
13 * \author Angus Leeming
17 * Full author contact details are available in file CREDITS.
19 * General path-mangling functions
24 #include "support/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/lstrings.h"
28 #include "support/lyxlib.h"
29 #include "support/os.h"
30 #include "support/Package.h"
31 #include "support/Path.h"
32 #include "support/Systemcall.h"
34 // FIXME Interface violation
38 #include <boost/assert.hpp>
39 #include <boost/filesystem/operations.hpp>
40 #include <boost/regex.hpp>
57 using std::ostringstream;
61 namespace fs = boost::filesystem;
66 bool isLyXFilename(string const & filename)
68 return suffixIs(ascii_lowercase(filename), ".lyx");
72 bool isSGMLFilename(string const & filename)
74 return suffixIs(ascii_lowercase(filename), ".sgml");
78 bool isValidLaTeXFilename(string const & filename)
80 string const invalid_chars("#$%{}()[]\"^");
81 return filename.find_first_of(invalid_chars) == string::npos;
85 string const latex_path(string const & original_path,
86 latex_path_extension extension,
89 // On cygwin, we may need windows or posix style paths.
90 string path = os::latex_path(original_path);
91 path = subst(path, "~", "\\string~");
92 if (path.find(' ') != string::npos) {
93 // We can't use '"' because " is sometimes active (e.g. if
94 // babel is loaded with the "german" option)
95 if (extension == EXCLUDE_EXTENSION) {
96 // ChangeExtension calls os::internal_path internally
97 // so don't use it to remove the extension.
98 string const ext = getExtension(path);
99 string const base = ext.empty() ?
101 path.substr(0, path.length() - ext.length() - 1);
102 // ChangeExtension calls os::internal_path internally
103 // so don't use it to re-add the extension.
104 path = "\\string\"" + base + "\\string\"." + ext;
106 path = "\\string\"" + path + "\\string\"";
110 return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
114 // Substitutes spaces with underscores in filename (and path)
115 string const makeLatexName(string const & file)
117 string name = onlyFilename(file);
118 string const path = onlyPath(file);
120 // ok so we scan through the string twice, but who cares.
121 string const keep = "abcdefghijklmnopqrstuvwxyz"
122 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
123 "@!'()*+,-./0123456789:;<=>?[]`|";
125 string::size_type pos = 0;
126 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
129 return addName(path, name);
133 string const quoteName(string const & name, quote_style style)
137 // This does not work for filenames containing " (windows)
138 // or ' (all other OSes). This can't be changed easily, since
139 // we would need to adapt the command line parser in
140 // Forkedcall::generateChild. Therefore we don't pass user
141 // filenames to child processes if possible. We store them in
142 // a python script instead, where we don't have these
144 return (os::shell() == os::UNIX) ?
148 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
151 // shut up stupid compiler
157 // Uses a string of paths separated by ";"s to find a file to open.
158 // Can't cope with pathnames with a ';' in them. Returns full path to file.
159 // If path entry begins with $$LyX/, use system_lyxdir
160 // If path entry begins with $$User/, use user_lyxdir
161 // Example: "$$User/doc;$$LyX/doc"
162 FileName const fileOpenSearch(string const & path, string const & name,
167 bool notfound = true;
168 string tmppath = split(path, path_element, ';');
170 while (notfound && !path_element.empty()) {
171 path_element = os::internal_path(path_element);
172 if (!suffixIs(path_element, '/'))
174 path_element = subst(path_element, "$$LyX",
175 package().system_support().absFilename());
176 path_element = subst(path_element, "$$User",
177 package().user_support().absFilename());
179 real_file = fileSearch(path_element, name, ext);
181 if (real_file.empty()) {
183 tmppath = split(tmppath, path_element, ';');
184 } while (!tmppath.empty() && path_element.empty());
194 /// Returns a vector of all files in directory dir having extension ext.
195 vector<FileName> const dirList(FileName const & dir, string const & ext)
197 // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
198 vector<FileName> dirlist;
200 if (!(dir.exists() && dir.isDirectory())) {
202 << "Directory \"" << dir
203 << "\" does not exist to DirList." << endl;
208 if (!ext.empty() && ext[0] != '.')
212 string const encoded_dir = dir.toFilesystemEncoding();
213 fs::directory_iterator dit(encoded_dir);
214 fs::directory_iterator end;
215 for (; dit != end; ++dit) {
216 string const & fil = dit->leaf();
217 if (suffixIs(fil, extension))
218 dirlist.push_back(FileName::fromFilesystemEncoding(
219 encoded_dir + '/' + fil));
225 // Returns the real name of file name in directory path, with optional
227 FileName const fileSearch(string const & path, string const & name,
228 string const & ext, search_mode mode)
230 // if `name' is an absolute path, we ignore the setting of `path'
231 // Expand Environmentvariables in 'name'
232 string const tmpname = replaceEnvironmentPath(name);
233 FileName fullname(makeAbsPath(tmpname, path));
234 // search first without extension, then with it.
235 if (fullname.isReadable())
239 return mode == allow_unreadable ? fullname : FileName();
240 // Only add the extension if it is not already the extension of
242 if (getExtension(fullname.absFilename()) != ext)
243 fullname = FileName(addExtension(fullname.absFilename(), ext));
244 if (fullname.isReadable() || mode == allow_unreadable)
250 // Search the file name.ext in the subdirectory dir of
252 // 2) build_lyxdir (if not empty)
254 FileName const libFileSearch(string const & dir, string const & name,
257 FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
259 if (!fullname.empty())
262 if (!package().build_support().empty())
263 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
265 if (!fullname.empty())
268 return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
272 FileName const i18nLibFileSearch(string const & dir, string const & name,
275 /* The highest priority value is the `LANGUAGE' environment
276 variable. But we don't use the value if the currently
277 selected locale is the C locale. This is a GNU extension.
279 Otherwise, w use a trick to guess what gettext has done:
280 each po file is able to tell us its name. (JMarc)
283 string lang = to_ascii(_("[[Replace with the code of your language]]"));
284 string const language = getEnv("LANGUAGE");
285 if (!lang.empty() && !language.empty())
289 lang = split(lang, l, ':');
292 // First try with the full name
293 tmp = libFileSearch(addPath(dir, l), name, ext);
297 // Then the name without country code
298 string const shortl = token(l, '_', 0);
300 tmp = libFileSearch(addPath(dir, shortl), name, ext);
306 // For compatibility, to be removed later (JMarc)
307 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
310 lyxerr << "i18nLibFileSearch: File `" << tmp
311 << "' has been found by the old method" <<endl;
315 lang = split(lang, l, ':');
318 return libFileSearch(dir, name, ext);
322 string const libScriptSearch(string const & command_in, quote_style style)
324 static string const token_scriptpath = "$$s/";
326 string command = command_in;
327 // Find the starting position of "$$s/"
328 string::size_type const pos1 = command.find(token_scriptpath);
329 if (pos1 == string::npos)
331 // Find the end of the "$$s/some_subdir/some_script" word within
332 // command. Assumes that the script name does not contain spaces.
333 string::size_type const start_script = pos1 + 4;
334 string::size_type const pos2 = command.find(' ', start_script);
335 string::size_type const size_script = pos2 == string::npos?
336 (command.size() - start_script) : pos2 - start_script;
338 // Does this script file exist?
339 string const script =
340 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
342 if (script.empty()) {
343 // Replace "$$s/" with ""
344 command.erase(pos1, 4);
346 // Replace "$$s/foo/some_script" with "<path to>/some_script".
347 string::size_type const size_replace = size_script + 4;
348 command.replace(pos1, size_replace, quoteName(script, style));
355 static FileName createTmpDir(FileName const & tempdir, string const & mask)
358 << "createTmpDir: tempdir=`" << tempdir << "'\n"
359 << "createTmpDir: mask=`" << mask << '\'' << endl;
361 FileName const tmpfl(tempName(tempdir, mask));
362 // lyx::tempName actually creates a file to make sure that it
363 // stays unique. So we have to delete it before we can create
364 // a dir with the same name. Note also that we are not thread
365 // safe because of the gap between unlink and mkdir. (Lgb)
368 if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
369 lyxerr << "LyX could not create the temporary directory '"
370 << tmpfl << "'" << endl;
377 string const createBufferTmpDir()
380 // We are in our own directory. Why bother to mangle name?
381 // In fact I wrote this code to circumvent a problematic behaviour
382 // (bug?) of EMX mkstemp().
384 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
385 convert<string>(count++);
387 if (mkdir(FileName(tmpfl), 0777)) {
388 lyxerr << "LyX could not create the temporary directory '"
389 << tmpfl << "'" << endl;
396 FileName const createLyXTmpDir(FileName const & deflt)
398 if (deflt.empty() || deflt.absFilename() == "/tmp")
399 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
401 if (!mkdir(deflt, 0777))
404 if (deflt.isDirWritable()) {
405 // deflt could not be created because it
406 // did exist already, so let's create our own
408 return createTmpDir(deflt, "lyx_tmpdir");
410 // some other error occured.
411 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
416 // Strip filename from path name
417 string const onlyPath(string const & filename)
419 // If empty filename, return empty
420 if (filename.empty())
423 // Find last / or start of filename
424 size_t j = filename.rfind('/');
425 return j == string::npos ? "./" : filename.substr(0, j + 1);
429 // Convert relative path into absolute path based on a basepath.
430 // If relpath is absolute, just use that.
431 // If basepath is empty, use CWD as base.
432 FileName const makeAbsPath(string const & relPath, string const & basePath)
434 // checks for already absolute path
435 if (os::is_absolute_path(relPath))
436 return FileName(relPath);
438 // Copies given paths
439 string tempRel = os::internal_path(relPath);
440 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
441 tempRel = subst(tempRel, "//", "/");
445 if (os::is_absolute_path(basePath))
448 tempBase = addPath(getcwd().absFilename(), basePath);
450 // Handle /./ at the end of the path
451 while (suffixIs(tempBase, "/./"))
452 tempBase.erase(tempBase.length() - 2);
454 // processes relative path
455 string rTemp = tempRel;
458 while (!rTemp.empty()) {
460 rTemp = split(rTemp, temp, '/');
462 if (temp == ".") continue;
464 // Remove one level of TempBase
465 string::difference_type i = tempBase.length() - 2;
468 while (i > 0 && tempBase[i] != '/')
471 tempBase.erase(i, string::npos);
474 } else if (temp.empty() && !rTemp.empty()) {
475 tempBase = os::current_root() + rTemp;
478 // Add this piece to TempBase
479 if (!suffixIs(tempBase, '/'))
485 // returns absolute path
486 return FileName(os::internal_path(tempBase));
490 // Correctly append filename to the pathname.
491 // If pathname is '.', then don't use pathname.
492 // Chops any path of filename.
493 string const addName(string const & path, string const & fname)
495 string const basename = onlyFilename(fname);
498 if (path != "." && path != "./" && !path.empty()) {
499 buf = os::internal_path(path);
500 if (!suffixIs(path, '/'))
504 return buf + basename;
508 // Strips path from filename
509 string const onlyFilename(string const & fname)
514 string::size_type j = fname.rfind('/');
515 if (j == string::npos) // no '/' in fname
519 return fname.substr(j + 1);
523 /// Returns true is path is absolute
524 bool absolutePath(string const & path)
526 return os::is_absolute_path(path);
530 // Create absolute path. If impossible, don't do anything
531 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
532 string const expandPath(string const & path)
534 // checks for already absolute path
535 string rTemp = replaceEnvironmentPath(path);
536 if (os::is_absolute_path(rTemp))
540 string const copy = rTemp;
543 rTemp = split(rTemp, temp, '/');
546 return getcwd().absFilename() + '/' + rTemp;
549 return package().home_dir().absFilename() + '/' + rTemp;
552 return makeAbsPath(copy).absFilename();
554 // Don't know how to handle this
559 // Normalize a path. Constracts path/../path
560 // Can't handle "../../" or "/../" (Asger)
561 // Also converts paths like /foo//bar ==> /foo/bar
562 string const normalizePath(string const & path)
564 // Normalize paths like /foo//bar ==> /foo/bar
565 static boost::regex regex("/{2,}");
566 string const tmppath = boost::regex_merge(path, regex, "/");
568 fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
570 if (!npath.is_complete())
571 return "./" + npath.string() + '/';
573 return npath.string() + '/';
577 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
578 string const replaceEnvironmentPath(string const & path)
580 // ${VAR} is defined as
581 // $\{[A-Za-z_][A-Za-z_0-9]*\}
582 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
584 // $VAR is defined as:
585 // $\{[A-Za-z_][A-Za-z_0-9]*\}
586 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
588 static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
589 static boost::regex envvar_re("(.*)" + envvar + "(.*)");
592 string result = path;
594 regex_match(result, what, envvar_br_re);
595 if (!what[0].matched) {
596 regex_match(result, what, envvar_re);
597 if (!what[0].matched)
600 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
606 // Make relative path out of two absolute paths
607 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
608 // Makes relative path out of absolute path. If it is deeper than basepath,
609 // it's easy. If basepath and abspath share something (they are all deeper
610 // than some directory), it'll be rendered using ..'s. If they are completely
611 // different, then the absolute path will be used as relative path.
613 docstring::size_type const abslen = abspath.length();
614 docstring::size_type const baselen = basepath.length();
616 docstring::size_type i = os::common_path(abspath, basepath);
619 // actually no match - cannot make it relative
623 // Count how many dirs there are in basepath above match
624 // and append as many '..''s into relpath
626 docstring::size_type j = i;
627 while (j < baselen) {
628 if (basepath[j] == '/') {
629 if (j + 1 == baselen)
636 // Append relative stuff from common directory to abspath
637 if (abspath[i] == '/')
639 for (; i < abslen; ++i)
642 if (suffixIs(buf, '/'))
643 buf.erase(buf.length() - 1);
644 // Substitute empty with .
651 // Append sub-directory(ies) to a path in an intelligent way
652 string const addPath(string const & path, string const & path_2)
655 string const path2 = os::internal_path(path_2);
657 if (!path.empty() && path != "." && path != "./") {
658 buf = os::internal_path(path);
659 if (path[path.length() - 1] != '/')
663 if (!path2.empty()) {
664 string::size_type const p2start = path2.find_first_not_of('/');
665 string::size_type const p2end = path2.find_last_not_of('/');
666 string const tmp = path2.substr(p2start, p2end - p2start + 1);
673 string const changeExtension(string const & oldname, string const & extension)
675 string::size_type const last_slash = oldname.rfind('/');
676 string::size_type last_dot = oldname.rfind('.');
677 if (last_dot < last_slash && last_slash != string::npos)
678 last_dot = string::npos;
681 // Make sure the extension starts with a dot
682 if (!extension.empty() && extension[0] != '.')
683 ext= '.' + extension;
687 return os::internal_path(oldname.substr(0, last_dot) + ext);
691 string const removeExtension(string const & name)
693 return changeExtension(name, string());
697 string const addExtension(string const & name, string const & extension)
699 if (!extension.empty() && extension[0] != '.')
700 return name + '.' + extension;
701 return name + extension;
705 /// Return the extension of the file (not including the .)
706 string const getExtension(string const & name)
708 string::size_type const last_slash = name.rfind('/');
709 string::size_type const last_dot = name.rfind('.');
710 if (last_dot != string::npos &&
711 (last_slash == string::npos || last_dot > last_slash))
712 return name.substr(last_dot + 1,
713 name.length() - (last_dot + 1));
719 string const unzippedFileName(string const & zipped_file)
721 string const ext = getExtension(zipped_file);
722 if (ext == "gz" || ext == "z" || ext == "Z")
723 return changeExtension(zipped_file, string());
724 return "unzipped_" + zipped_file;
728 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
730 FileName const tempfile = FileName(unzipped_file.empty() ?
731 unzippedFileName(zipped_file.toFilesystemEncoding()) :
734 string const command = "gunzip -c " +
735 zipped_file.toFilesystemEncoding() + " > " +
736 tempfile.toFilesystemEncoding();
738 one.startscript(Systemcall::Wait, command);
739 // test that command was executed successfully (anon)
740 // yes, please do. (Lgb)
745 docstring const makeDisplayPath(string const & path, unsigned int threshold)
749 // If file is from LyXDir, display it as if it were relative.
750 string const system = package().system_support().absFilename();
751 if (prefixIs(str, system) && str != system)
752 return from_utf8("[" + str.erase(0, system.length()) + "]");
754 // replace /home/blah with ~/
755 string const home = package().home_dir().absFilename();
756 if (!home.empty() && prefixIs(str, home))
757 str = subst(str, home, "~");
759 if (str.length() <= threshold)
760 return from_utf8(os::external_path(str));
762 string const prefix = ".../";
765 while (str.length() > threshold)
766 str = split(str, temp, '/');
768 // Did we shorten everything away?
770 // Yes, filename itself is too long.
771 // Pick the start and the end of the filename.
772 str = onlyFilename(path);
773 string const head = str.substr(0, threshold / 2 - 3);
775 string::size_type len = str.length();
777 str.substr(len - threshold / 2 - 2, len - 1);
778 str = head + "..." + tail;
781 return from_utf8(os::external_path(prefix + str));
785 bool readLink(FileName const & file, FileName & link)
788 char linkbuffer[512];
789 // Should be PATH_MAX but that needs autconf support
790 string const encoded = file.toFilesystemEncoding();
791 int const nRead = ::readlink(encoded.c_str(),
792 linkbuffer, sizeof(linkbuffer) - 1);
795 linkbuffer[nRead] = '\0'; // terminator
796 link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
804 cmd_ret const runCommand(string const & cmd)
806 // FIXME: replace all calls to RunCommand with ForkedCall
807 // (if the output is not needed) or the code in ISpell.cpp
808 // (if the output is needed).
810 // One question is if we should use popen or
811 // create our own popen based on fork, exec, pipe
812 // of course the best would be to have a
813 // pstream (process stream), with the
814 // variants ipstream, opstream
816 #if defined (HAVE_POPEN)
817 FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
818 #elif defined (HAVE__POPEN)
819 FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
821 #error No popen() function.
824 // (Claus Hentschel) Check if popen was succesful ;-)
826 lyxerr << "RunCommand:: could not start child process" << endl;
827 return make_pair(-1, string());
833 ret += static_cast<char>(c);
837 #if defined (HAVE_PCLOSE)
838 int const pret = pclose(inf);
839 #elif defined (HAVE__PCLOSE)
840 int const pret = _pclose(inf);
842 #error No pclose() function.
846 perror("RunCommand:: could not terminate child process");
848 return make_pair(pret, ret);
852 FileName const findtexfile(string const & fil, string const & /*format*/)
854 /* There is no problem to extend this function too use other
855 methods to look for files. It could be setup to look
856 in environment paths and also if wanted as a last resort
857 to a recursive find. One of the easier extensions would
858 perhaps be to use the LyX file lookup methods. But! I am
859 going to implement this until I see some demand for it.
863 // If the file can be found directly, we just return a
864 // absolute path version of it.
865 FileName const absfile(makeAbsPath(fil));
866 if (absfile.exists())
869 // No we try to find it using kpsewhich.
870 // It seems from the kpsewhich manual page that it is safe to use
871 // kpsewhich without --format: "When the --format option is not
872 // given, the search path used when looking for a file is inferred
873 // from the name given, by looking for a known extension. If no
874 // known extension is found, the search path for TeX source files
876 // However, we want to take advantage of the format sine almost all
877 // the different formats has environment variables that can be used
878 // to controll which paths to search. f.ex. bib looks in
879 // BIBINPUTS and TEXBIB. Small list follows:
880 // bib - BIBINPUTS, TEXBIB
882 // graphic/figure - TEXPICTS, TEXINPUTS
883 // ist - TEXINDEXSTYLE, INDEXSTYLE
884 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
886 // tfm - TFMFONTS, TEXFONTS
887 // This means that to use kpsewhich in the best possible way we
888 // should help it by setting additional path in the approp. envir.var.
889 string const kpsecmd = "kpsewhich " + fil;
891 cmd_ret const c = runCommand(kpsecmd);
893 LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
894 << "kpse result = `" << rtrim(c.second, "\n\r")
897 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
904 void removeAutosaveFile(string const & filename)
906 string a = onlyPath(filename);
908 a += onlyFilename(filename);
910 FileName const autosave(a);
911 if (autosave.exists())
916 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
917 string const & message)
919 LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
920 << message << std::endl;
921 // FIXME: Why is this func deleting a file? (Lgb)
927 string const readBB_from_PSFile(FileName const & file)
929 // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
930 // It seems that every command in the header has an own line,
931 // getline() should work for all files.
932 // On the other hand some plot programs write the bb at the
933 // end of the file. Than we have in the header:
934 // %%BoundingBox: (atend)
935 // In this case we must check the end.
936 bool zipped = file.isZippedFile();
937 FileName const file_ = zipped ? unzipFile(file) : file;
938 string const format = file_.guessFormatFromContents();
940 if (format != "eps" && format != "ps") {
941 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
945 static boost::regex bbox_re(
946 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
947 std::ifstream is(file_.toFilesystemEncoding().c_str());
952 if (regex_match(s, what, bbox_re)) {
953 // Our callers expect the tokens in the string
954 // separated by single spaces.
955 // FIXME: change return type from string to something
958 os << what.str(1) << ' ' << what.str(2) << ' '
959 << what.str(3) << ' ' << what.str(4);
960 string const bb = os.str();
961 readBB_lyxerrMessage(file_, zipped, bb);
965 readBB_lyxerrMessage(file_, zipped, "no bb found");
970 int compare_timestamps(FileName const & file1, FileName const & file2)
972 // If the original is newer than the copy, then copy the original
973 // to the new directory.
976 if (file1.exists() && file2.exists()) {
977 double const tmp = difftime(file1.lastModified(), file2.lastModified());
979 cmp = tmp > 0 ? 1 : -1;
981 } else if (file1.exists()) {
983 } else if (file2.exists()) {
990 } //namespace support