public static ParsedMarkupExtensionInfo Parse(string raw, IXamlNamespaceResolver nsResolver, XamlSchemaContext sctx) { if (raw == null) { throw new ArgumentNullException(nameof(raw)); } if (raw.Length == 0 || raw[0] != '{') { throw Error("Invalid markup extension attribute. It should begin with '{{', but was {0}", raw); } var ret = new ParsedMarkupExtensionInfo(); int idx = raw.LastIndexOf('}'); if (idx < 0) { throw Error("Expected '}}' in the markup extension attribute: '{0}'", raw); } raw = raw.Substring(1, idx - 1); idx = raw.IndexOf(' '); string name = idx < 0 ? raw : raw.Substring(0, idx); XamlTypeName xtn; if (!XamlTypeName.TryParse(name, nsResolver, out xtn)) { throw Error("Failed to parse type name '{0}'", name); } var xt = sctx.GetXamlType(xtn) ?? new XamlType(nsResolver.GetNamespace(""), name, null, sctx); ret.Type = xt; if (idx < 0) { return(ret); } var valueWithoutBinding = raw.Substring(idx + 1, raw.Length - idx - 1); var vpairs = BindingMembersRegex.Matches(valueWithoutBinding) .Cast <Match>() .Select(m => m.Value.Trim()) .ToList(); if (vpairs.Count == 0) { vpairs.Add(valueWithoutBinding); } List <string> posPrms = null; XamlMember lastMember = null; foreach (var vpair in vpairs) { idx = vpair.IndexOf('='); // FIXME: unescape string (e.g. comma) if (idx < 0) { if (vpair.ElementAtOrDefault(0) == ')') { if (lastMember != null) { if (ret.Arguments[lastMember] is string s) { ret.Arguments[lastMember] = s + ')'; } } else { posPrms[posPrms.Count - 1] += ')'; } } else { if (posPrms == null) { posPrms = new List <string>(); ret.Arguments.Add(XamlLanguage.PositionalParameters, posPrms); } posPrms.Add(UnescapeValue(vpair.Trim())); } } else { var key = vpair.Substring(0, idx).Trim(); // FIXME: is unknown member always isAttacheable = false? var xm = xt.GetMember(key) ?? new XamlMember(key, xt, false); // Binding member values may be wrapped in quotes (single or double) e.g. 'A,B,C,D'. // Remove those wrapping quotes from the resulting string value. var valueString = RemoveWrappingStringQuotes(vpair.Substring(idx + 1).Trim()); var value = IsValidMarkupExtension(valueString) ? (object)Parse(valueString, nsResolver, sctx) : UnescapeValue(valueString); ret.Arguments.Add(xm, value); lastMember = xm; } } return(ret); }
// Note that it could return invalid (None) node to tell the caller that it is not really an object element. IEnumerable <XamlXmlNodeInfo> ReadObjectElement(XamlType parentType, XamlMember currentMember) { if (r.NodeType != XmlNodeType.Element) { //throw new XamlParseException (String.Format ("Element is expected, but got {0}", r.NodeType)); yield return(Node(XamlNodeType.Value, ReadCurrentContentString(isFirstElementString: false))); if (r.NodeType != XmlNodeType.Element) { r.ReadContentAsString(); } yield break; } if (r.MoveToFirstAttribute()) { do { if (r.NamespaceURI == XamlLanguage.Xmlns2000Namespace) { yield return(Node(XamlNodeType.NamespaceDeclaration, new NamespaceDeclaration(r.Value, r.Prefix == "xmlns" ? r.LocalName : String.Empty))); } } while (r.MoveToNextAttribute()); r.MoveToElement(); } var sti = GetStartTagInfo(); using (PushIgnorables(sti.Members)) { if (IsIgnored(r.Prefix)) { r.Skip(); yield break; } if (r.NodeType != XmlNodeType.Element) { //throw new XamlParseException (String.Format ("Element is expected, but got {0}", r.NodeType)); yield return(Node(XamlNodeType.Value, r.Value)); } var xt = sctx.GetXamlType(sti.TypeName); if (xt == null) { // creates name-only XamlType. Also, it does not seem that it does not store this XamlType to XamlSchemaContext (Try GetXamlType(xtn) after reading such xaml node, it will return null). xt = new XamlType(sti.Namespace, sti.Name, sti.TypeName.TypeArguments?.Select(xxtn => sctx.GetXamlType(xxtn)).ToArray(), sctx); } bool isGetObject = false; if (currentMember != null && !xt.CanAssignTo(currentMember.Type)) { if (currentMember.DeclaringType != null && currentMember.DeclaringType.ContentProperty == currentMember) { isGetObject = true; } // It could still be GetObject if current_member // is not a directive and current type is not // a markup extension. // (I'm not very sure about the condition; // it could be more complex.) // seealso: bug #682131 else if (!(currentMember is XamlDirective) && !xt.IsMarkupExtension) { isGetObject = true; } } if (isGetObject) { yield return(Node(XamlNodeType.GetObject, currentMember.Type)); foreach (var ni in ReadMembers(parentType, currentMember.Type)) { yield return(ni); } yield return(Node(XamlNodeType.EndObject, currentMember.Type)); yield break; } // else yield return(Node(XamlNodeType.StartObject, xt)); // process attribute members (including MarkupExtensions) ProcessAttributesToMember(sctx, sti, xt); foreach (var pair in sti.Members) { if (pair.Key == XamlLanguage.Ignorable) { continue; } yield return(Node(XamlNodeType.StartMember, pair.Key)); // Try markup extension // FIXME: is this rule correct? var v = pair.Value; if (!String.IsNullOrEmpty(v) && v[0] == '{' && v.ElementAtOrDefault(1) != '}') { IEnumerable <XamlXmlNodeInfo> ProcessArgs(ParsedMarkupExtensionInfo info) { yield return(Node(XamlNodeType.StartObject, info.Type)); foreach (var xepair in info.Arguments) { yield return(Node(XamlNodeType.StartMember, xepair.Key)); switch (xepair.Value) { case List <string> list: foreach (var s in list) { yield return(Node(XamlNodeType.Value, s)); } break; case ParsedMarkupExtensionInfo inner: foreach (var innerInfo in ProcessArgs(inner)) { yield return(innerInfo); } break; default: yield return(Node(XamlNodeType.Value, xepair.Value)); break; } yield return(Node(XamlNodeType.EndMember, xepair.Key)); } yield return(Node(XamlNodeType.EndObject, info.Type)); } var pai = ParsedMarkupExtensionInfo.Parse(v, xaml_namespace_resolver, sctx); foreach (var info in ProcessArgs(pai)) { yield return(info); } } else { yield return(Node(XamlNodeType.Value, CleanupBindingEscape(pair.Value))); } yield return(Node(XamlNodeType.EndMember, pair.Key)); } // process content members if (!r.IsEmptyElement) { r.Read(); foreach (var ni in ReadMembers(parentType, xt)) { yield return(ni); } r.ReadEndElement(); } else { r.Read(); // consume empty element. } yield return(Node(XamlNodeType.EndObject, xt)); } }
public static ParsedMarkupExtensionInfo Parse(string raw, IXamlNamespaceResolver nsResolver, XamlSchemaContext sctx) { if (raw == null) { throw new ArgumentNullException(nameof(raw)); } if (raw.Length == 0 || raw[0] != '{') { throw Error("Invalid markup extension attribute. It should begin with '{{', but was {0}", raw); } if (raw.Length >= 2 && raw[1] == '}') { throw Error("Markup extension can not begin with an '{}' escape: '{0}'", raw); } var ret = new ParsedMarkupExtensionInfo(); if (raw[raw.Length - 1] != '}') { // Any character after the final closing bracket is not accepted. Therefore, the last character should be '}'. // Ideally, we should still ran the entire markup through the parser to get a more meaningful error. throw Error("Expected '}}' in the markup extension attribute: '{0}'", raw); } var nameSeparatorIndex = raw.IndexOf(' '); var name = nameSeparatorIndex != -1 ? raw.Substring(1, nameSeparatorIndex - 1) : raw.Substring(1, raw.Length - 2); if (!XamlTypeName.TryParse(name, nsResolver, out var xtn)) { throw Error("Failed to parse type name '{0}'", name); } var xt = sctx.GetXamlType(xtn) ?? new XamlType(xtn.Namespace, xtn.Name, null, sctx); ret.Type = xt; if (nameSeparatorIndex < 0) { return(ret); } var valueWithoutBinding = raw.Substring(nameSeparatorIndex + 1, raw.Length - 1 - (nameSeparatorIndex + 1)); var vpairs = SliceParameters(valueWithoutBinding, raw); List <string> posPrms = null; XamlMember lastMember = null; foreach (var vpair in vpairs) { var idx = vpair.IndexOf('='); // FIXME: unescape string (e.g. comma) if (idx < 0) { if (vpair.ElementAtOrDefault(0) == ')') { if (lastMember != null) { if (ret.Arguments[lastMember] is string s) { ret.Arguments[lastMember] = s + ')'; } } else { posPrms[posPrms.Count - 1] += ')'; } } else { if (posPrms == null) { posPrms = new List <string>(); ret.Arguments.Add(XamlLanguage.PositionalParameters, posPrms); } posPrms.Add(UnescapeValue(vpair.Trim())); } } else { var key = vpair.Substring(0, idx).Trim(); // FIXME: is unknown member always isAttacheable = false? var xm = xt.GetMember(key) ?? new XamlMember(key, xt, false); // Binding member values may be wrapped in quotes (single or double) e.g. 'A,B,C,D'. // Remove those wrapping quotes from the resulting string value. var valueString = RemoveWrappingStringQuotes(vpair.Substring(idx + 1).Trim()); var value = IsValidMarkupExtension(valueString) ? (object)Parse(valueString, nsResolver, sctx) : UnescapeValue(valueString); ret.Arguments.Add(xm, value); lastMember = xm; } } return(ret); }