/// ------------------------------------------------------------------------------------
        private static bool TryToFindAudioFile(WordCacheEntry entry, string audioFilePath,
                                               string rootPath)
        {
            if (string.IsNullOrEmpty(rootPath) || string.IsNullOrEmpty(audioFilePath))
            {
                return(false);
            }

            // First, combine the audioFilePath and the specified rootPath.
            string newPath = Path.Combine(rootPath, audioFilePath);
            var    s       = newPath.Normalize(NormalizationForm.FormC);

            if (File.Exists(s))
            {
                entry.AbsoluteAudioFilePath = s;
                return(true);
            }

            // Now try removing just the filename from audioFilePath and
            // combining that with the specified root path.
            newPath = Path.GetFileName(audioFilePath);
            newPath = Path.Combine(rootPath, newPath);
            if (File.Exists(newPath))
            {
                entry.AbsoluteAudioFilePath = newPath;
                return(true);
            }

            return(false);
        }
        /// ------------------------------------------------------------------------------------
        /// <summary>
        /// This method will determine if the specified audio file path is relative or
        /// absolute. If it's relative, then it is combined with several different absolute
        /// paths in an attempt to find the audio file.
        /// </summary>
        /// ------------------------------------------------------------------------------------
        private static string AttemptToFindMissingAudioFile(WordCacheEntry entry, string audioFilePath)
        {
            // In case the path is rooted with just a backslash (as opposed to a drive letter),
            // strip off the backslash before trying to find the audio file by combining the
            // result with various other rooted paths. I do this because combining a rooted
            // path (using Path.Combine) with any other path just returns the rooted path so
            // we're no better off than we were before the combine method was called.
            if (Path.IsPathRooted(audioFilePath))
            {
                audioFilePath = audioFilePath.TrimStart('\\');
            }

            entry.AbsoluteAudioFilePath = null;
            if (entry.RecordEntry.DataSource.FwSourceDirectFromDB &&
                TryToFindAudioFile(entry, audioFilePath, FwDBUtils.FwRootDataDir))
            {
                return(entry.AbsoluteAudioFilePath);
            }

            // Check a path relative to the data source file's path.
            if (TryToFindAudioFile(entry, audioFilePath, entry[PaField.kDataSourcePathFieldName]))
            {
                return(entry.AbsoluteAudioFilePath);
            }

            if (entry.RecordEntry.DataSource.Type == DataSourceType.FW7 &&
                TryToFindAudioFile(entry, audioFilePath, Path.Combine(entry[PaField.kDataSourcePathFieldName], "LinkedFiles\\AudioVisual")))
            {
                return(entry.AbsoluteAudioFilePath);
            }

            // Check a path relative to the project file's path
            if (TryToFindAudioFile(entry, audioFilePath, entry.Project.Folder))
            {
                return(entry.AbsoluteAudioFilePath);
            }

            // Check an 'Audio' path relative to the project file's path
            if (TryToFindAudioFile(entry, audioFilePath, Path.Combine(entry.Project.Folder, "Audio")))
            {
                return(entry.AbsoluteAudioFilePath);
            }

            // Check a path relative to the application's startup path
            if (TryToFindAudioFile(entry, audioFilePath, Application.StartupPath))
            {
                return(entry.AbsoluteAudioFilePath);
            }

            // Now try the alternate path location the user may have specified in
            // the project's undocumented alternate audio file location field.
            return(TryToFindAudioFile(entry, audioFilePath, entry.Project.AlternateAudioFileFolder) ?
                   entry.AbsoluteAudioFilePath : null);
        }
        /// ------------------------------------------------------------------------------------
        /// <summary>
        ///
        /// \tx nimeolewa.
        /// \mb ni-  me - oa    -le  -wa
        /// \ge 1S-  PF - marry -DER -PASS
        /// \ps pro- tns- v     -sfx -sfx
        ///
        /// </summary>
        /// ------------------------------------------------------------------------------------
        private static void ParseEntryAsInterlinear(RecordCacheEntry entry)
        {
            string firstInterlinearLine = entry[entry.FirstInterlinearField];

            if (string.IsNullOrEmpty(firstInterlinearLine))
            {
                return;
            }

            // Get the width of each interlinear column.
            var colWidths = GetInterlinearColumnWidths(firstInterlinearLine);

            // Store the unparsed interlinear lines in a collection of strings, then remove
            // those lines from the record cache entry so they no longer take up space.
            var unparsedLines = entry.InterlinearFields.ToDictionary(f => f, f => entry[f]);

            foreach (var field in entry.InterlinearFields)
            {
                entry.SetValue(field, null);
            }

            // Now parse each interlinear line.
            int startIndex = 0;
            int wordIndex  = 0;

            foreach (int width in colWidths)
            {
                var wordEntry = new WordCacheEntry(entry, wordIndex++);

                foreach (var line in unparsedLines.Where(l => l.Value != null && startIndex < l.Value.Length))
                {
                    wordEntry[line.Key] = (startIndex + width > line.Value.Length ?
                                           line.Value.Substring(startIndex) : line.Value.Substring(startIndex, width)).Trim();

                    if (line.Key == entry.FirstInterlinearField)
                    {
                        wordEntry.SetFieldAsFirstLineInterlinear(line.Key);
                    }
                    else
                    {
                        wordEntry.SetFieldAsSubordinateInterlinear(line.Key);
                    }
                }

                entry.WordEntries.Add(wordEntry);
                startIndex += width;
            }
        }
        /// ------------------------------------------------------------------------------------
        /// <summary>
        /// Uses the offset and length to build three groups of strings, the phones in the
        /// environment before, the phones in the environment after and the phones in the
        /// search item.
        /// </summary>
        /// <param name="entry">The underlying word cache entry from which we're building
        /// a WordListCacheEntry.</param>
        /// <param name="phones">Array of phones that make up the phonetic word. When
        /// this is null or empty, then it's assumed the WordListCacheEntry is not for a
        /// search result item.</param>
        /// <param name="offset">Offset in the phones array where a search item was found.
        /// </param>
        /// <param name="length">Length in phones of the matched search item.</param>
        /// <param name="savePhonesArray">When true, it forces the phones passed in the phones
        /// parameter to be saved in the new WordListCacheEntry rather than the
        /// new WordListCacheEntry deferring to the phones from its associated WordCacheEntry
        /// (which is passed in the entry parameter).</param>
        /// ------------------------------------------------------------------------------------
        public void Add(WordCacheEntry entry, string[] phones, int offset, int length,
                        bool savePhonesArray)
        {
            var newEntry = new WordListCacheEntry();

            newEntry.WordCacheEntry = entry;
            Add(newEntry);

            // When the array of phones is non-existent, it means
            // we're not adding an entry for a search result cache.
            if (phones == null || phones.Length == 0)
            {
                return;
            }

            // Do some preprocessing for spaces before and after.
            ProcessSpaces(ref phones, ref offset, ref length);

            IsForSearchResults        = true;
            newEntry.SearchItemOffset = offset;
            newEntry.SearchItemLength = length;

            if (savePhonesArray)
            {
                newEntry.SetPhones(phones);
            }

            // Build the environment before string.
            if (offset > 0)
            {
                newEntry.EnvironmentBefore = string.Join(string.Empty, phones, 0, offset);
            }

            // Build the environment after string.
            if (offset < phones.Length - 1)
            {
                int afterStart = offset + length;
                newEntry.EnvironmentAfter = string.Join(string.Empty, phones,
                                                        afterStart, phones.Length - afterStart);
            }

            // Build the search item string.
            newEntry.SearchItem = (length == 0 ? string.Empty :
                                   string.Join(string.Empty, phones, offset, length));
        }
 /// ------------------------------------------------------------------------------------
 public void Add(WordCacheEntry entry)
 {
     Add(entry, null, 0, 0, false);
 }