Skip to content

Commit

Permalink
Initial validation attribute support (#590)
Browse files Browse the repository at this point in the history
This change removes the need for some of the data files by moving the validation info to be expressed as attributes on properties/classes.
  • Loading branch information
twsouthwick authored and tomjebo committed May 31, 2019
1 parent d5d41e4 commit 98df2f9
Show file tree
Hide file tree
Showing 116 changed files with 14,376 additions and 34,800 deletions.
12 changes: 12 additions & 0 deletions src/DocumentFormat.OpenXml/Framework/ElementPropertyAccessor{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ internal class ElementPropertyAccessor<T>
private readonly Lazy<Action<OpenXmlElement, T>> _setter;
private readonly Lazy<Func<T>> _activator;

public ElementPropertyAccessor(Func<OpenXmlElement, T> getter, Action<OpenXmlElement, T> setter, Type type)
{
Type = type;

_getter = new Lazy<Func<OpenXmlElement, T>>(() => getter, true);
_setter = new Lazy<Action<OpenXmlElement, T>>(() => setter, true);
}

public ElementPropertyAccessor(Func<Type, Func<T>> activatorFactory, PropertyInfo property)
{
Type = property.PropertyType;

_getter = new Lazy<Func<OpenXmlElement, T>>(() => CreateGetter(property), true);
_setter = new Lazy<Action<OpenXmlElement, T>>(() => CreateSetter(property), true);
_activator = new Lazy<Func<T>>(() => activatorFactory(property.PropertyType), true);
Expand All @@ -26,6 +36,8 @@ public ElementPropertyAccessor(Func<Type, Func<T>> activatorFactory, PropertyInf

public T Create() => _activator.Value();

public Type Type { get; }

private static Func<OpenXmlElement, T> CreateGetter(PropertyInfo property)
{
#if NETSTANDARD1_3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public static ReadOnlyArray<ElementProperty<T>> GetProperties(Func<Type, Func<T>
schema.NamespaceId,
schema.Tag,
schema.Index,
new ValidatorCollection(property),
new ElementPropertyAccessor<T>(activator, property));
})
.Where(tag => tag.IsValid)
Expand Down
9 changes: 9 additions & 0 deletions src/DocumentFormat.OpenXml/Framework/ElementProperty{T}.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Xml;

namespace DocumentFormat.OpenXml.Framework
Expand All @@ -13,15 +14,19 @@ internal ElementProperty(
byte namespaceId,
string name,
int order,
ValidatorCollection validators,
ElementPropertyAccessor<T> accessor)
{
_accessor = accessor;

Order = order;
Name = name;
NamespaceId = namespaceId;
Validators = validators;
}

public XmlQualifiedName TypeName => Validators.GetSimpleTypeQualifiedName(Type);

public bool IsValid => Name != null;

public int Order { get; }
Expand All @@ -30,6 +35,8 @@ internal ElementProperty(

public byte NamespaceId { get; }

public ValidatorCollection Validators { get; }

public string Namespace => NamespaceIdMap.GetNamespaceUri(NamespaceId);

public string NamespacePrefix => NamespaceIdMap.GetNamespacePrefix(NamespaceId);
Expand All @@ -40,6 +47,8 @@ internal ElementProperty(

public T CreateNew() => _accessor.Create();

public Type Type => _accessor?.Type;

public XmlQualifiedName GetQName() => new XmlQualifiedName(Name, Namespace);
}
}
7 changes: 7 additions & 0 deletions src/DocumentFormat.OpenXml/Framework/ElementTypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Reflection;
using System.Xml;

namespace DocumentFormat.OpenXml.Framework
{
Expand All @@ -14,13 +16,18 @@ public ElementTypeInfo(Type type)
PartContentType = type.GetTypeInfo().GetCustomAttribute<ContentTypeAttribute>()?.ContentType;
Availability = type.GetTypeInfo().GetCustomAttribute<OfficeAvailabilityAttribute>()?.OfficeVersion ?? FileFormatVersions.Office2007;
RelationshipType = type.GetTypeInfo().GetCustomAttribute<RelationshipTypeAttribute>()?.RelationshipType ?? string.Empty;
Validators = new ValidatorCollection(type);
Schema = type.GetTypeInfo().GetCustomAttribute<SchemaAttrAttribute>();
}

public XmlQualifiedName TypeName => Validators.GetSimpleTypeQualifiedName();

public static ElementTypeInfo Create(Type type) => new ElementTypeInfo(type);

public string RelationshipType { get; }

public ValidatorCollection Validators { get; }

public string PartClassName { get; }

public string PartContentType { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Framework;
using DocumentFormat.OpenXml.Validation;
using System;

namespace DocumentFormat.OpenXml
Expand All @@ -9,7 +11,7 @@ namespace DocumentFormat.OpenXml
/// Defines an OfficeAvailabilityAttribute class to indicate whether the property is available in a specific version of an Office application.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Field)]
public sealed class OfficeAvailabilityAttribute : Attribute
public sealed class OfficeAvailabilityAttribute : Attribute, IOpenXmlSimpleTypeValidator
{
/// <summary>
/// Gets the Office version of the available property.
Expand All @@ -25,5 +27,16 @@ public OfficeAvailabilityAttribute(FileFormatVersions officeVersion)
{
OfficeVersion = officeVersion;
}

void IOpenXmlSimpleTypeValidator.Validate(ValidatorContext context)
{
if (!context.Version.AtLeast(OfficeVersion) && context.Value?.HasValue == true && !context.McContext.IsIgnorableNs(context.QName.Namespace))
{
context.CreateError(
id: "Sch_UndeclaredAttribute",
description: SR.Format(ValidationResources.Sch_UndeclaredAttribute, context.QName),
errorType: ValidationErrorType.Schema);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Validation;
using System;

namespace DocumentFormat.OpenXml.Framework
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
internal sealed class EnumValidatorAttribute : VersionedValidatorAttribute
{
protected override void ValidateVersion(ValidatorContext context)
{
var value = GetValue(context);

if (value != null && !value.IsValid)
{
var errorMessageResourceId = context.IsAttribute ? "Sch_AttributeValueDataTypeDetailed" : "Sch_ElementValueDataTypeDetailed";
var message = context.IsAttribute ? ValidationResources.Sch_AttributeValueDataTypeDetailed : ValidationResources.Sch_ElementValueDataTypeDetailed;

if (!value.IsEnum && string.IsNullOrEmpty(value.InnerText))
{
context.CreateError(
id: errorMessageResourceId,
description: SR.Format(message, context.QName, context.Value.InnerText, ValidationResources.Sch_EmptyAttributeValue),
errorType: ValidationErrorType.Schema);
}
else
{
context.CreateError(
id: errorMessageResourceId,
description: SR.Format(message, context.QName, value, ValidationResources.Sch_EnumerationConstraintFailed),
errorType: ValidationErrorType.Schema);
}
}
}
}
}
12 changes: 12 additions & 0 deletions src/DocumentFormat.OpenXml/Framework/Validation/INameProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Xml;

namespace DocumentFormat.OpenXml.Framework
{
internal interface INameProvider
{
XmlQualifiedName QName { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace DocumentFormat.OpenXml.Framework
{
internal interface IOpenXmlSimpleTypeValidator
{
void Validate(ValidatorContext context);
}
}
10 changes: 10 additions & 0 deletions src/DocumentFormat.OpenXml/Framework/Validation/IUnionValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace DocumentFormat.OpenXml.Framework
{
internal interface IUnionValidator : IOpenXmlSimpleTypeValidator
{
byte? UnionId { get; }
}
}
47 changes: 47 additions & 0 deletions src/DocumentFormat.OpenXml/Framework/Validation/ListValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Validation;

namespace DocumentFormat.OpenXml.Framework
{
internal class ListValidator : IOpenXmlSimpleTypeValidator
{
public static IOpenXmlSimpleTypeValidator Instance { get; } = new ListValidator();

private ListValidator()
{
}

public void Validate(ValidatorContext context)
{
var value = context.Value;

if (value is null)
{
return;
}

if (!value.IsValid)
{
var id = context.IsAttribute ? "Sch_AttributeValueDataTypeDetailed" : "Sch_ElementValueDataTypeDetailed";
var description = context.IsAttribute ? ValidationResources.Sch_AttributeValueDataTypeDetailed : ValidationResources.Sch_ElementValueDataTypeDetailed;

if (string.IsNullOrEmpty(value.InnerText))
{
context.CreateError(
id: id,
description: SR.Format(description, context.QName, context.Value.InnerText, context.IsAttribute ? ValidationResources.Sch_EmptyAttributeValue : ValidationResources.Sch_EmptyElementValue),
errorType: ValidationErrorType.Schema);
}
else
{
context.CreateError(
id: id,
description: SR.Format(description, context.QName, context.Value.InnerText, string.Empty),
errorType: ValidationErrorType.Schema);
}
}
}
}
}
Loading

0 comments on commit 98df2f9

Please sign in to comment.