summaryrefslogtreecommitdiffstats
path: root/tools/Sandcastle/Source/BuildAssembler/BuildComponents/SharedContentComponent.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/Sandcastle/Source/BuildAssembler/BuildComponents/SharedContentComponent.cs')
-rw-r--r--tools/Sandcastle/Source/BuildAssembler/BuildComponents/SharedContentComponent.cs377
1 files changed, 377 insertions, 0 deletions
diff --git a/tools/Sandcastle/Source/BuildAssembler/BuildComponents/SharedContentComponent.cs b/tools/Sandcastle/Source/BuildAssembler/BuildComponents/SharedContentComponent.cs
new file mode 100644
index 0000000..f339800
--- /dev/null
+++ b/tools/Sandcastle/Source/BuildAssembler/BuildComponents/SharedContentComponent.cs
@@ -0,0 +1,377 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.IO;
+using System.Xml;
+using System.Xml.XPath;
+using System.Xml.Schema;
+
+// still have problems with spaces
+
+namespace Microsoft.Ddue.Tools {
+
+ public class SharedContentComponent : BuildComponent {
+
+ public SharedContentComponent (BuildAssembler assembler, XPathNavigator configuration) : base(assembler, configuration) {
+
+ // get context
+ context = GetContext(configuration);
+
+ // get the tags to be resolved
+ XPathNodeIterator resolve_nodes = configuration.Select("replace");
+ foreach (XPathNavigator resolve_node in resolve_nodes) {
+ string path = resolve_node.GetAttribute("elements", String.Empty);
+ if (String.IsNullOrEmpty(path)) path = "//(include|includeAttribute)";
+ // if (String.IsNullOrEmpty(path)) WriteMessage(MessageLevel.Error, "Each resolve element must contain a path attribute specifying an XPath expression for shared content elements.");
+ try {
+ XPathExpression path_expresion = XPathExpression.Compile(path, context);
+ } catch (XPathException) {
+ WriteMessage(MessageLevel.Error, String.Format("The elements expression '{0}' is not a valid XPath.", path));
+ }
+
+ string item = resolve_node.GetAttribute("item", String.Empty);
+ if (String.IsNullOrEmpty(item)) item = "string(@item)";
+ try {
+ XPathExpression item_expression = XPathExpression.Compile(item, context);
+ } catch (XPathException) {
+ WriteMessage(MessageLevel.Error, String.Format("The item expression '{0}' is not a valid XPath.", item));
+ }
+
+ string parameters = resolve_node.GetAttribute("parameters", String.Empty);
+ if (String.IsNullOrEmpty(parameters)) parameters = "parameter";
+
+ string attribute = resolve_node.GetAttribute("attribute", String.Empty);
+ if (String.IsNullOrEmpty(attribute)) attribute = "string(@name)";
+
+ elements.Add( new SharedContentElement(path, item, parameters, attribute, context) );
+ }
+
+ // Console.WriteLine("{0} elements explicitly defined", elements.Count);
+
+ if (elements.Count == 0) elements.Add( new SharedContentElement(@"//include | //includeAttribute", "string(@item)", "parameter", "string(@name)", context) );
+
+ // get the source and target formats
+ XPathNodeIterator content_nodes = configuration.Select("content");
+ foreach (XPathNavigator content_node in content_nodes)
+ {
+ // get the files
+ string sharedContentFiles = content_node.GetAttribute("file", String.Empty);
+ if (String.IsNullOrEmpty(sharedContentFiles))
+ WriteMessage(MessageLevel.Error, "The content/@file attribute must specify a path.");
+ ParseDocuments(sharedContentFiles);
+ }
+ WriteMessage(MessageLevel.Info, String.Format("Loaded {0} shared content items.", content.Count));
+ }
+
+ public void ParseDocuments(string wildcardPath)
+ {
+ string sharedContentFiles = Environment.ExpandEnvironmentVariables(wildcardPath);
+ if (String.IsNullOrEmpty(sharedContentFiles))
+ WriteMessage(MessageLevel.Error, "The content/@file attribute specifies an empty string.");
+
+ WriteMessage(MessageLevel.Info, String.Format("Searching for files that match '{0}'.", sharedContentFiles));
+ string directoryPart = Path.GetDirectoryName(sharedContentFiles);
+ if (String.IsNullOrEmpty(directoryPart))
+ directoryPart = Environment.CurrentDirectory;
+ directoryPart = Path.GetFullPath(directoryPart);
+ string filePart = Path.GetFileName(sharedContentFiles);
+ string[] files = Directory.GetFiles(directoryPart, filePart);
+ foreach (string file in files)
+ LoadContent(file);
+ WriteMessage(MessageLevel.Info, String.Format("Found {0} files in {1}.", files.Length, sharedContentFiles));
+ }
+
+ private void LoadContent(string file)
+ {
+
+ WriteMessage(MessageLevel.Info, String.Format("Loading shared content file '{0}'.", file) );
+
+ try {
+ XmlReader reader = XmlReader.Create(file);
+
+ try {
+ reader.MoveToContent();
+ while (!reader.EOF) {
+
+ if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "item")) {
+
+ string key = reader.GetAttribute("id").ToLower();
+ string value = reader.ReadInnerXml();
+
+ if (content.ContainsKey(key)) WriteMessage(MessageLevel.Info, String.Format("Overriding shared content item '{0}' with value in file '{1}'.", key, file));
+ content[key] = value;
+ // content.Add(key, value);
+ } else {
+ reader.Read();
+ }
+
+ }
+ } finally {
+ reader.Close();
+ }
+
+ } catch (IOException e) {
+ WriteMessage(MessageLevel.Error, String.Format("The shared content file '{0}' could not be opened. The error message is: {1}", file, BuildComponentUtilities.GetExceptionMessage(e)));
+ } catch (XmlException e) {
+ WriteMessage(MessageLevel.Error, String.Format("The shared content file '{0}' is not well-formed. The error message is: {1}", file, BuildComponentUtilities.GetExceptionMessage(e)));
+ } catch (XmlSchemaException e) {
+ WriteMessage(MessageLevel.Error, String.Format("The shared content file '{0}' is not valid. The error message is: {1}", file, BuildComponentUtilities.GetExceptionMessage(e)));
+ }
+
+ }
+
+ private void DetectLoops () {
+
+ }
+
+ // Stored data
+
+ // The context
+ private CustomContext context = new CustomContext();
+
+ // The shared content items
+ private Dictionary<string,string> content = new Dictionary<string,string>();
+
+ // THe shared content elements
+ private List<SharedContentElement> elements = new List<SharedContentElement>();
+
+ public override void Apply (XmlDocument document, string key) {
+ ResolveContent(document);
+ }
+
+ // private XPathExpression expression = XPathExpression.Compile("//include | //includeAttribute");
+
+ private void ResolveContent (XmlDocument document) {
+ //Console.WriteLine("doc={0}", document.CreateNavigator().InnerXml);
+ ResolveContent(document, document.CreateNavigator());
+ }
+
+ private void ResolveContent (XmlDocument document, XPathNavigator start) {
+
+ // for each kind of shared content element
+ foreach (SharedContentElement element in elements) {
+
+ // find all such elements
+ XPathNodeIterator nodeIterator = start.Select(element.Path);
+ // Console.WriteLine("Found {0} shared content elements.", nodeIterator.Count);
+
+ // convert to an array so as not to cause an error when manipulating the document
+ XPathNavigator[] nodes = ConvertIteratorToArray(nodeIterator);
+
+ // process each element
+ foreach (XPathNavigator node in nodes) {
+
+ // Console.WriteLine(node.LocalName);
+
+ // get the key
+ string item = node.Evaluate(element.Item).ToString().ToLower();
+
+ // check for missing key
+ if (String.IsNullOrEmpty(item)) {
+ WriteMessage(MessageLevel.Warn, "A shared content element did not specify an item.");
+ } else {
+
+ // Console.WriteLine("node={0}", node.OuterXml);
+
+ // extract parameters
+ List<string> parameters = new List<string>();
+ XPathNodeIterator parameter_nodes = node.Select(element.Parameters);
+ foreach (XPathNavigator parameter_node in parameter_nodes) {
+ string parameter = BuildComponentUtilities.GetInnerXml(parameter_node);
+ // Console.WriteLine("parameter={0}", parameter);
+ parameters.Add(parameter);
+ }
+
+ // get the content
+ string content = GetContent(item, parameters.ToArray());
+
+ // check for missing content
+ if (content == null) {
+ WriteMessage(MessageLevel.Warn, String.Format("Missing shared content item. Tag:'{0}'; Id:'{1}'.", node.LocalName, item));
+ } else {
+
+ // store the content in a document fragment
+ XmlDocumentFragment fragment = document.CreateDocumentFragment();
+ fragment.InnerXml = content;
+
+ // resolve any shared content in the fragment
+ ResolveContent(document, fragment.CreateNavigator());
+ //string resolvedContent = fragment.InnerXml;
+ //Console.WriteLine("value = '{0}'", resolvedContent);
+
+ // look for an attribute name
+ string attribute = node.Evaluate(element.Attribute).ToString();
+
+ // insert the resolved content
+ if (String.IsNullOrEmpty(attribute)) {
+ // as mixed content
+ // node.InsertAfter(resolvedContent);
+ XmlWriter writer = node.InsertAfter();
+ fragment.WriteTo(writer);
+ writer.Close();
+ } else {
+ // as an attribute
+ XPathNavigator parent = node.CreateNavigator();
+ parent.MoveToParent();
+ parent.CreateAttribute(String.Empty, attribute, String.Empty, fragment.InnerText);
+ }
+
+ }
+
+
+ }
+
+ // keep a reference to the parent element
+ XPathNavigator parentElement = node.CreateNavigator();
+ parentElement.MoveToParent();
+
+ // remove the node
+ node.DeleteSelf();
+
+ // if there is no content left in the parent element, make sure it is self-closing
+ if (!parentElement.HasChildren && !parentElement.IsEmptyElement) {
+
+ //If 'node' was already the root then we will have a blank node now and
+ //doing an InsertAfter() will throw an exception.
+ if (parentElement.Name.Length > 0)
+ {
+ // create a new element
+ XmlWriter attributeWriter = parentElement.InsertAfter();
+ attributeWriter.WriteStartElement(parentElement.Prefix, parentElement.LocalName, parentElement.NamespaceURI);
+
+ // copy attributes to it
+ XmlReader attributeReader = parentElement.ReadSubtree();
+ attributeReader.Read();
+ attributeWriter.WriteAttributes(attributeReader, false);
+ attributeReader.Close();
+
+ // close it
+ attributeWriter.WriteEndElement();
+ attributeWriter.Close();
+
+ // delete the old element
+ parentElement.DeleteSelf();
+ }
+ else
+ {
+ //if we are inside a tag such as title, removing the content will make it in the
+ //form of <title /> which is not allowed in html.
+ //Since this usually means there is a problem with the shared content or the transforms
+ //leading up to this we will just report the error here.
+ WriteMessage(MessageLevel.Error, "Error replacing item.");
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+ // look up shared content
+ private string GetContent (string key, string[] parameters) {
+
+ string value;
+ if (content.TryGetValue(key, out value)) {
+ try {
+ value = String.Format(value, parameters);
+ } catch (FormatException) {
+ WriteMessage(MessageLevel.Error, String.Format("The shared content item '{0}' could not be formatted with {1} parameters.", key, parameters.Length));
+ }
+
+ return(value);
+ } else {
+ return(null);
+ }
+
+ }
+
+ private static XPathNavigator[] ConvertIteratorToArray (XPathNodeIterator iterator) {
+ XPathNavigator[] result = new XPathNavigator[iterator.Count];
+ for (int i=0; i<result.Length; i++) {
+ iterator.MoveNext();
+ result[i] = iterator.Current.Clone();
+ }
+ return(result);
+ }
+
+ private static CustomContext GetContext (XPathNavigator configuration) {
+
+ CustomContext context = new CustomContext();
+
+ XPathNodeIterator context_nodes = configuration.Select("context");
+ foreach (XPathNavigator context_node in context_nodes) {
+ string prefix = context_node.GetAttribute("prefix", String.Empty);
+ string name = context_node.GetAttribute("name", String.Empty);
+ context.AddNamespace(prefix, name);
+ }
+
+ return(context);
+
+ }
+
+ }
+
+ internal class SharedContentElement {
+
+ public SharedContentElement (string path, string item, string parameters, string attribute, IXmlNamespaceResolver context) {
+ this.path = XPathExpression.Compile(path, context);
+ this.item = XPathExpression.Compile(item, context);
+ this.parameters = XPathExpression.Compile(parameters, context);
+ this.attribute = XPathExpression.Compile(attribute, context);
+ }
+
+
+
+ public SharedContentElement (string path, string item, string parameters, string attribute) {
+ this.path = XPathExpression.Compile(path);
+ this.item = XPathExpression.Compile(item);
+ this.parameters = XPathExpression.Compile(parameters);
+ if (attribute != null) {
+ this.attribute = XPathExpression.Compile(attribute);
+ }
+ }
+
+ private XPathExpression path;
+
+ private XPathExpression item;
+
+ private XPathExpression parameters;
+
+ private XPathExpression attribute;
+
+ public XPathExpression Path {
+ get {
+ return(path);
+ }
+ }
+
+ public XPathExpression Item {
+ get {
+ return(item);
+ }
+ }
+
+ public XPathExpression Parameters {
+ get {
+ return(parameters);
+ }
+ }
+
+ public XPathExpression Attribute {
+ get {
+ return(attribute);
+ }
+ }
+
+ public bool IsAttribute {
+ get {
+ return(attribute != null);
+ }
+ }
+
+ }
+
+}