Merge pull request #2338 from BogdanovKirill/httpwritefix3
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / XmlPeek.cs
1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>Returns the value specified by XPath.</summary>
6 //-----------------------------------------------------------------------
7
8 using System;
9 using System.Collections.Generic;
10 using System.IO;
11 using System.Linq;
12 using System.Reflection;
13 using System.Security;
14 using System.Security.Permissions;
15 using System.Text;
16 using System.Xml;
17 using System.Xml.Xsl;
18 using System.Xml.XPath;
19
20 using Microsoft.Build.Framework;
21 using Microsoft.Build.Utilities;
22
23 namespace Microsoft.Build.Tasks
24 {
25         /// <summary>
26         /// A task that returns values as specified by XPath Query
27         /// from an XML file.
28         /// </summary>
29         public class XmlPeek : TaskExtension
30         {
31                 #region Members
32
33                 /// <summary>
34                 /// The XML input as a file path.
35                 /// </summary>
36                 private ITaskItem _xmlInputPath;
37
38                 /// <summary>
39                 /// The XML input as a string.
40                 /// </summary>
41                 private string _xmlContent;
42
43                 /// <summary>
44                 /// The XPath Query.
45                 /// </summary>
46                 private string _query;
47
48                 /// <summary>
49                 /// The results that this task will return.
50                 /// </summary>
51                 private ITaskItem[] _result;
52
53                 /// <summary>
54                 /// The namespaces for XPath query's prefixes.
55                 /// </summary>
56                 private string _namespaces;
57
58                 #endregion
59
60                 #region Properties
61
62                 /// <summary>
63                 /// The XML input as a file path.
64                 /// </summary>
65                 public ITaskItem XmlInputPath {
66                         get {
67                                 return _xmlInputPath;
68                         }
69
70                         set {
71                                 _xmlInputPath = value;
72                         }
73                 }
74
75                 /// <summary>
76                 /// The XML input as a string.
77                 /// </summary>
78                 public string XmlContent {
79                         get {
80                                 return _xmlContent;
81                         }
82
83                         set {
84                                 _xmlContent = value;
85                         }
86                 }
87
88                 /// <summary>
89                 /// The XPath Query.
90                 /// </summary>
91                 public string Query {
92                         get {
93                                 if (_query == null)
94                                         throw new ArgumentNullException ("Query");
95                                 return _query;
96                         }
97
98                         set {
99                                 _query = value;
100                         }
101                 }
102
103                 /// <summary>
104                 /// The results returned by this task.
105                 /// </summary>
106                 [Output]
107                 public ITaskItem[] Result {
108                         get {
109                                 return _result;
110                         }
111                 }
112
113                 /// <summary>
114                 /// The namespaces for XPath query's prefixes.
115                 /// </summary>
116                 public string Namespaces {
117                         get {
118                                 return _namespaces;
119                         }
120
121                         set {
122                                 _namespaces = value;
123                         }
124                 }
125
126                 #endregion
127
128
129                 internal static bool IsCriticalException (Exception e)
130                 {
131                         if (e is StackOverflowException
132                             || e is OutOfMemoryException
133                             || e is AccessViolationException) {
134                                 return true;
135                         }
136                         return false;
137                 }
138
139                 /// <summary>
140                 /// Executes the XMLPeek task.
141                 /// </summary>
142                 /// <returns>true if transformation succeeds.</returns>
143                 public override bool Execute ()
144                 {
145                         XmlInput xmlinput;
146                         if (_query == null)
147                                 throw new ArgumentNullException ("Query");
148
149                         try {
150                                 xmlinput = new XmlInput (_xmlInputPath, _xmlContent);
151                         } catch (Exception e) {
152                                 if (IsCriticalException (e)) {
153                                         throw;
154                                 }
155
156                                 Log.LogErrorWithCodeFromResources ("XmlPeek.ArgumentError", e.Message);
157                                 return false;
158                         }
159
160                         XPathDocument xpathdoc;
161                         try {
162                                 // Load the XPath Document
163                                 using (XmlReader xr = xmlinput.CreateReader ()) {
164                                         xpathdoc = new XPathDocument (xr);
165                                         xr.Close ();
166                                 }
167                         } catch (Exception e) {
168                                 if (IsCriticalException (e)) {
169                                         throw;
170                                 }
171
172                                 Log.LogErrorWithCodeFromResources ("XmlPeekPoke.InputFileError", _xmlInputPath.ItemSpec, e.Message);
173                                 return false;
174                         } finally {
175                                 xmlinput.CloseReader ();
176                         }
177
178                         XPathNavigator nav = xpathdoc.CreateNavigator ();
179                         XPathExpression expr = null;
180                         try {
181                                 // Create the expression from query
182                                 expr = nav.Compile (_query);
183                         } catch (Exception e) {
184                                 if (IsCriticalException (e)) {
185                                         throw;
186                                 }
187
188                                 Log.LogErrorWithCodeFromResources ("XmlPeekPoke.XPathError", _query, e.Message);
189                                 return false;
190                         }
191
192                         // Create the namespace manager and parse the input.
193                         XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager (nav.NameTable);
194
195                         try {
196                                 LoadNamespaces (ref xmlNamespaceManager, _namespaces);
197                         } catch (Exception e) {
198                                 if (IsCriticalException (e)) {
199                                         throw;
200                                 }
201
202                                 Log.LogErrorWithCodeFromResources ("XmlPeek.NamespacesError", e.Message);
203                                 return false;
204                         }
205
206                         try {
207                                 expr.SetContext (xmlNamespaceManager);
208                         } catch (XPathException e) {
209                                 Log.LogErrorWithCodeFromResources ("XmlPeek.XPathContextError", e.Message);
210                                 return false;
211                         }
212
213                         XPathNodeIterator iter = nav.Select (expr);
214
215                         List<string> peekValues = new List<string> ();
216                         while (iter.MoveNext ()) {
217                                 if (iter.Current.NodeType == XPathNodeType.Attribute
218                                                 || iter.Current.NodeType == XPathNodeType.Text) {
219                                         peekValues.Add (iter.Current.Value);
220                                 } else {
221                                         peekValues.Add (iter.Current.OuterXml);
222                                 }
223                         }
224
225                         _result = new ITaskItem[peekValues.Count];
226                         int i = 0;
227                         foreach (string item in peekValues) {
228                                 _result [i++] = new TaskItem (item);
229
230                                 // This can be logged a lot, so low importance
231                                 Log.LogMessageFromResources (MessageImportance.Low, "XmlPeek.Found", item);
232                         }
233
234                         if (_result.Length == 0) {
235                                 // Logged no more than once per execute of this task
236                                 Log.LogMessageFromResources ("XmlPeek.NotFound");
237                         }
238
239                         return true;
240                 }
241
242                 /// <summary>
243                 /// Loads the namespaces specified at Namespaces parameter to XmlNSManager.
244                 /// </summary>
245                 /// <param name="namespaceManager">The namespace manager to load namespaces to.</param>
246                 /// <param name="namepaces">The namespaces as XML snippet.</param>
247                 private void LoadNamespaces (ref XmlNamespaceManager namespaceManager, string namepaces)
248                 {
249                         XmlDocument doc = new XmlDocument ();
250                         try {
251                                 XmlReaderSettings settings = new XmlReaderSettings ();
252                                 settings.DtdProcessing = DtdProcessing.Ignore;
253
254                                 using (XmlReader reader = XmlReader.Create (new StringReader ("<Namespaces>" + namepaces + "</Namespaces>"), settings)) {
255                                         doc.Load (reader);
256                                 }
257                         } catch (XmlException xe) {
258                                 throw new ArgumentException ("The specified Namespaces attribute is not a well-formed XML fragment.", xe);
259                         }
260
261                         XmlNodeList xnl = doc.SelectNodes ("/Namespaces/*[local-name() = 'Namespace']");
262                         for (int i = 0; i < xnl.Count; i++) {
263                                 XmlNode xn = xnl [i];
264
265                                 if (xn.Attributes ["Prefix"] == null) {
266                                         throw new ArgumentException (string.Format ("The specified Namespaces attribute doesn't have attribute \"{0}\".", "Name"));
267                                 }
268
269                                 if (xn.Attributes ["Uri"] == null) {
270                                         throw new ArgumentException (string.Format ("The specified Namespaces attribute doesn't have attribute \"{0}\".", "Uri"));
271                                 }
272
273                                 namespaceManager.AddNamespace (xn.Attributes ["Prefix"].Value, xn.Attributes ["Uri"].Value);
274                         }
275                 }
276
277                 /// <summary>
278                 /// This class prepares XML input from XMLInputPath and XMLContent parameters
279                 /// </summary>
280                 internal class XmlInput
281                 {
282                         /// <summary>
283                         /// What XML input type are we at.
284                         /// </summary>
285                         private XmlModes _xmlMode;
286
287                         /// <summary>
288                         /// This either contains the raw Xml or the path to Xml file.
289                         /// </summary>
290                         private string _data;
291
292                         /// <summary>
293                         /// Filestream used to read XML.
294                         /// </summary>
295                         private FileStream _fs;
296
297                         /// <summary>
298                         /// Constructor.
299                         /// Only one parameter should be non null or will throw ArgumentException.
300                         /// </summary>
301                         /// <param name="xmlInputPath">The path to XML file or null.</param>
302                         /// <param name="xmlContent">The raw XML.</param>
303                         public XmlInput (ITaskItem xmlInputPath, string xmlContent)
304                         {
305                                 if (xmlInputPath != null && xmlContent != null) {
306                                         throw new ArgumentException ("Only one of XmlContent or XmlInputPaths arguments can be set.");
307                                 } else if (xmlInputPath == null && xmlContent == null) {
308                                         throw new ArgumentException ("One of XmlContent or XmlInputPaths arguments must be set.");
309                                 }
310
311                                 if (xmlInputPath != null) {
312                                         _xmlMode = XmlModes.XmlFile;
313                                         _data = xmlInputPath.ItemSpec;
314                                 } else {
315                                         _xmlMode = XmlModes.Xml;
316                                         _data = xmlContent;
317                                 }
318                         }
319
320                         /// <summary>
321                         /// Possible accepted types of XML input.
322                         /// </summary>
323                         public enum XmlModes
324                         {
325                                 /// <summary>
326                                 /// If the mode is a XML file.
327                                 /// </summary>
328                                 XmlFile,
329
330                                 /// <summary>
331                                 /// If the mode is a raw XML.
332                                 /// </summary>
333                                 Xml
334                         }
335
336                         /// <summary>
337                         /// Returns the current mode of the XmlInput
338                         /// </summary>
339                         public XmlModes XmlMode {
340                                 get {
341                                         return _xmlMode;
342                                 }
343                         }
344
345                         /// <summary>
346                         /// Creates correct reader based on the input type.
347                         /// </summary>
348                         /// <returns>The XmlReader object</returns>
349                         public XmlReader CreateReader ()
350                         {
351                                 if (_xmlMode == XmlModes.XmlFile) {
352                                         _fs = new FileStream (_data, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
353                                         return XmlReader.Create (_fs);
354                                 } else { // xmlModes.Xml 
355                                         return XmlReader.Create (new StringReader (_data));
356                                 }
357                         }
358
359                         /// <summary>
360                         /// Closes the reader.
361                         /// </summary>
362                         public void CloseReader ()
363                         {
364                                 if (_fs != null) {
365                                         _fs.Close ();
366                                         _fs = null;
367                                 }
368                         }
369                 }
370         }
371 }