[Xaml[C]] Accept prefixed property names of markup extensions (#5186)

This commit is contained in:
Akihiko Odaki 2019-12-19 17:25:40 +09:00 коммит произвёл Stephane Delcroix
Родитель 6c3b4f57fc
Коммит a1a9f6e1be
8 изменённых файлов: 215 добавлений и 144 удалений

Просмотреть файл

@ -127,27 +127,68 @@ namespace Xamarin.Forms.Build.Tasks
if (split.Length > 2)
throw new ArgumentException();
string prefix, name;
if (split.Length == 2) {
prefix = split[0];
name = split[1];
}
else {
prefix = "";
name = split[0];
}
var (prefix, name) = ParseName(match);
var namespaceuri = nsResolver.LookupNamespace(prefix) ?? "";
if (!string.IsNullOrEmpty(prefix) && string.IsNullOrEmpty(namespaceuri))
throw new XamlParseException($"Undeclared xmlns prefix '{prefix}'", xmlLineInfo);
IList<XmlType> typeArguments = null;
var childnodes = new List<(XmlName, INode)>();
var contentname = new XmlName(null, null);
if (remaining.StartsWith("}", StringComparison.Ordinal))
{
remaining = remaining.Substring(1);
}
else
{
char next;
string piece;
while ((piece = GetNextPiece(serviceProvider, ref remaining, out next)) != null)
{
var parsed = ParseProperty(piece, serviceProvider, ref remaining, next != '=');
XmlName childname;
if (parsed.name == null)
{
childname = contentname;
}
else
{
var (propertyPrefix, propertyName) = ParseName(parsed.name);
childname = XamlParser.ParsePropertyName(new XmlName(
propertyPrefix == "" ? "" : nsResolver.LookupNamespace(propertyPrefix),
propertyName));
if (childname.NamespaceURI == null && childname.LocalName == null)
continue;
}
if (childname == XmlName.xTypeArguments)
{
typeArguments = TypeArgumentsParser.ParseExpression(parsed.strValue, nsResolver, xmlLineInfo);
childnodes.Add((childname, new ValueNode(typeArguments, nsResolver)));
}
else
{
var childnode = parsed.value as INode ?? new ValueNode(parsed.strValue, nsResolver);
childnodes.Add((childname, childnode));
}
}
}
//The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix.
XmlType type;
try {
type = new XmlType(namespaceuri, name + "Extension", null);
try
{
type = new XmlType(namespaceuri, name + "Extension", typeArguments);
type.GetTypeReference(contextProvider.Context.Module, null);
}
catch (XamlParseException) {
type = new XmlType(namespaceuri, name, null);
catch (XamlParseException)
{
type = new XmlType(namespaceuri, name, typeArguments);
}
if (type == null)
@ -157,30 +198,18 @@ namespace Xamarin.Forms.Build.Tasks
? new ElementNode(type, "", nsResolver)
: new ElementNode(type, "", nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition);
if (remaining.StartsWith("}", StringComparison.Ordinal)) {
remaining = remaining.Substring(1);
return _node;
foreach (var (childname, childnode) in childnodes) {
if (childname == contentname) {
//ContentProperty
_node.CollectionItems.Add(childnode);
}
else {
_node.Properties[childname] = childnode;
}
}
string piece;
while ((piece = GetNextPiece(serviceProvider, ref remaining, out var next)) != null)
HandleProperty(piece, serviceProvider, ref remaining, next != '=');
return _node;
}
protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
{
if (value == null && strValue == null)
throw new XamlParseException($"No value found for property '{prop}' in markup expression", serviceProvider);
var nsResolver = serviceProvider.GetService(typeof(IXmlNamespaceResolver)) as IXmlNamespaceResolver;
if (prop != null) {
var name = new XmlName(_node.NamespaceURI, prop);
_node.Properties[name] = value as INode ?? new ValueNode(strValue, nsResolver);
}
else //ContentProperty
_node.CollectionItems.Add(value as INode ?? new ValueNode(strValue, nsResolver));
}
}
}
}

Просмотреть файл

@ -3,7 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
x:Class="Xamarin.Forms.Xaml.UnitTests.GenericsTests">
x:Class="Xamarin.Forms.Xaml.UnitTests.GenericsTests"
P="{scg:List x:TypeArguments=x:String}">
<ContentPage.Resources>
<ResourceDictionary>
<scg:List x:TypeArguments="Button" x:Key="genericButtonList">

Просмотреть файл

@ -9,6 +9,8 @@ namespace Xamarin.Forms.Xaml.UnitTests
{
public partial class GenericsTests : ContentPage
{
public List<string> P { get; set; }
public GenericsTests ()
{
InitializeComponent ();
@ -71,6 +73,9 @@ namespace Xamarin.Forms.Xaml.UnitTests
public void TestGenericParsing (bool useCompiledXaml)
{
var layout = new GenericsTests (useCompiledXaml);
Assert.NotNull (layout.P);
var list = layout.Resources ["list"];
Assert.NotNull (list);
Assert.That (list, Is.TypeOf<List<String>> ());

Просмотреть файл

@ -119,26 +119,59 @@ namespace Xamarin.Forms.Xaml
if (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider)
xmlLineInfo = xmlLineInfoProvider.XmlLineInfo;
var split = match.Split(':');
if (split.Length > 2)
throw new ArgumentException();
var (prefix, name) = ParseName(match);
string prefix; //, name;
if (split.Length == 2) {
prefix = split[0];
// name = split [1];
var namespaceuri = nsResolver.LookupNamespace(prefix) ?? "";
IList<XmlType> typeArguments = null;
var childnodes = new List<(XmlName, INode)>();
var contentname = new XmlName(null, null);
if (remaining.StartsWith("}", StringComparison.Ordinal)) {
remaining = remaining.Substring(1);
}
else {
prefix = "";
// name = split [0];
char next;
string piece;
while ((piece = GetNextPiece(serviceProvider, ref remaining, out next)) != null) {
var parsed = ParseProperty(piece, serviceProvider, ref remaining, next != '=');
XmlName childname;
if (parsed.name == null) {
childname = contentname;
}
else {
var (propertyPrefix, propertyName) = ParseName(parsed.name);
childname = XamlParser.ParsePropertyName(new XmlName(
propertyPrefix == "" ? null : nsResolver.LookupNamespace(propertyPrefix),
propertyName));
if (childname.NamespaceURI == null && childname.LocalName == null)
continue;
}
if (childname == XmlName.xTypeArguments) {
typeArguments = TypeArgumentsParser.ParseExpression(parsed.strValue, nsResolver, xmlLineInfo);
childnodes.Add((childname, new ValueNode(typeArguments, nsResolver)));
}
else {
var childnode = parsed.value as INode ?? new ValueNode(parsed.strValue, nsResolver);
childnodes.Add((childname, childnode));
}
}
}
Type type;
if (!(serviceProvider.GetService(typeof(IXamlTypeResolver)) is IXamlTypeResolver typeResolver))
type = null;
else {
//The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix.
if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type)) {
if (!(serviceProvider.GetService(typeof (IXamlTypeResolver)) is XamlTypeResolver typeResolver))
throw new NotSupportedException();
var xmltype = new XmlType(namespaceuri, name + "Extension", typeArguments);
//The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix.
if (!typeResolver.TryResolve(xmltype, out _)) {
xmltype = new XmlType(namespaceuri, name, typeArguments);
if (!typeResolver.TryResolve(xmltype, out _)) {
var ex = new XamlParseException($"MarkupExtension not found for {match}", serviceProvider);
if (ExceptionHandler != null) {
ExceptionHandler(ex);
@ -148,50 +181,24 @@ namespace Xamarin.Forms.Xaml
}
}
var namespaceuri = nsResolver.LookupNamespace(prefix) ?? "";
var xmltype = new XmlType(namespaceuri, type.Name, null);
if (type == null)
throw new NotSupportedException();
_node = xmlLineInfo == null
? new ElementNode(xmltype, null, nsResolver)
: new ElementNode(xmltype, null, nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition);
if (remaining.StartsWith("}", StringComparison.Ordinal)) {
remaining = remaining.Substring(1);
return _node;
}
foreach (var (childname, childnode) in childnodes) {
childnode.Parent = _node;
string piece;
while ((piece = GetNextPiece(serviceProvider, ref remaining, out var next)) != null)
HandleProperty(piece, serviceProvider, ref remaining, next != '=');
if (childname == contentname) {
//ContentProperty
_node.CollectionItems.Add(childnode);
}
else {
_node.Properties[childname] = childnode;
}
}
return _node;
}
protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
{
if (value == null && strValue == null) {
var xpe = new XamlParseException($"No value found for property '{prop}' in markup expression", serviceProvider);
if (ExceptionHandler != null) {
ExceptionHandler(xpe);
return;
}
throw xpe;
}
var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver;
var childnode = value as INode ?? new ValueNode(strValue, nsResolver);
childnode.Parent = _node;
if (prop != null) {
var name = new XmlName(_node.NamespaceURI, prop);
_node.Properties[name] = childnode;
}
else //ContentProperty
_node.CollectionItems.Add(childnode);
}
}
}
}

Просмотреть файл

@ -38,6 +38,13 @@ namespace Xamarin.Forms.Xaml
{
abstract class MarkupExpressionParser
{
protected struct Property
{
public string name;
public string strValue;
public object value;
}
public object ParseExpression(ref string expression, IServiceProvider serviceProvider)
{
if (serviceProvider == null)
@ -112,7 +119,7 @@ namespace Xamarin.Forms.Xaml
return true;
}
protected void HandleProperty(string prop, IServiceProvider serviceProvider, ref string remaining, bool isImplicit)
protected Property ParseProperty(string prop, IServiceProvider serviceProvider, ref string remaining, bool isImplicit)
{
char next;
object value = null;
@ -120,8 +127,7 @@ namespace Xamarin.Forms.Xaml
if (isImplicit)
{
SetPropertyValue(null, prop, null, serviceProvider);
return;
return new Property { name = null, strValue = prop, value = null };
}
remaining = remaining.TrimStart();
if (remaining.StartsWith("{", StringComparison.Ordinal))
@ -136,14 +142,16 @@ namespace Xamarin.Forms.Xaml
str_value = value as string;
}
else
else {
str_value = GetNextPiece(serviceProvider, ref remaining, out next);
if (str_value == null) {
throw new XamlParseException($"No value found for property '{prop}' in markup expression", serviceProvider);
}
}
SetPropertyValue(prop, str_value, value, serviceProvider);
return new Property { name = prop, strValue = str_value, value = value };
}
protected abstract void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider);
protected string GetNextPiece(IServiceProvider serviceProvider, ref string remaining, out char next)
{
bool inString = false;
@ -225,5 +233,15 @@ namespace Xamarin.Forms.Xaml
return piece.ToString();
}
protected static (string, string) ParseName(string name)
{
var split = name.Split(':');
if (split.Length > 2)
throw new ArgumentException();
return split.Length == 2 ? (split[0], split[1]) : ("", split[0]);
}
}
}

Просмотреть файл

@ -43,13 +43,15 @@ namespace Xamarin.Forms.Xaml
return markupExtension.ProvideValue(serviceProvider);
string piece;
while ((piece = GetNextPiece(serviceProvider, ref remaining, out char next)) != null)
HandleProperty(piece, serviceProvider, ref remaining, next != '=');
while ((piece = GetNextPiece(serviceProvider, ref remaining, out char next)) != null) {
var value = ParseProperty(piece, serviceProvider, ref remaining, next != '=');
SetPropertyValue(value.name, value.strValue, value.value, serviceProvider);
}
return markupExtension.ProvideValue(serviceProvider);
}
protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
private void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
{
MethodInfo setter;
if (prop == null) {

Просмотреть файл

@ -219,58 +219,15 @@ namespace Xamarin.Forms.Xaml
var namespaceUri = reader.NamespaceURI;
if (reader.LocalName.Contains(".") && namespaceUri == "")
namespaceUri = ((IXmlNamespaceResolver)reader).LookupNamespace("");
var propertyName = new XmlName(namespaceUri, reader.LocalName);
var propertyName = ParsePropertyName(new XmlName(namespaceUri, reader.LocalName));
if (propertyName.NamespaceURI == null && propertyName.LocalName == null)
continue;
object value = reader.Value;
if (reader.NamespaceURI == X2006Uri)
{
switch (reader.LocalName) {
case "Key":
propertyName = XmlName.xKey;
break;
case "Name":
propertyName = XmlName.xName;
break;
case "Class":
case "FieldModifier":
continue;
default:
Debug.WriteLine("Unhandled attribute {0}", reader.Name);
continue;
}
}
if (reader.NamespaceURI == X2009Uri)
{
switch (reader.LocalName) {
case "Key":
propertyName = XmlName.xKey;
break;
case "Name":
propertyName = XmlName.xName;
break;
case "TypeArguments":
propertyName = XmlName.xTypeArguments;
value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader);
break;
case "DataType":
propertyName = XmlName.xDataType;
break;
case "Class":
case "FieldModifier":
continue;
case "FactoryMethod":
propertyName = XmlName.xFactoryMethod;
break;
case "Arguments":
propertyName = XmlName.xArguments;
break;
default:
Debug.WriteLine("Unhandled attribute {0}", reader.Name);
continue;
}
}
if (propertyName == XmlName.xTypeArguments)
value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader);
var propertyNode = GetValueNode(value, reader);
attributes.Add(new KeyValuePair<XmlName, INode>(propertyName, propertyNode));
@ -279,6 +236,51 @@ namespace Xamarin.Forms.Xaml
return attributes;
}
public static XmlName ParsePropertyName(XmlName name)
{
if (name.NamespaceURI == X2006Uri)
{
switch (name.LocalName) {
case "Key":
return XmlName.xKey;
case "Name":
return XmlName.xName;
case "Class":
case "FieldModifier":
return new XmlName(null, null);
default:
Debug.WriteLine("Unhandled attribute {0}", name);
return new XmlName(null, null);
}
}
if (name.NamespaceURI == X2009Uri)
{
switch (name.LocalName) {
case "Key":
return XmlName.xKey;
case "Name":
return XmlName.xName;
case "TypeArguments":
return XmlName.xTypeArguments;
case "DataType":
return XmlName.xDataType;
case "Class":
case "FieldModifier":
return new XmlName(null, null);
case "FactoryMethod":
return XmlName.xFactoryMethod;
case "Arguments":
return XmlName.xArguments;
default:
Debug.WriteLine("Unhandled attribute {0}", name);
return new XmlName(null, null);
}
}
return name;
}
static IList<string> PrefixesToIgnore(IList<KeyValuePair<string, string>> xmlns)
{
var prefixes = new List<string>();

Просмотреть файл

@ -196,6 +196,13 @@ namespace Xamarin.Forms.Xaml.Internals
return exception == null;
}
internal bool TryResolve(XmlType xmlType, out Type type)
{
XamlParseException exception;
type = getTypeFromXmlName(xmlType, null, currentAssembly, out exception);
return exception == null;
}
Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider, out XamlParseException exception)
{
exception = null;