зеркало из https://github.com/DeGsoft/maui-linux.git
[Xaml[C]] Accept prefixed property names of markup extensions (#5186)
This commit is contained in:
Родитель
6c3b4f57fc
Коммит
a1a9f6e1be
|
@ -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;
|
||||
}
|
||||
|
||||
Type type;
|
||||
if (!(serviceProvider.GetService(typeof(IXamlTypeResolver)) is IXamlTypeResolver typeResolver))
|
||||
type = null;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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(match + "Extension", out type) && !typeResolver.TryResolve(match, out type)) {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
foreach (var (childname, childnode) in childnodes) {
|
||||
childnode.Parent = _node;
|
||||
if (prop != null) {
|
||||
var name = new XmlName(_node.NamespaceURI, prop);
|
||||
_node.Properties[name] = childnode;
|
||||
}
|
||||
else //ContentProperty
|
||||
|
||||
if (childname == contentname) {
|
||||
//ContentProperty
|
||||
_node.CollectionItems.Add(childnode);
|
||||
}
|
||||
else {
|
||||
_node.Properties[childname] = childnode;
|
||||
}
|
||||
}
|
||||
|
||||
return _node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,13 +142,15 @@ namespace Xamarin.Forms.Xaml
|
|||
|
||||
str_value = value as string;
|
||||
}
|
||||
else
|
||||
else {
|
||||
str_value = GetNextPiece(serviceProvider, ref remaining, out next);
|
||||
|
||||
SetPropertyValue(prop, str_value, value, serviceProvider);
|
||||
if (str_value == null) {
|
||||
throw new XamlParseException($"No value found for property '{prop}' in markup expression", serviceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider);
|
||||
return new Property { name = prop, strValue = str_value, value = value };
|
||||
}
|
||||
|
||||
protected string GetNextPiece(IServiceProvider serviceProvider, ref string remaining, out char next)
|
||||
{
|
||||
|
@ -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;
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче