/// <summary>
        /// This is overridden to allow use of an ESENT backed MSDN content ID cache
        /// </summary>
        /// <param name="configuration">The component configuration</param>
        /// <returns>An MSDN resolver instance</returns>
        protected override MsdnResolver CreateMsdnResolver(XPathNavigator configuration)
        {
            MsdnResolver resolver;
            IDictionary<string, string> cache = null;
            int localCacheSize;

            if(BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId))
                cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary<string, string>;

            // If the shared cache already exists, return an instance that uses it.  It is assumed that all
            // subsequent instances will use the same cache.
            if(cache != null)
                return new MsdnResolver(cache, true);

            XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache");

            // If an <msdnContentIdCache> element is not specified, use the default resolver
            if(node == null)
                resolver = base.CreateMsdnResolver(configuration);
            else
            {
                string msdnIdCachePath = node.GetAttribute("cachePath", String.Empty);

                // If a cache path is not defined, use the default resolver
                if(String.IsNullOrWhiteSpace(msdnIdCachePath))
                    resolver = base.CreateMsdnResolver(configuration);
                else
                {
                    msdnIdCachePath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(msdnIdCachePath));

                    string cacheSize = node.GetAttribute("localCacheSize", String.Empty);

                    if(String.IsNullOrWhiteSpace(cacheSize) || !Int32.TryParse(cacheSize, out localCacheSize))
                        localCacheSize = 2500;

                    // Load or create the cache database and the resolver.  The resolver will dispose of the
                    // dictionary when it is disposed of since it implements IDisposable.  We won't compress the
                    // columns as they're typically not that big and there aren't usually that many entries.
                    // This gives a slight performance increase.
                    resolver = new MsdnResolver(new PersistentDictionary<string, string>(msdnIdCachePath, false)
                        { LocalCacheSize = localCacheSize }, false);

                    // We own the cache and will report statistics when done
                    ownsResolverCache = true;

                    int cacheCount = resolver.MsdnContentIdCache.Count;

                    if(cacheCount == 0)
                    {
                        // Log a diagnostic message since looking up all IDs can significantly slow the build
                        base.WriteMessage(MessageLevel.Diagnostic, "The ESENT MSDN content ID cache in '" +
                            msdnIdCachePath + "' does not exist yet.  All IDs will be looked up in this build " +
                            "which will slow it down.");
                    }
                    else
                        base.WriteMessage(MessageLevel.Info, "{0} cached MSDN content ID entries exist", cacheCount);

                    BuildComponentCore.Data[SharedMsdnContentIdCacheId] = resolver.MsdnContentIdCache;
                }
            }

            return resolver;
        }
        /// <summary>
        /// This is overridden to allow use of an SQL backed MSDN content ID cache
        /// </summary>
        /// <param name="configuration">The component configuration</param>
        /// <returns>An MSDN resolver instance</returns>
        protected override MsdnResolver CreateMsdnResolver(XPathNavigator configuration)
        {
            MsdnResolver resolver;
            IDictionary<string, string> cache = null;
            int localCacheSize;

            if(BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId))
                cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary<string, string>;

            // If the shared cache already exists, return an instance that uses it.  It is assumed that all
            // subsequent instances will use the same cache.
            if(cache != null)
                return new MsdnResolver(cache, true);

            XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache");

            // If a <cache> element is not specified, use the default resolver
            if(node == null)
                resolver = base.CreateMsdnResolver(configuration);
            else
            {
                node = configuration.SelectSingleNode("sqlCache");
                string connectionString = node.GetAttribute("connectionString", String.Empty);

                // If a connection string is not defined, use the default resolver
                if(String.IsNullOrWhiteSpace(connectionString))
                    resolver = base.CreateMsdnResolver(configuration);
                else
                {
                    string cacheSize = node.GetAttribute("msdnLocalCacheSize", String.Empty);

                    if(String.IsNullOrWhiteSpace(cacheSize) || !Int32.TryParse(cacheSize, out localCacheSize))
                        localCacheSize = 2500;

                    // Load or create the cache database and the resolver.  The resolver will dispose of the
                    // dictionary when it is disposed of since it implements IDisposable.
                    resolver = new MsdnResolver(new SqlDictionary<string>(connectionString, "ContentIds",
                        "TargetKey", "ContentId") { LocalCacheSize = localCacheSize }, false);

                    int cacheCount = resolver.MsdnContentIdCache.Count;

                    if(cacheCount == 0)
                    {
                        // Log a diagnostic message since looking up all IDs can significantly slow the build
                        base.WriteMessage(MessageLevel.Diagnostic, "The SQL MSDN content ID cache in '" +
                            connectionString + "' does not exist yet.  All IDs will be looked up in this " +
                            "build which will slow it down.");
                    }
                    else
                        base.WriteMessage(MessageLevel.Info, "{0} cached MSDN content ID entries exist", cacheCount);

                    BuildComponentCore.Data[SharedMsdnContentIdCacheId] = resolver.MsdnContentIdCache;
                }
            }

            return resolver;
        }
        //=====================================================================

        /// <inheritdoc />
        public override void Initialize(XPathNavigator configuration)
        {
            TargetDictionary newTargets;
            ReferenceLinkType type;
            string attrValue, id;

            targets = new TargetTypeDictionary();
            resolver = new LinkTextResolver(targets);

            // Get the shared instances dictionary.  Create it if it doesn't exist.
            if(BuildComponentCore.Data.ContainsKey(SharedReferenceTargetsId))
                sharedTargets = BuildComponentCore.Data[SharedReferenceTargetsId] as Dictionary<string, TargetDictionary>;

            if(sharedTargets == null)
                BuildComponentCore.Data[SharedReferenceTargetsId] = sharedTargets = new Dictionary<string, TargetDictionary>();

            // base-url is an xpath expression applied against the current document to pick up the save location of the
            // document. If specified, local links will be made relative to the base-url.
            string baseUrlValue = configuration.GetAttribute("base-url", String.Empty);

            if(!String.IsNullOrEmpty(baseUrlValue))
                baseUrl = XPathExpression.Compile(baseUrlValue);

            // hrefFormat is a string format that is used to format the value of local href attributes. The
            // default is "{0}.htm" if not specified.
            hrefFormat = (string)configuration.Evaluate("string(hrefFormat/@value)");

            if(String.IsNullOrWhiteSpace(hrefFormat))
                hrefFormat = "{0}.htm";

            // The container XPath can be replaced; this is useful
            string containerValue = configuration.GetAttribute("container", String.Empty);

            if(!String.IsNullOrEmpty(containerValue))
                XmlTargetDictionaryUtilities.ContainerExpression = containerValue;

            XPathNodeIterator targetsNodes = configuration.Select("targets");

#if DEBUG
            base.WriteMessage(MessageLevel.Diagnostic, "Loading reference link target info");

            DateTime startLoad = DateTime.Now;
#endif
            foreach(XPathNavigator targetsNode in targetsNodes)
            {
                // Get target type
                attrValue = targetsNode.GetAttribute("type", String.Empty);

                if(String.IsNullOrEmpty(attrValue))
                    base.WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute " +
                        "that specifies which type of links to create");

                if(!Enum.TryParse<ReferenceLinkType>(attrValue, true, out type))
                    base.WriteMessage(MessageLevel.Error, "'{0}' is not a supported reference link type",
                        attrValue);

                // Check for shared instance by ID.  If not there, create it and add it.
                id = targetsNode.GetAttribute("id", String.Empty);

                if(!sharedTargets.TryGetValue(id, out newTargets))
                {
                    this.WriteMessage(MessageLevel.Info, "Loading {0} reference link type targets", type);

                    newTargets = this.CreateTargetDictionary(targetsNode);
                    sharedTargets[newTargets.DictionaryId] = newTargets;
                }

                targets.Add(type, newTargets);
            }

#if DEBUG
            TimeSpan loadTime = (DateTime.Now - startLoad);
            base.WriteMessage(MessageLevel.Diagnostic, "Load time: {0} seconds", loadTime.TotalSeconds);

            // Dump targets for comparison to other versions
//            targets.DumpTargetDictionary(Path.GetFullPath("TargetDictionary.xml"));

            // Serialization test
//            targets.SerializeDictionary(Directory.GetCurrentDirectory());
#endif
            // Getting the count from a database cache can be expensive so only report it if it will be seen
            if(base.BuildAssembler.VerbosityLevel == MessageLevel.Info)
                base.WriteMessage(MessageLevel.Info, "{0} total reference link targets", targets.Count);

            if(targets.NeedsMsdnResolver)
            {
                base.WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver");

                msdnResolver = this.CreateMsdnResolver(configuration);

                string localeValue = (string)configuration.Evaluate("string(locale/@value)");

                if(msdnResolver != null && !String.IsNullOrWhiteSpace(localeValue))
                    msdnResolver.Locale = localeValue;
            }

            linkTarget = (string)configuration.Evaluate("string(linkTarget/@value)");

            if(String.IsNullOrWhiteSpace(linkTarget))
                linkTarget = "_blank";
        }
        //=====================================================================

        /// <summary>
        /// This is used to create an MSDN resolver for the reference link to use in looking up MSDN content iDs
        /// </summary>
        /// <param name="configuration">The component configuration</param>
        /// <returns>An MSDN resolver instance</returns>
        /// <remarks>This can be overridden in derived classes to provide persistent caches with backing stores
        /// other than the default dictionary serialized to a binary file.  It also allows sharing the cache
        /// across instances by placing it in the <see cref="BuildComponentCore.Data"/> dictionary using the key
        /// name <c>SharedMsdnContentIdCacheID</c>.
        /// 
        /// <para>If overridden, the <see cref="UpdateMsdnContentIdCache"/> method should also be overridden to
        /// persist changes to the cache if needed.</para></remarks>
        protected virtual MsdnResolver CreateMsdnResolver(XPathNavigator configuration)
        {
            MsdnResolver newResolver;
            IDictionary<string, string> cache = null;

            if(BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId))
                cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary<string, string>;

            // If the shared cache already exists, return an instance that uses it.  It is assumed that all
            // subsequent instances will use the same cache.
            if(cache != null)
                return new MsdnResolver(cache, true);

            // If a <cache> element is not specified, we'll use the standard resolver without a persistent cache.
            // We will share it across all instances though.
            XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache");

            if(node == null)
                newResolver = new MsdnResolver();
            else
            {
                // Keep the filename.  If we own it, we'll update the cache file when disposed.
                msdnIdCacheFile = node.GetAttribute("path", String.Empty);

                if(String.IsNullOrWhiteSpace(msdnIdCacheFile))
                    base.WriteMessage(MessageLevel.Error, "You must specify a path attribute value on the " +
                        "msdnContentIdCache element.");

                // Create the folder if it doesn't exist
                msdnIdCacheFile = Path.GetFullPath(Environment.ExpandEnvironmentVariables(msdnIdCacheFile));
                string path = Path.GetDirectoryName(msdnIdCacheFile);

                if(!Directory.Exists(path))
                    Directory.CreateDirectory(path);

                // Load the cache if it exists
                if(!File.Exists(msdnIdCacheFile))
                {
                    // Logged as a diagnostic message since looking up all IDs can significantly slow the build
                    base.WriteMessage(MessageLevel.Diagnostic, "The MSDN content ID cache '" + msdnIdCacheFile +
                        "' does not exist yet.  All IDs will be looked up in this build which will slow it down.");

                    newResolver = new MsdnResolver();
                }
                else
                    using(FileStream fs = new FileStream(msdnIdCacheFile, FileMode.Open, FileAccess.Read,
                      FileShare.Read))
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        newResolver = new MsdnResolver((IDictionary<string, string>)bf.Deserialize(fs), false);

                        base.WriteMessage(MessageLevel.Info, "Loaded {0} cached MSDN content ID entries",
                            newResolver.MsdnContentIdCache.Count);
                    }
            }

            BuildComponentCore.Data[SharedMsdnContentIdCacheId] = newResolver.MsdnContentIdCache;

            return newResolver;
        }
        //=====================================================================

        /// <summary>
        /// This is used to create an MSDN resolver for the reference link to use in looking up MSDN content iDs
        /// </summary>
        /// <param name="configuration">The component configuration</param>
        /// <returns>An MSDN resolver instance</returns>
        /// <remarks>This can be overridden in derived classes to provide persistent caches with backing stores
        /// other than the default dictionary serialized to a binary file.  It also allows sharing the cache
        /// across instances by placing it in the <see cref="BuildComponentCore.Data"/> dictionary using the key
        /// name <c>SharedMsdnContentIdCacheID</c>.
        ///
        /// <para>If overridden, the <see cref="UpdateMsdnContentIdCache"/> method should also be overridden to
        /// persist changes to the cache if needed.</para></remarks>
        protected virtual MsdnResolver CreateMsdnResolver(XPathNavigator configuration)
        {
            MsdnResolver newResolver;
            IDictionary <string, string> cache = null;

            if (BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId))
            {
                cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary <string, string>;
            }

            // If the shared cache already exists, return an instance that uses it.  It is assumed that all
            // subsequent instances will use the same cache.
            if (cache != null)
            {
                return(new MsdnResolver(cache, true));
            }

            // If a <cache> element is not specified, we'll use the standard resolver without a persistent cache.
            // We will share it across all instances though.
            XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache");

            if (node == null)
            {
                newResolver = new MsdnResolver();
            }
            else
            {
                // Keep the filename.  If we own it, we'll update the cache file when disposed.
                msdnIdCacheFile = node.GetAttribute("path", String.Empty);

                if (String.IsNullOrWhiteSpace(msdnIdCacheFile))
                {
                    base.WriteMessage(MessageLevel.Error, "You must specify a path attribute value on the " +
                                      "msdnContentIdCache element.");
                }

                // Create the folder if it doesn't exist
                msdnIdCacheFile = Path.GetFullPath(Environment.ExpandEnvironmentVariables(msdnIdCacheFile));
                string path = Path.GetDirectoryName(msdnIdCacheFile);

                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }

                // Load the cache if it exists
                if (!File.Exists(msdnIdCacheFile))
                {
                    // Logged as a diagnostic message since looking up all IDs can significantly slow the build
                    base.WriteMessage(MessageLevel.Diagnostic, "The MSDN content ID cache '" + msdnIdCacheFile +
                                      "' does not exist yet.  All IDs will be looked up in this build which will slow it down.");

                    newResolver = new MsdnResolver();
                }
                else
                {
                    using (FileStream fs = new FileStream(msdnIdCacheFile, FileMode.Open, FileAccess.Read,
                                                          FileShare.Read))
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        newResolver = new MsdnResolver((IDictionary <string, string>)bf.Deserialize(fs), false);

                        base.WriteMessage(MessageLevel.Info, "Loaded {0} cached MSDN content ID entries",
                                          newResolver.MsdnContentIdCache.Count);
                    }
                }
            }

            BuildComponentCore.Data[SharedMsdnContentIdCacheId] = newResolver.MsdnContentIdCache;

            return(newResolver);
        }
        //=====================================================================

        /// <inheritdoc />
        public override void Initialize(XPathNavigator configuration)
        {
            TargetDictionary  newTargets;
            ReferenceLinkType type;
            string            attrValue, id;

            targets  = new TargetTypeDictionary();
            resolver = new LinkTextResolver(targets);

            // Get the shared instances dictionary.  Create it if it doesn't exist.
            if (BuildComponentCore.Data.ContainsKey(SharedReferenceTargetsId))
            {
                sharedTargets = BuildComponentCore.Data[SharedReferenceTargetsId] as Dictionary <string, TargetDictionary>;
            }

            if (sharedTargets == null)
            {
                BuildComponentCore.Data[SharedReferenceTargetsId] = sharedTargets = new Dictionary <string, TargetDictionary>();
            }

            // base-url is an xpath expression applied against the current document to pick up the save location of the
            // document. If specified, local links will be made relative to the base-url.
            string baseUrlValue = configuration.GetAttribute("base-url", String.Empty);

            if (!String.IsNullOrEmpty(baseUrlValue))
            {
                baseUrl = XPathExpression.Compile(baseUrlValue);
            }

            // url-format is a string format that is used to format the value of local href attributes. The default is
            // "{0}.htm" for backwards compatibility.
            hrefFormat = configuration.GetAttribute("href-format", String.Empty);

            if (String.IsNullOrWhiteSpace(hrefFormat))
            {
                hrefFormat = "{0}.htm";
            }

            // The container XPath can be replaced; this is useful
            string containerValue = configuration.GetAttribute("container", String.Empty);

            if (!String.IsNullOrEmpty(containerValue))
            {
                XmlTargetDictionaryUtilities.ContainerExpression = containerValue;
            }

            XPathNodeIterator targetsNodes = configuration.Select("targets");

#if DEBUG
            base.WriteMessage(MessageLevel.Diagnostic, "Loading reference link target info");

            DateTime startLoad = DateTime.Now;
#endif
            foreach (XPathNavigator targetsNode in targetsNodes)
            {
                // Get target type
                attrValue = targetsNode.GetAttribute("type", String.Empty);

                if (String.IsNullOrEmpty(attrValue))
                {
                    base.WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute " +
                                      "that specifies which type of links to create");
                }

                if (!Enum.TryParse <ReferenceLinkType>(attrValue, true, out type))
                {
                    base.WriteMessage(MessageLevel.Error, "'{0}' is not a supported reference link type",
                                      attrValue);
                }

                // Check for shared instance by ID.  If not there, create it and add it.
                id = targetsNode.GetAttribute("id", String.Empty);

                if (!sharedTargets.TryGetValue(id, out newTargets))
                {
                    this.WriteMessage(MessageLevel.Info, "Loading {0} reference link type targets", type);

                    newTargets = this.CreateTargetDictionary(targetsNode);
                    sharedTargets[newTargets.DictionaryId] = newTargets;
                }

                targets.Add(type, newTargets);
            }

#if DEBUG
            TimeSpan loadTime = (DateTime.Now - startLoad);
            base.WriteMessage(MessageLevel.Diagnostic, "Load time: {0} seconds", loadTime.TotalSeconds);

            // Dump targets for comparison to other versions
//            targets.DumpTargetDictionary(Path.GetFullPath("TargetDictionary.xml"));

            // Serialization test
//            targets.SerializeDictionary(Directory.GetCurrentDirectory());
#endif
            // Getting the count from a database cache can be expensive so only report it if it will be seen
            if (base.BuildAssembler.VerbosityLevel == MessageLevel.Info)
            {
                base.WriteMessage(MessageLevel.Info, "{0} total reference link targets", targets.Count);
            }

            if (targets.NeedsMsdnResolver)
            {
                base.WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver");

                msdnResolver = this.CreateMsdnResolver(configuration);

                string localeValue = (string)configuration.Evaluate("string(locale/@value)");

                if (msdnResolver != null && !String.IsNullOrWhiteSpace(localeValue))
                {
                    msdnResolver.Locale = localeValue;
                }
            }

            linkTarget = (string)configuration.Evaluate("string(linkTarget/@value)");

            if (String.IsNullOrWhiteSpace(linkTarget))
            {
                linkTarget = "_blank";
            }
        }
Example #7
0
        private void ProcessTargetsNode(XPathNavigator targets_node)
        {
            // get target type
            string typeValue = targets_node.GetAttribute("type", String.Empty);

            if (String.IsNullOrEmpty(typeValue))
            {
                WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute that specifies which type of links to create.");
            }

            LinkType2 type = LinkType2.None;

            try
            {
                type = (LinkType2)Enum.Parse(typeof(LinkType2), typeValue, true);
                if ((type == LinkType2.Msdn) && (msdn == null))
                {
                    WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver.");
                    msdn = new MsdnResolver();
                }
            }
            catch (ArgumentException)
            {
                WriteMessage(MessageLevel.Error, String.Format("'{0}' is not a supported reference link type.", typeValue));
            }

            if (type == LinkType2.External)
            {
                ProcessExternalTargetNode(targets_node);
                return;
            }

            // get base directory
            string baseValue = targets_node.GetAttribute("base", String.Empty);

            // get file pattern
            string filesValue = targets_node.GetAttribute("files", String.Empty);

            if (String.IsNullOrEmpty(filesValue))
            {
                WriteMessage(MessageLevel.Error, "Each targets element must have a files attribute specifying which target files to load.");
            }

            // determine whether to search recursively
            bool   recurse      = false;
            string recurseValue = targets_node.GetAttribute("recurse", String.Empty);

            if (!String.IsNullOrEmpty(recurseValue))
            {
                if (String.Compare(recurseValue, Boolean.TrueString, true) == 0)
                {
                    recurse = true;
                }
                else if (String.Compare(recurseValue, Boolean.FalseString, true) == 0)
                {
                    recurse = false;
                }
                else
                {
                    WriteMessage(MessageLevel.Error, String.Format("On the targets element, recurse='{0}' is not an allowed value.", recurseValue));
                }
            }

            // turn baseValue and filesValue into directoryPath and filePattern
            string fullPath;

            if (String.IsNullOrEmpty(baseValue))
            {
                fullPath = filesValue;
            }
            else
            {
                fullPath = Path.Combine(baseValue, filesValue);
            }
            fullPath = Environment.ExpandEnvironmentVariables(fullPath);
            string directoryPath = Path.GetDirectoryName(fullPath);

            if (String.IsNullOrEmpty(directoryPath))
            {
                directoryPath = Environment.CurrentDirectory;
            }
            string filePattern = Path.GetFileName(fullPath);

            // verify that directory exists
            if (!Directory.Exists(directoryPath))
            {
                WriteMessage(MessageLevel.Error, String.Format("The targets directory '{0}' does not exist.", directoryPath));
            }

            // add the specified targets from the directory
            WriteMessage(MessageLevel.Info, String.Format("Searching directory '{0}' for targets files of the form '{1}'.", directoryPath, filePattern));
            AddTargets(directoryPath, filePattern, recurse, type);
        }
        // instantiation logic

        public ResolveReferenceLinksComponent2(BuildAssembler assembler, XPathNavigator configuration)
            : base(assembler, configuration)
        {
            // base-url is an xpath expression applied against the current document to pick up the save location of the
            // document. If specified, local links will be made relative to the base-url.
            string baseUrlValue = configuration.GetAttribute("base-url", String.Empty);

            if (!String.IsNullOrEmpty(baseUrlValue))
            {
                baseUrl = XPathExpression.Compile(baseUrlValue);
            }

            // url-format is a string format that is used to format the value of local href attributes. The default is
            // "{0}.htm" for backwards compatibility.
            string hrefFormatValue = configuration.GetAttribute("href-format", String.Empty);

            if (!String.IsNullOrEmpty(hrefFormatValue))
            {
                hrefFormat = hrefFormatValue;
            }

            // the container XPath can be replaced; this is useful
            string containerValue = configuration.GetAttribute("container", String.Empty);

            if (!String.IsNullOrEmpty(containerValue))
            {
                XmlTargetCollectionUtilities.ContainerExpression = containerValue;
            }

            targets  = new TargetCollection();
            resolver = new LinkTextResolver(targets);

            XPathNodeIterator targets_nodes = configuration.Select("targets");

            foreach (XPathNavigator targets_node in targets_nodes)
            {
                // get target type
                string typeValue = targets_node.GetAttribute("type", String.Empty);
                if (String.IsNullOrEmpty(typeValue))
                {
                    WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute that specifies which type of links to create.");
                }

                LinkType2 type = LinkType2.None;
                try {
                    type = (LinkType2)Enum.Parse(typeof(LinkType2), typeValue, true);
                    if ((type == LinkType2.Msdn) && (msdn == null))
                    {
                        WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver.");
                        msdn = new MsdnResolver();
                    }
                } catch (ArgumentException) {
                    WriteMessage(MessageLevel.Error, String.Format("'{0}' is not a supported reference link type.", typeValue));
                }

                // get base directory
                string baseValue = targets_node.GetAttribute("base", String.Empty);

                // get file pattern
                string filesValue = targets_node.GetAttribute("files", String.Empty);
                if (String.IsNullOrEmpty(filesValue))
                {
                    WriteMessage(MessageLevel.Error, "Each targets element must have a files attribute specifying which target files to load.");
                }

                // determine whether to search recursively
                bool   recurse      = false;
                string recurseValue = targets_node.GetAttribute("recurse", String.Empty);
                if (!String.IsNullOrEmpty(recurseValue))
                {
                    if (String.Compare(recurseValue, Boolean.TrueString, true) == 0)
                    {
                        recurse = true;
                    }
                    else if (String.Compare(recurseValue, Boolean.FalseString, true) == 0)
                    {
                        recurse = false;
                    }
                    else
                    {
                        WriteMessage(MessageLevel.Error, String.Format("On the targets element, recurse='{0}' is not an allowed value.", recurseValue));
                    }
                }

                // turn baseValue and filesValue into directoryPath and filePattern
                string fullPath;
                if (String.IsNullOrEmpty(baseValue))
                {
                    fullPath = filesValue;
                }
                else
                {
                    fullPath = Path.Combine(baseValue, filesValue);
                }
                fullPath = Environment.ExpandEnvironmentVariables(fullPath);
                string directoryPath = Path.GetDirectoryName(fullPath);
                if (String.IsNullOrEmpty(directoryPath))
                {
                    directoryPath = Environment.CurrentDirectory;
                }
                string filePattern = Path.GetFileName(fullPath);

                // verify that directory exists
                if (!Directory.Exists(directoryPath))
                {
                    WriteMessage(MessageLevel.Error, String.Format("The targets directory '{0}' does not exist.", directoryPath));
                }

                // add the specified targets from the directory
                WriteMessage(MessageLevel.Info, String.Format("Searching directory '{0}' for targets files of the form '{1}'.", directoryPath, filePattern));
                AddTargets(directoryPath, filePattern, recurse, type);
            }

            WriteMessage(MessageLevel.Info, String.Format("Loaded {0} reference targets.", targets.Count));

            string locale_value = configuration.GetAttribute("locale", String.Empty);

            if (!String.IsNullOrEmpty(locale_value) && msdn != null)
            {
                msdn.Locale = locale_value;
            }

            string target_value = configuration.GetAttribute("linkTarget", String.Empty);

            if (!String.IsNullOrEmpty(target_value))
            {
                linkTarget = target_value;
            }
        }
        // instantiation logic 

        public ResolveReferenceLinksComponent2 (BuildAssembler assembler, XPathNavigator configuration)
            : base(assembler, configuration) {

            // base-url is an xpath expression applied against the current document to pick up the save location of the
            // document. If specified, local links will be made relative to the base-url.
            string baseUrlValue = configuration.GetAttribute("base-url", String.Empty);
            if (!String.IsNullOrEmpty(baseUrlValue))
                baseUrl = XPathExpression.Compile(baseUrlValue);

            // url-format is a string format that is used to format the value of local href attributes. The default is
            // "{0}.htm" for backwards compatibility.
            string hrefFormatValue = configuration.GetAttribute("href-format", String.Empty);
            if (!String.IsNullOrEmpty(hrefFormatValue))
                hrefFormat = hrefFormatValue;

            // the container XPath can be replaced; this is useful
            string containerValue = configuration.GetAttribute("container", String.Empty);
            if (!String.IsNullOrEmpty(containerValue)) XmlTargetCollectionUtilities.ContainerExpression = containerValue;

            targets = new TargetCollection();
            resolver = new LinkTextResolver(targets);

            XPathNodeIterator targets_nodes = configuration.Select("targets");
            foreach (XPathNavigator targets_node in targets_nodes) {

                // get target type
                string typeValue = targets_node.GetAttribute("type", String.Empty);
                if (String.IsNullOrEmpty(typeValue)) WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute that specifies which type of links to create.");

                LinkType2 type = LinkType2.None;
                try {
                    type = (LinkType2)Enum.Parse(typeof(LinkType2), typeValue, true);
                    if ((type == LinkType2.Msdn) && (msdn == null)) {
                        WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver.");
                        msdn = new MsdnResolver();
                    }
                } catch (ArgumentException) {
                    WriteMessage(MessageLevel.Error, String.Format("'{0}' is not a supported reference link type.", typeValue));
                }

                // get base directory
                string baseValue = targets_node.GetAttribute("base", String.Empty);

                // get file pattern
                string filesValue = targets_node.GetAttribute("files", String.Empty);
                if (String.IsNullOrEmpty(filesValue)) WriteMessage(MessageLevel.Error, "Each targets element must have a files attribute specifying which target files to load.");

                // determine whether to search recursively
                bool recurse = false;
                string recurseValue = targets_node.GetAttribute("recurse", String.Empty);
                if (!String.IsNullOrEmpty(recurseValue)) {
                    if (String.Compare(recurseValue, Boolean.TrueString, true) == 0) {
                        recurse = true;
                    } else if (String.Compare(recurseValue, Boolean.FalseString, true) == 0) {
                        recurse = false;
                    } else {
                        WriteMessage(MessageLevel.Error, String.Format("On the targets element, recurse='{0}' is not an allowed value.", recurseValue));
                    }
                }

                // turn baseValue and filesValue into directoryPath and filePattern
                string fullPath;
                if (String.IsNullOrEmpty(baseValue)) {
                    fullPath = filesValue;
                } else {
                    fullPath = Path.Combine(baseValue, filesValue);
                }
                fullPath = Environment.ExpandEnvironmentVariables(fullPath);
                string directoryPath = Path.GetDirectoryName(fullPath);
                if (String.IsNullOrEmpty(directoryPath)) directoryPath = Environment.CurrentDirectory;
                string filePattern = Path.GetFileName(fullPath);

                // verify that directory exists
                if (!Directory.Exists(directoryPath)) WriteMessage(MessageLevel.Error, String.Format("The targets directory '{0}' does not exist.", directoryPath));

                // add the specified targets from the directory
                WriteMessage(MessageLevel.Info, String.Format("Searching directory '{0}' for targets files of the form '{1}'.", directoryPath, filePattern));
                AddTargets(directoryPath, filePattern, recurse, type);

            }

            WriteMessage(MessageLevel.Info, String.Format("Loaded {0} reference targets.", targets.Count));

            string locale_value = configuration.GetAttribute("locale", String.Empty);
            if (!String.IsNullOrEmpty(locale_value) && msdn != null) msdn.Locale = locale_value;

            string target_value = configuration.GetAttribute("linkTarget", String.Empty);
            if (!String.IsNullOrEmpty(target_value)) linkTarget = target_value; 
        }