summaryrefslogtreecommitdiffstats
path: root/tools/Sandcastle/Source/BuildAssembler/BuildComponents/SaveComponent.cs
blob: 8b8fea9aa792caafdef62ee3dc2ca840c02ebf3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// 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.Configuration;
using System.Text;
using System.Xml;
using System.Xml.XPath;

using System.IO;

namespace Microsoft.Ddue.Tools {

	public class SaveComponent : BuildComponent {

		private CustomContext context = new CustomContext();

		private XPathExpression path_expression;

        private XPathExpression select_expression;

		private XmlWriterSettings settings = new XmlWriterSettings();

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

			// load the target path format
			XPathNavigator save_node = configuration.SelectSingleNode("save");
			if (save_node == null) throw new ConfigurationErrorsException("When instantiating a save component, you must specify a the target file using the <save> element.");

			string base_value = save_node.GetAttribute("base", String.Empty);
			if (!String.IsNullOrEmpty(base_value)) {
				basePath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(base_value));
			}

			string path_value = save_node.GetAttribute("path", String.Empty);
			if (String.IsNullOrEmpty(path_value)) WriteMessage(MessageLevel.Error, "Each save element must have a path attribute specifying an XPath that evaluates to the location to save the file.");
			path_expression = XPathExpression.Compile(path_value);

            string select_value = save_node.GetAttribute("select", String.Empty);
            if (!String.IsNullOrEmpty(select_value))
                select_expression = XPathExpression.Compile(select_value);

            settings.Encoding = Encoding.UTF8;

			string indent_value = save_node.GetAttribute("indent", String.Empty);
			if (!String.IsNullOrEmpty(indent_value)) settings.Indent = Convert.ToBoolean(indent_value);

			string omit_value = save_node.GetAttribute("omit-xml-declaration", String.Empty);
			if (!String.IsNullOrEmpty(omit_value)) settings.OmitXmlDeclaration = Convert.ToBoolean(omit_value);

            linkPath = save_node.GetAttribute("link", String.Empty);
            if (String.IsNullOrEmpty(linkPath)) linkPath = "../html";

			// encoding

			settings.CloseOutput = true;

		}

		private string basePath = null;

        private string linkPath = null;

		public override void Apply (XmlDocument document, string key) {

			// set the evaluation context
			context["key"] = key;

			XPathExpression path_xpath = path_expression.Clone();
			path_xpath.SetContext(context);

			// evaluate the path
			string path = document.CreateNavigator().Evaluate(path_xpath).ToString();
            string file = Path.GetFileName(path);
            
            string fileLinkPath = Path.Combine(linkPath, file);
            if (basePath != null) path = Path.Combine(basePath, path);
                       

            // *SECURIY* The path name may be derived from user entered meta data in Doc Studio and as such 
            // it is not trustworthy. To pass, the path must be inside the directory tree base_directory 
            // (which is the current directory if a path is not specified).

            // This test is causing problems
            /*
            string absoluteBasePath = (basePath == null) ? Directory.GetCurrentDirectory() : Path.GetFullPath(basePath);
            string targetPath = Path.GetDirectoryName(path);
            if (!targetPath.StartsWith(absoluteBasePath, StringComparison.CurrentCultureIgnoreCase)) {
                WriteMessage(MessageLevel.Error, string.Format("Cannot save document outside of base directory: {0}", targetPath));
                return;
            }
            */
            string targetDirectory = Path.GetDirectoryName(path);
            if (!Directory.Exists(targetDirectory)) Directory.CreateDirectory(targetDirectory);

            // save the document
            // select_expression determines which nodes get saved. If there is no select_expression
            // we simply save the root node as before. If there is a select_expression, we evaluate the
            // xpath expression and save the resulting node set. The select expression also enables the 
            // "literal-text" processing instruction, which outputs its content as unescaped text.
            if (select_expression == null) {
                XmlNode doctype = document.DocumentType;
                try {
                    //Console.WriteLine("path = '{0}'", path);
                    //document.Save(path);

                    using (XmlWriter writer = XmlWriter.Create(path, settings)) {
                        document.Save(writer);
                    }

                } catch (IOException e) {
                    WriteMessage(MessageLevel.Error, String.Format("An access error occured while attempting to save to the file '{0}'. The error message is: {1}", path, BuildComponentUtilities.GetExceptionMessage(e)));
                } catch (XmlException e) {
                    WriteMessage(MessageLevel.Error, String.Format("Invalid XML was written to the output file '{0}'. The error message is: '{1}'", path, BuildComponentUtilities.GetExceptionMessage(e)));
                }

                // Get the relative html path for HXF generation.
                int index = fileLinkPath.IndexOf('/');
                string htmlPath = fileLinkPath.Substring(index + 1, fileLinkPath.Length - (index + 1));
                
                FileCreatedEventArgs fe = new FileCreatedEventArgs(htmlPath, Path.GetDirectoryName(targetDirectory));
                OnComponentEvent(fe);
            }
            else {
                // IMPLEMENTATION NOTE: The separate StreamWriter is used to maintain XML indenting.
                // Without it the XmlWriter won't honor our indent settings after plain text nodes have been
                // written.
                settings.ConformanceLevel = ConformanceLevel.Auto;
                using (StreamWriter output = File.CreateText(path)) {
                    using (XmlWriter writer = XmlWriter.Create(output, settings)) {
                        XPathExpression select_xpath = select_expression.Clone();
                        select_xpath.SetContext(context);
                        XPathNodeIterator ni = document.CreateNavigator().Select(select_expression);
                        while (ni.MoveNext()) {
                            if (ni.Current.NodeType == XPathNodeType.ProcessingInstruction && ni.Current.Name.Equals("literal-text")) {
                                writer.Flush();
                                output.Write(ni.Current.Value);
                            }
                            else
                                ni.Current.WriteSubtree(writer);
                        }
                    }
                }
            }
		}
	}
}