Skip to content
Permalink
Browse files

Added support for automatically loading industry-standard schemas ref…

…erenced by namespaces used by facts but not explicitly loaded by a <schemaRef> element. Fixes #1.
  • Loading branch information...
Jeff Ferguson
Jeff Ferguson committed Oct 5, 2017
1 parent 28183ac commit f7f77989ba6a187b6f5018baba3dfe7e9fca8d65
@@ -0,0 +1,16 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace JeffFerguson.Gepsio.Test.IssueTests._1
{
[TestClass]
public class Issue1Test
{
[TestMethod]
public void VerifyFixForIssue1()
{
var xbrlDoc = new XbrlDocument();
xbrlDoc.Load("http://xbrlsite.com/US-GAAP/BasicExample/2010-09-30/abc-20101231.xml");
Assert.IsTrue(xbrlDoc.IsValid);
}
}
}
@@ -288,4 +288,10 @@
<value>Fact {0} is based on an element that is named as the summation concept in a calculation link. One of the contributing item concepts to the summation concept specifies a precision of zero. For items in which zero precision is specified, nothing is known about the precision of the number, nothing can be inferred about decimals, and any calculation link summation involving the item must be inconsistent.</value>
<comment>XBRL spec section 4.6.6: "If the value of the @precision attribute of a Numeric Item is equal to 0 , nothing is known about the precision of the number, nothing can be inferred about decimals, and thus any consuming V-Equals comparison must be false, and any calculation link summation involving the item must be inconsistent."</comment>
</data>
<data name="NoSchemasForNamespace" xml:space="preserve">
<value>Fact {0} references a namespace with a URI of {1}, but no schema can be found for that namespace. Schema element information is not available for this fact. If this URI references an industry standard namespace, contact the project administrators to get support for this industry standard in the project.</value>
</data>
<data name="NoSchemaForElementItemType" xml:space="preserve">
<value>None of the schemas referenced by the XBRL fragment contain a definition for item type {0} on element {1}.</value>
</data>
</root>
@@ -153,7 +153,7 @@ private void SetPrecision()
// in the schema definition for the type. Check there if the type is a complex
// type.

if(this.Type.IsComplex == true)
if(this.Type?.IsComplex == true)
{
var precisionAttribute = this.Type.GetAttribute("precision");
if (precisionAttribute != null)
@@ -190,7 +190,7 @@ private void SetDecimals()
// in the schema definition for the type. Check there if the type is a complex
// type.

if (this.Type.IsComplex == true)
if (this.Type?.IsComplex == true)
{
var decimalsAttribute = this.Type.GetAttribute("decimals");
if (decimalsAttribute != null)
@@ -232,7 +232,7 @@ private void InferPrecision()
//------------------------------------------------------------------------------------
private void GetSchemaElementFromSchema()
{
thisSchema = thisParentFragment.Schemas.GetSchemaFromTargetNamespace(this.Namespace);
thisSchema = thisParentFragment.Schemas.GetSchemaFromTargetNamespace(this.Namespace, thisParentFragment);
if (thisSchema == null)
{
if (thisParentFragment.Schemas.Count == 0)
@@ -242,6 +242,13 @@ private void GetSchemaElementFromSchema()
MessageFormatBuilder.AppendFormat(MessageFormat);
thisParentFragment.AddValidationError(new ItemValidationError(this, MessageFormatBuilder.ToString()));
}
else
{
string MessageFormat = AssemblyResources.GetName("NoSchemasForNamespace");
StringBuilder MessageFormatBuilder = new StringBuilder();
MessageFormatBuilder.AppendFormat(MessageFormat, this.Name, this.Namespace);
thisParentFragment.AddValidationError(new ItemValidationError(this, MessageFormatBuilder.ToString()));
}
}
this.SchemaElement = thisParentFragment.Schemas.GetElement(this.Name);
if (this.SchemaElement == null)
@@ -257,12 +264,22 @@ private void GetSchemaElementFromSchema()
//------------------------------------------------------------------------------------
private void SetItemType(IQualifiedName ItemTypeValue)
{
this.Type = thisSchema.GetXmlSchemaType(ItemTypeValue);
this.Type = null;
if(thisSchema != null)
this.Type = thisSchema.GetXmlSchemaType(ItemTypeValue);
if (this.Type == null)
{
string MessageFormat = AssemblyResources.GetName("InvalidElementItemType");
StringBuilder MessageFormatBuilder = new StringBuilder();
MessageFormatBuilder.AppendFormat(MessageFormat, thisSchema.Path, ItemTypeValue.Name, this.Name);
var MessageFormatBuilder = new StringBuilder();
if (thisSchema == null)
{
string MessageFormat = AssemblyResources.GetName("NoSchemaForElementItemType");
MessageFormatBuilder.AppendFormat(MessageFormat, ItemTypeValue.Name, this.Name);
}
else
{
string MessageFormat = AssemblyResources.GetName("InvalidElementItemType");
MessageFormatBuilder.AppendFormat(MessageFormat, thisSchema.Path, ItemTypeValue.Name, this.Name);
}
thisParentFragment.AddValidationError(new ItemValidationError(this, MessageFormatBuilder.ToString()));
}
}
@@ -349,6 +366,8 @@ private bool TypeNameContains(string TypeName)
/// </returns>
private bool TypeNameContains(string TypeName, ISchemaType CurrentType)
{
if (CurrentType == null)
return false;
if (CurrentType.Name.Contains(TypeName) == true)
return true;
if (CurrentType.IsComplex == true)
@@ -284,7 +284,7 @@ public XbrlSchema GetXbrlSchemaForPrefix(string Prefix)
return null;
if (Uri.Length == 0)
return null;
return this.Schemas.GetSchemaFromTargetNamespace(Uri);
return this.Schemas.GetSchemaFromTargetNamespace(Uri, this);
}

/// <summary>
@@ -507,7 +507,7 @@ private void ReadFacts()
//-------------------------------------------------------------------------------
private bool IsTaxonomyNamespace(string CandidateNamespace)
{
var matchingSchema = this.Schemas.GetSchemaFromTargetNamespace(CandidateNamespace);
var matchingSchema = this.Schemas.GetSchemaFromTargetNamespace(CandidateNamespace, this);
if (matchingSchema == null)
return false;
return true;
@@ -40,6 +40,36 @@ public class XbrlSchema
/// </summary>
public string TargetNamespace { get; private set; }

/// <summary>
/// An alias URI for the target namespace of the schema.
/// </summary>
/// <remarks>
/// <para>
/// Some industry-standard schemas specify target namespaces that differ from
/// their published target namespace URIs. For example, the Web page at
/// https://xbrl.us/xbrl-taxonomy/2009-us-gaap/ describes the schemas for
/// US GAAP Taxonomies, Release 2009. According to the Web site, the target
/// namespace for the schemas is http://taxonomies.xbrl.us/us-gaap/2009-01-31.
/// However, the XSD that implements the schema, which is available at
/// http://taxonomies.xbrl.us/us-gaap/2009/elts/us-gaap-std-2009-01-31.xsd,
/// specifies a target namespace of http://xbrl.us/us-gaap-std/2009-01-31. This
/// differs from the target namespace listed in the spec, which does not list
/// the "-std" suffix in the URI.
/// </para>
/// <para>
/// To combat this discrepany, Gepsio offers a "target namespace alias" which
/// may differ from the target namespace specified in the schema itself. This
/// alias allows a place for Gepsio to specify an alias target namespace which
/// matches the URI used in specification documents.
/// </para>
/// <para>
/// When a schema is loaded, the alias will be set to the target namespace
/// specified in the source schema. However, Gepsio can change this value to
/// something else once the schema is loaded.
/// </para>
/// </remarks>
public string TargetNamespaceAlias { get; internal set; }

/// <summary>
/// A collection of <see cref="Element"/> objects representing all elements defined in the schema.
/// </summary>
@@ -310,6 +340,7 @@ private void ReadSchemaNode()
return;
}
this.TargetNamespace = this.SchemaRootNode.Attributes["targetNamespace"].Value;
this.TargetNamespaceAlias = this.TargetNamespace;
foreach (IAttribute CurrentAttribute in this.SchemaRootNode.Attributes)
if (CurrentAttribute.Prefix == "xmlns")
this.NamespaceManager.AddNamespace(CurrentAttribute.LocalName, CurrentAttribute.Value);
@@ -18,6 +18,8 @@ public class XbrlSchemaCollection
{
internal List<XbrlSchema> SchemaList { get; private set; }

private Dictionary<string, string> StandardNamespaceSchemaLocationDictionary;

/// <summary>
/// The number of schemas in the collection.
/// </summary>
@@ -29,6 +31,48 @@ public int Count
internal XbrlSchemaCollection()
{
SchemaList = new List<XbrlSchema>();
BuildStandardNamespaceSchemaLocationDictionary();
}

/// <summary>
/// Builds a dictionary of standard, pre-defined namespaces and
/// corresponding schema locations.
/// </summary>
/// <remarks>
/// <para>
/// Some XBRL document instances may contain facts that reference namespaces
/// defined by external specifications and schemas. For example, the Document
/// Information and Entity Information schema defines a namespace of
/// http://xbrl.us/dei/2009-01-31. This namespace is defined by the schema at
/// http://taxonomies.xbrl.us/us-gaap/2009/non-gaap/dei-2009-01-31.xsd.
/// </para>
/// <para>
/// XBRL instances will not (generally) explictly load schemas defined by external
/// specifications with a &gt;schemaRef&lt; tag; they may, however, define facts
/// with the namespaces defined by these external specifications.
/// </para>
/// <para>
/// This method builds a dictionary of standard, well-known, externally defined
/// namespaces and corresponding schema locations so that, if Gepsio needs
/// element information from one of these schemas, it knows where to find the
/// corresponding schema.
/// </para>
/// </remarks>
private void BuildStandardNamespaceSchemaLocationDictionary()
{
StandardNamespaceSchemaLocationDictionary = new Dictionary<string, string>
{
{
// Document Information and Entity Information 2009
"http://xbrl.us/dei/2009-01-31",
"http://taxonomies.xbrl.us/us-gaap/2009/non-gaap/dei-2009-01-31.xsd"
},
{
// US-GAAP 2009
"http://xbrl.us/us-gaap/2009-01-31",
"http://taxonomies.xbrl.us/us-gaap/2009/elts/us-gaap-std-2009-01-31.xsd"
}
};
}

/// <summary>
@@ -104,11 +148,47 @@ public Element GetElement(string elementLocalName)
/// A reference to the schema matching the target namespace. A null reference will be returned
/// if no matching schema can be found.
/// </returns>
public XbrlSchema GetSchemaFromTargetNamespace(string targetNamespace)
public XbrlSchema GetSchemaFromTargetNamespace(string targetNamespace, XbrlFragment parentFragment)
{
var foundSchema = FindSchema(targetNamespace);
if(foundSchema == null)
{

// There is no loaded schema for the target namespace. The target
// namespace be an industry-standard namespace referencing a schema
// that has not been explicitly loaded. Find out if the namespace is
// a standard one, and, if so, load its corresponding schema and
// retry the search.

string schemaLocation;
StandardNamespaceSchemaLocationDictionary.TryGetValue(targetNamespace, out schemaLocation);
if (string.IsNullOrEmpty(schemaLocation) == true)
return null;
var newSchema = new XbrlSchema(parentFragment, schemaLocation, string.Empty);
newSchema.TargetNamespaceAlias = targetNamespace;
Add(newSchema);
foundSchema = FindSchema(targetNamespace);
}
return foundSchema;
}

/// <summary>
/// Gets the schema having the target namespace.
/// </summary>
/// <param name="targetNamespace">
/// The namespace whose schema should be returned.
/// </param>
/// <returns>
/// A reference to the schema matching the target namespace. A null reference will be returned
/// if no matching schema can be found.
/// </returns>
private XbrlSchema FindSchema(string targetNamespace)
{
foreach (var CurrentSchema in SchemaList)
{
if(CurrentSchema.TargetNamespace.Equals(targetNamespace) == true)
if (CurrentSchema.TargetNamespace.Equals(targetNamespace) == true)
return CurrentSchema;
if (CurrentSchema.TargetNamespaceAlias.Equals(targetNamespace) == true)
return CurrentSchema;
}
return null;

0 comments on commit f7f7798

Please sign in to comment.
You can’t perform that action at this time.