// Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml;
namespace ICSharpCode.NRefactory.Xml
{
///
/// XML element.
///
public class AXmlElement : AXmlObject, IXmlNamespaceResolver
{
internal AXmlElement(AXmlObject parent, int startOffset, InternalElement internalObject)
: base(parent, startOffset, internalObject)
{
Log.Assert(internalObject.NestedObjects[0] is InternalTag, "First child of element must be start tag");
}
/// No tags are missing anywhere within this element (recursive)
public bool IsProperlyNested {
get { return ((InternalElement)internalObject).IsPropertyNested; }
}
/// The start or empty-element tag for this element.
public AXmlTag StartTag {
get { return (AXmlTag)this.Children[0]; }
}
/// Name with namespace prefix - exactly as in source
public string Name {
get { return ((InternalTag)internalObject.NestedObjects[0]).Name; }
}
/// Gets whether an end tag exists for this node.
public bool HasEndTag {
get { return ((InternalElement)internalObject).HasEndTag; }
}
/// The end tag, if there is any. Returns null for empty elements "<Element/>" and missing end tags in malformed XML.
public AXmlTag EndTag {
get {
if (HasEndTag)
return (AXmlTag)this.Children[this.Children.Count - 1];
else
return null;
}
}
///
/// Gets the attributes.
///
public IEnumerable Attributes {
get {
return ((AXmlTag)this.Children[0]).Children.OfType();
}
}
///
/// Gets the content (all children except for the start and end tags)
///
public IEnumerable Content {
get {
int end = this.Children.Count;
if (HasEndTag)
end--;
for (int i = 1; i < end; i++) {
yield return this.Children[i];
}
}
}
/// The part of name before ":"
/// Empty string if not found
public string Prefix {
get { return ((InternalElement)internalObject).Prefix; }
}
/// The part of name after ":"
/// Empty string if not found
public string LocalName {
get { return ((InternalElement)internalObject).LocalName; }
}
/// Resolved namespace of the name
/// Empty string if prefix is not found
public string Namespace {
get {
string prefix = this.Prefix;
return LookupNamespace(prefix);
}
}
/// Find the default namespace for this context
[Obsolete("Use LookupNamespace(string.Empty) instead")]
public string FindDefaultNamespace()
{
return LookupNamespace(string.Empty) ?? NoNamespace;
}
///
/// Recursively resolve given prefix in this context. Prefix must have some value.
///
/// Empty string if prefix is not found
[Obsolete("Use LookupNamespace() instead")]
public string ResolvePrefix(string prefix)
{
return LookupNamespace(prefix) ?? NoNamespace;
}
///
/// Recursively resolve given prefix in this context.
///
/// null if prefix is not found
public string LookupNamespace(string prefix)
{
if (prefix == null)
throw new ArgumentNullException("prefix");
// Implicit namespaces
if (prefix == "xml") return XmlNamespace;
if (prefix == "xmlns") return XmlnsNamespace;
string lookFor = (prefix.Length > 0 ? "xmlns:" + prefix : "xmlns");
for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
foreach (var attr in current.Attributes) {
if (attr.Name == lookFor)
return attr.Value;
}
}
return null; // Can not find prefix
}
///
/// Gets the prefix that is mapped to the specified namespace URI.
///
/// The prefix that is mapped to the namespace URI; null if the namespace URI is not mapped to a prefix.
public string LookupPrefix(string namespaceName)
{
if (namespaceName == null)
throw new ArgumentNullException("namespaceName");
if (namespaceName == XmlNamespace)
return "xml";
if (namespaceName == XmlnsNamespace)
return "xmlns";
for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
foreach (var attr in current.Attributes) {
if (attr.Value == namespaceName) {
if (attr.Name.StartsWith("xmlns:", StringComparison.Ordinal))
return attr.LocalName;
else if (attr.Name == "xmlns")
return string.Empty;
}
}
}
return null; // Can not find prefix
}
///
/// Gets a collection of defined prefix-namespace mappings that are currently in scope.
///
public IDictionary GetNamespacesInScope(XmlNamespaceScope scope)
{
var result = new Dictionary();
if (scope == XmlNamespaceScope.All) {
result["xml"] = XmlNamespace;
//result["xmlns"] = XmlnsNamespace; xmlns should not be included in GetNamespacesInScope() results
}
for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
foreach (var attr in current.Attributes) {
if (attr.Name.StartsWith("xmlns:", StringComparison.Ordinal)) {
string prefix = attr.LocalName;
if (!result.ContainsKey(prefix)) {
result.Add(prefix, attr.Value);
}
} else if (attr.Name == "xmlns" && !result.ContainsKey(string.Empty)) {
result.Add(string.Empty, attr.Value);
}
}
if (scope == XmlNamespaceScope.Local)
break;
}
return result;
}
///
/// Get unquoted value of attribute.
/// It looks in the no namespace (empty string).
///
/// Null if not found
public string GetAttributeValue(string localName)
{
return GetAttributeValue(string.Empty, localName);
}
///
/// Get unquoted value of attribute
///
/// Namespace. Can be no namepace (empty string), which is the default for attributes.
/// Local name - text after ":"
/// Null if not found
public string GetAttributeValue(string @namespace, string localName)
{
@namespace = @namespace ?? string.Empty;
foreach (AXmlAttribute attr in this.Attributes) {
if (attr.LocalName == localName && attr.Namespace == @namespace)
return attr.Value;
}
return null;
}
///
public override void AcceptVisitor(AXmlVisitor visitor)
{
visitor.VisitElement(this);
}
///
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}' Attr:{2} Chld:{3} Nest:{4}]", base.ToString(), this.Name, this.StartTag.Children.Count, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad");
}
}
}