private static object GetReturnTypeDefault(XPathEvaluationReturnType expectedReturnType) { // According to https://docs.microsoft.com/en-us/dotnet/api/system.xml.xpath.xpathnavigator.evaluate?view=netframework-4.5 // XPathNavigator.Evaluate can only return one of the following data types: // bool, double, string or XPathNodeIterator. These correspond to the values of the // XPathEvaluationReturnType enum, where XPathNodeIterator can represent a Nodeset or // a Node. IDictionary <XPathEvaluationReturnType, Type> returnTypeMappings = ReturnTypeMappings; string errorMessage = null; Type expectedType = returnTypeMappings.GetValueOrNull(expectedReturnType); if (expectedType == null) { errorMessage = string.Format("Invalid return type. " + "Result of XPath evaluation cannot return type {0}.", expectedReturnType); throw new ArgumentException(errorMessage); } if (expectedType.IsValueType) { return(Activator.CreateInstance(expectedType)); } // Can return null for all reference types. return(null); }
/// <summary> /// Applies the XPath expression to the XML content and returns the result. /// </summary> /// <param name="xpathExpression">The XPath expression to apply.</param> /// <param name="content">The XML content the Xpath expression will be applied to.</param> /// <param name="returnType">The return type: Boolean, Number, String, Node or Nodeset.</param> /// <returns>The result of applying the XPath expression to the XML content. The type /// returned will depend on the specified return type: Boolean will return a /// System.Boolean, Number will return a System.Double, String will return a /// System.String, and Node or Nodeset will return an XPathNavigator object representing /// a single node. Return types Node and Nodeset are equivalent: They will each only /// return a single node. If the XPath expression matches multiple nodes then only the /// first node or its value will be returned, depending on the return type specified.</returns> /// <remarks>This method is only used to read the contents of a node, as either a string /// or a boolean, or to check the existence of a node. When multiple nodes match the /// XPath expression only the contents of the first is ever read, and only the first node /// is needed to prove existence. Hence the return type Nodeset is identical to return /// type Node, returning only a single node. /// /// The original Java implementation had a charset parameter for this method. /// However, in .NET charset or encoding does not need to be specified as the content /// being parsed is a .NET string, which does not have encoding associated with it. It /// would be different if we had to parse the contents of a stream or a file.</remarks> public static object extractXPath(string xpathExpression, string content, XPathEvaluationReturnType returnType) { // Use the java Xpath API to return a NodeList to the caller so they can // iterate through return(extractXPath(new Dictionary <string, string>(), xpathExpression, content, returnType)); }
private void ExtractNonExistentNode(string xpathExpression, XPathEvaluationReturnType returnType) { // Arrange. string xml = "<a><b>test</b><c>1</c><c>2</c></a>"; // Act. object rawValue = XmlTools.extractXPath(xpathExpression, xml, returnType); // Assert. Assert.IsNull(rawValue); }
private static object GetNodeValue(object rawResult, XPathEvaluationReturnType returnType) { if (rawResult == null) { return(null); } XPathNodeIterator iterator = rawResult as XPathNodeIterator; if (iterator == null) { return(rawResult); } // If there is a match the Count will be a positive integer, if there is no match // the Count will be 0. Can't depend on the node the iterator moves to as if // there is no match it will just move to the root node. So we have to check the // Count to determine if there is a match or not. if (iterator.Count == 0) { return(null); } iterator.MoveNext(); XPathNavigator node = iterator.Current; if (node == null) { return(rawResult); } switch (returnType) { case XPathEvaluationReturnType.Boolean: return(node.ValueAsBoolean); case XPathEvaluationReturnType.Number: return(node.ValueAsDouble); case XPathEvaluationReturnType.String: return(node.Value); case XPathEvaluationReturnType.Node: case XPathEvaluationReturnType.Nodeset: return(node); } return(rawResult); }
private void ExtractValueFromXml <T>(string xpathExpression, XPathEvaluationReturnType returnType, T expectedValue) { // Arrange. string xml = "<a><b>test</b><c>1</c><c>2</c></a>"; // Act. object rawValue = XmlTools.extractXPath(xpathExpression, xml, returnType); object actualValue = default(T); if (typeof(T) == typeof(string)) { actualValue = rawValue.ToString(); } else { actualValue = (T)rawValue; } // Assert. Assert.AreEqual(expectedValue, actualValue); }
private static void CheckExtractionReturnType(object returnValue, XPathEvaluationReturnType expectedReturnType) { // According to https://docs.microsoft.com/en-us/dotnet/api/system.xml.xpath.xpathnavigator.evaluate?view=netframework-4.5 // XPathNavigator.Evaluate can only return one of the following data types: // bool, double, string or XPathNodeIterator. These correspond to the values of the // XPathEvaluationReturnType enum, where XPathNodeIterator can represent a Nodeset or // a Node. IDictionary <XPathEvaluationReturnType, Type> returnTypeMappings = ReturnTypeMappings; string errorMessage = null; Type expectedType = returnTypeMappings.GetValueOrNull(expectedReturnType); if (expectedType == null) { errorMessage = string.Format("Invalid return type. " + "Result of XPath evaluation cannot return type {0}.", expectedReturnType); throw new ArgumentException(errorMessage); } Type actualType = returnValue.GetType(); // Actual type could be derived from the expected type, for example the actual type // could be XPathSelectionIterator which is derived from the expected type // XPathNodeIterator. So use IsAssignableFrom instead of equality comparison. // Assume any return type could be converted to a string. if (!expectedType.IsAssignableFrom(actualType) && expectedType != typeof(string)) { errorMessage = string.Format("XPath expression return type {0} is not compatible " + "with the specified return type {1}.", actualType.FullName, expectedReturnType); throw new XPathException(errorMessage); } }
private void ExtractNonExistentTextNode(XPathEvaluationReturnType returnType) { ExtractNonExistentNode("/a/nonExistentNode/text()", returnType); }
/// <summary> /// Applies the XPath expression to the XML content and returns the result. /// </summary> /// <param name="ns">The namespaces map, which lists the namespaces in the XML and their /// aliases.</param> /// <param name="xpathExpression">The XPath expression to apply.</param> /// <param name="content">The XML content the Xpath expression will be applied to.</param> /// <param name="returnType">The return type: Boolean, Number, String, Node or Nodeset.</param> /// <returns>The result of applying the XPath expression to the XML content. The type /// returned will depend on the specified return type: Boolean will return a /// System.Boolean, Number will return a System.Double, String will return a /// System.String, and Node or Nodeset will return an XPathNavigator object representing /// a single node. Return types Node and Nodeset are equivalent: They will each only /// return a single node. If the XPath expression matches multiple nodes then only the /// first node or its value will be returned, depending on the return type specified.</returns> /// <remarks>This method is only used to read the contents of a node, as either a string /// or a boolean, or to check the existence of a node. When multiple nodes match the /// XPath expression only the contents of the first is ever read, and only the first node /// is needed to prove existence. Hence the return type Nodeset is identical to return /// type Node, returning only a single node. /// /// The original Java implementation had a charset parameter for this method. /// However, in .NET charset or encoding does not need to be specified as the content /// being parsed is a .NET string, which does not have encoding associated with it. It /// would be different if we had to parse the contents of a stream or a file.</remarks> public static object extractXPath(IDictionary <string, string> ns, string xpathExpression, string content, XPathEvaluationReturnType returnType) { if (string.IsNullOrWhiteSpace(xpathExpression)) { throw new ArgumentException("XPath expression is null"); } if (string.IsNullOrWhiteSpace(content)) { throw new ArgumentException("XML is null"); } XPathDocument document = null; XmlNamespaceManager namespaceManager = null; using (StringReader sr = new StringReader(content)) using (XmlReader xr = XmlReader.Create(sr)) { try { document = new XPathDocument(xr); } catch (Exception ex) { throw new ArgumentException("XML is ill-formed", ex); } // Handling namespaces: // XPathNavigator.Evaluate(xpath) and XPathNavigator.SelectSingleNode(xpath) both // compile the xpath expression into an XPathExpression object, using // XPathExpression.Compile(string xpath, IXmlNamespaceResolver nsResolver). They // pass null in as the IXmlNamespaceResolver. This is equivalent to calling // XPathNavigator.Evaluate(string xpath, IXmlNamespaceResolver resolver) or // XPathNavigator.SelectSingleNode(string xpath, IXmlNamespaceResolver resolver), // respectively, with the resolver set to null. So we'll just pass null in as // the resolver if we have no namespaces. namespaceManager = GetNamespaceManager(ns, xr.NameTable); } object result = null; XPathNavigator navigator = document.CreateNavigator(); try { result = navigator.Evaluate(xpathExpression, namespaceManager); result = GetNodeValue(result, returnType); if (result == null) { return(null); } return(result); } catch (ArgumentException ex) { throw new ArgumentException( "xPath expression would return a node set: " + xpathExpression, ex); } catch (XPathException) { throw new ArgumentException( "xPath expression is not valid: " + xpathExpression); } catch (Exception ex) { throw new ArgumentException( "Error parsing XML with xPath expression " + xpathExpression, ex); } }