// Copyright © Microsoft Corporation. // This source file is subject to the Microsoft Permissive License. // See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx. // All other rights reserved. using System; using System.Collections.Generic; using System.Configuration; using System.Xml; using System.Xml.XPath; using System.IO; // still have problems with spaces namespace Microsoft.Ddue.Tools { public class TaskGrabberComponent : BuildComponent { private XmlNamespaceManager nsManager = new XmlNamespaceManager(new NameTable()); private XPathExpression valueQuery = null; private XPathExpression keyQuery = null; private string xpathFromConfig = string.Empty; private Dictionary> bKeywordMap = new Dictionary>(); private Dictionary index = new Dictionary(); // what to copy private List copySets = new List(); public TaskGrabberComponent(BuildAssembler assembler, XPathNavigator configuration) : base(assembler, configuration) { XPathNavigator keywordsNode = configuration.SelectSingleNode("keywords"); if (keywordsNode == null) return; string filespec = keywordsNode.GetAttribute("files", String.Empty); filespec = Environment.ExpandEnvironmentVariables(filespec); string keywordXPath = keywordsNode.GetAttribute("keyword", String.Empty); string topicXPath = keywordsNode.GetAttribute("topic", String.Empty); if (String.IsNullOrEmpty(keywordXPath) || String.IsNullOrEmpty(topicXPath) || String.IsNullOrEmpty(filespec)) return; xpathFromConfig = keywordXPath; string[] keywordFiles = null; if (File.Exists(filespec)) { // we're loading a single file AddBKeywords(filespec, topicXPath, keywordXPath); } else { // must be loading a set of files if (Directory.Exists(filespec)) { // if they specified a directory, transform all the files in the directory keywordFiles = Directory.GetFiles(filespec); } else { // it's not a file or a directory, maybe it's a path with wildcards string directoryPath = Path.GetDirectoryName(filespec); string filePath = Path.GetFileName(filespec); keywordFiles = Directory.GetFiles(directoryPath, filePath); } foreach (string file in keywordFiles) { // load targets from each file AddBKeywords(file, topicXPath, keywordXPath); } } // set up the context // put in a default entry for ddue nsManager.AddNamespace("ddue", "http://ddue.schemas.microsoft.com/authoring/2003/6"); // look for context nodes, which could override the default XPathNodeIterator contextNodes = configuration.Select("context"); foreach (XPathNavigator contextNode in contextNodes) { string prefix = contextNode.GetAttribute("prefix", String.Empty); string uri = contextNode.GetAttribute("name", String.Empty); nsManager.AddNamespace(prefix, uri); } // set up the index of source files XPathNodeIterator sourceNodes = configuration.Select("source"); foreach (XPathNavigator sourceNode in sourceNodes) { string valueXPath = sourceNode.GetAttribute("value", String.Empty); string keyXPath = sourceNode.GetAttribute("key", String.Empty); if (String.IsNullOrEmpty(valueXPath) || String.IsNullOrEmpty(keyXPath)) { WriteMessage(MessageLevel.Error, "@key and @value must be set in the 'source' node."); return; } keyQuery = XPathExpression.Compile(keyXPath); valueQuery = XPathExpression.Compile(valueXPath); // search the data directories for entries XPathNodeIterator dataNodes = sourceNode.Select("data"); foreach (XPathNavigator dataNode in dataNodes) { string dataFiles = dataNode.GetAttribute("files", String.Empty); dataFiles = Environment.ExpandEnvironmentVariables(dataFiles); if ((dataFiles == null) || (dataFiles.Length == 0)) throw new ConfigurationErrorsException("When instantiating a CopyFromDirectory component, you must specify a directory path using the files attribute."); WriteMessage(MessageLevel.Info, String.Format("Searching for files that match '{0}'.", dataFiles)); int fileCount = ParseDocuments(dataFiles); WriteMessage(MessageLevel.Info, String.Format("Found {0} files.", fileCount)); } WriteMessage(MessageLevel.Info, String.Format("Indexed {0} elements.", index.Count)); } // get the copy commands XPathNodeIterator copyNodes = configuration.Select("copy"); foreach (XPathNavigator copyNode in copyNodes) { string sourceXPath = copyNode.GetAttribute("source", String.Empty); string targetXPath = copyNode.GetAttribute("target", String.Empty); if (String.IsNullOrEmpty(sourceXPath) || String.IsNullOrEmpty(targetXPath)) { WriteMessage(MessageLevel.Error, "@source and @target must be set in the 'copy' node."); return; } copySets.Add(new CopySet(sourceXPath, targetXPath, nsManager)); } } private string currentKey = string.Empty; public override void Apply (XmlDocument document, string key) { currentKey = key; XPathNavigator targetDoc = document.CreateNavigator(); foreach (CopySet copySet in copySets) { XPathExpression targetExpression = copySet.GetTargetExpression(targetDoc, key); // get the target nodes in the document XPathNavigator targetNode = targetDoc.SelectSingleNode(targetExpression); while (targetNode != null) { string targetId = targetNode.Value; int pound = (string.IsNullOrEmpty(targetId)) ? -1 : targetId.IndexOf("#"); string bkeyword = (pound == -1) ? "" : targetId.Substring(pound + 1); if (bkeyword == "") { WriteMessage(MessageLevel.Warn, string.Format("Invalid id '{0}' in topic '{1}'.", targetId, currentKey)); // delete this target and get the next target node targetNode.DeleteSelf(); targetNode = targetDoc.SelectSingleNode(targetExpression); continue; } List idList; if (!bKeywordMap.TryGetValue(bkeyword, out idList)) { WriteMessage(MessageLevel.Warn, string.Format("B-keyword not found '{0}' in topic '{1}'.", targetId, currentKey)); // delete this target and get the next target node targetNode.DeleteSelf(); targetNode = targetDoc.SelectSingleNode(targetExpression); continue; } if (idList.Count > 1) Console.Write(""); // create a 'tasks' node to replace the target XPathNavigator tasksNode = document.CreateElement("tasks").CreateNavigator(); tasksNode.CreateAttribute(string.Empty, "bkeyword", string.Empty, bkeyword); foreach (string topicId in idList) { //create a task node for this source topic XPathNavigator taskNode = document.CreateElement("task").CreateNavigator(); taskNode.CreateAttribute(string.Empty, "topicId", string.Empty, topicId); // get the source document for the topic id string filepath; if (!index.TryGetValue(topicId, out filepath)) { WriteMessage(MessageLevel.Warn, string.Format("No file found for topicId '{0}' for B-keyword '{1}'. Source topic: '{2}'.", topicId, bkeyword, currentKey)); continue; } XPathNavigator sourceDoc = new XPathDocument(filepath).CreateNavigator(); XPathNavigator sourceNode = sourceDoc.SelectSingleNode(valueQuery); if (sourceNode == null) continue; XPathNodeIterator sources = sourceNode.Select(copySet.SourceExpression); // append the source nodes to the target node if (sources.Count > 0) { foreach (XPathNavigator source in sources) taskNode.AppendChild(source); } tasksNode.AppendChild(taskNode); } targetNode.ReplaceSelf(tasksNode); // get the next target node targetNode = targetDoc.SelectSingleNode(targetExpression); } } } public int ParseDocuments(string wildcardPath) { string directoryPart = Path.GetDirectoryName(wildcardPath); if (String.IsNullOrEmpty(directoryPart)) directoryPart = Environment.CurrentDirectory; directoryPart = Path.GetFullPath(directoryPart); string filePart = Path.GetFileName(wildcardPath); string[] files = Directory.GetFiles(directoryPart, filePart); foreach (string file in files) ParseDocument(file); return (files.Length); } private void ParseDocument(string file) { try { XPathDocument document = new XPathDocument(file); // set context for the xpath expression valueQuery.SetContext(nsManager); keyQuery.SetContext(nsManager); XPathNodeIterator valueNodes = document.CreateNavigator().Select(valueQuery); foreach (XPathNavigator valueNode in valueNodes) { XPathNavigator keyNode = valueNode.SelectSingleNode(keyQuery); if (keyNode == null) continue; string key = keyNode.Value; // log multiple occurences of a single id if (index.ContainsKey(key)) { WriteMessage(MessageLevel.Warn, String.Format("Entries for the key '{0}' occur in both '{1}' and '{2}'. The first entry will be used.", key, index[key], file)); } else { index[key] = file; } } } catch (Exception e) { WriteMessage(MessageLevel.Error, e.Message); } } private XPathDocument GetDocument(string identifier) { string file = index[identifier]; XPathDocument document = new XPathDocument(file); return (document); } private void AddBKeywords(string file, string topicXPath, string keywordXPath) { XPathDocument document = new XPathDocument(file); XPathNodeIterator targetNodes = document.CreateNavigator().Select(topicXPath); foreach (XPathNavigator targetNode in targetNodes) { string topicId = targetNode.GetAttribute("id", string.Empty); if (string.IsNullOrEmpty(topicId)) continue; foreach (XPathNavigator keywordNode in targetNode.Select(keywordXPath)) { string keyword = keywordNode.Value; if (string.IsNullOrEmpty(keyword)) continue; AddValueToListDictionary(bKeywordMap, keyword, topicId); } } } public static void AddValueToListDictionary(Dictionary> dict, K key, V value) { List list; try { if (!dict.TryGetValue(key, out list)) { list = new List(); dict.Add(key, list); } list.Add(value); } catch (Exception e) { Console.WriteLine("Exception adding to dictionary {0}", key); Console.WriteLine(e); throw; } } } internal class CopySet { public CopySet(string sourceXPath, string targetXPath, XmlNamespaceManager nsMgr) : this(null, sourceXPath, targetXPath, nsMgr) { } public CopySet(IndexedFileCache cache, string sourceXPath, string targXPath, XmlNamespaceManager nsMgr) { this.fileCache = cache; this.namespaceMgr = nsMgr; source = XPathExpression.Compile(sourceXPath); source.SetContext(nsMgr); if (targXPath.Contains("{0}")) { targetXPath = targXPath; } else { target = XPathExpression.Compile(targXPath); target.SetContext(nsMgr); } } private IndexedFileCache fileCache; public IndexedFileCache FileCache { get { return (fileCache); } } private XPathExpression source; public XPathExpression SourceExpression { get { return (source); } } private XmlNamespaceManager namespaceMgr; private string targetXPath = string.Empty; private XPathExpression target = null; public XPathExpression GetTargetExpression(XPathNavigator targetDoc, string key) { if (target == null) { XPathExpression keyedTargetExpression = targetDoc.Compile(string.Format(targetXPath, key)); keyedTargetExpression.SetContext(namespaceMgr); return keyedTargetExpression; } return target; } } }