Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/libraries/System.Private.Xml/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2313,6 +2313,12 @@
<data name="XmlInvalidFormUnqualified" xml:space="preserve">
<value>The Form property may not be 'Unqualified' when an explicit Namespace property is present.</value>
</data>
<data name="XmlInvalidTextSeparatorChar" xml:space="preserve">
<value>The specified Separator character is not valid in XML text content and cannot be used as a separator for member '{0}'.</value>
</data>
<data name="XmlInvalidAttributeSeparatorChar" xml:space="preserve">
<value>The specified Separator character is not valid in an XML attribute value and cannot be used as a separator for member '{0}'.</value>
</data>
<data name="XmlDuplicateNamespace" xml:space="preserve">
<value>The namespace, {0}, is a duplicate.</value>
</data>
Expand Down Expand Up @@ -2607,6 +2613,9 @@
<data name="XmlIllegalArrayTextAttribute" xml:space="preserve">
<value>Member '{0}' cannot be encoded using the XmlText attribute. You may use the XmlText attribute to encode primitives, enumerations, arrays of strings, or arrays of XmlNode.</value>
</data>
<data name="XmlIllegalArrayTextSeparator" xml:space="preserve">
<value>Member '{0}' cannot use the XmlText attribute Separator property. You may use the Separator property to encode arrays of strings.</value>
</data>
<data name="XmlIllegalTypedTextAttribute" xml:space="preserve">
<value>Cannot serialize object of type '{0}'. Consider changing type of XmlText member '{0}.{1}' from {2} to string or string array.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ internal MemberInfo? MemberInfo

internal sealed class TextAccessor : Accessor
{
internal char? Separator { get; set; }
}

internal sealed class XmlnsAccessor : Accessor
Expand All @@ -231,6 +232,8 @@ internal bool IsList
set { _isList = value; }
}

internal char? Separator { get; set; }

internal void CheckSpecial()
{
int colon = Name.LastIndexOf(':');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,14 +696,20 @@ private bool WriteMemberText(Member anyText)
{
if (anyTextMapping.TypeDesc!.IsArrayLike)
{
if (text.Mapping!.TypeDesc!.CollapseWhitespace)
{
value = CollapseWhitespace(Reader.ReadString());
}
else
string rawText = text.Mapping!.TypeDesc!.CollapseWhitespace
? CollapseWhitespace(Reader.ReadString())
: Reader.ReadString();

if (text.Separator.HasValue)
{
value = Reader.ReadString();
foreach (string part in rawText.Split(text.Separator.Value))
{
anyText.Source!(part);
}
return true;
}

value = rawText;
}
else
{
Expand Down Expand Up @@ -1994,7 +2000,9 @@ private void WriteAttribute(Member member, object? attr = null)
if (attribute.IsList)
{
string listValues = Reader.Value;
string[] vals = listValues.Split(null);
string[] vals = attribute.Separator.HasValue
? listValues.Split(attribute.Separator.Value)
: listValues.Split((char[]?)null);
Array arrayValue = Array.CreateInstance(member.Mapping.TypeDesc!.Type!.GetElementType()!, vals.Length);
for (int i = 0; i < vals.Length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private void WriteMember(object? o, object? choiceSource, ElementAccessor[] elem
}
else
{
WriteElements(o, choiceSource, elements, text, choice, writeAccessors, memberTypeDesc.IsNullable);
WriteElements(o, choiceSource, elements, text, choice, writeAccessors, memberTypeDesc.IsNullable, emitSeparator: false, separatorStr: null);
}
}

Expand Down Expand Up @@ -153,14 +153,17 @@ private void WriteArray(object o, object? choiceSource, ElementAccessor[] elemen
private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, object o, object? choiceSources)
{
var arr = o as IList;
bool hasSeparator = text?.Separator.HasValue == true;
string? separatorStr = hasSeparator ? text!.Separator!.Value.ToString() : null;
bool lastWasText = false;

if (arr != null)
{
for (int i = 0; i < arr.Count; i++)
{
object? ai = arr[i];
var choiceSource = ((Array?)choiceSources)?.GetValue(i);
WriteElements(ai, choiceSource, elements, text, choice, true, true);
lastWasText = WriteElements(ai, choiceSource, elements, text, choice, true, true, hasSeparator && lastWasText, separatorStr);
}
}
else
Expand All @@ -176,26 +179,33 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho
{
object ai = e.Current;
var choiceSource = ((Array?)choiceSources)?.GetValue(c++);
WriteElements(ai, choiceSource, elements, text, choice, true, true);
lastWasText = WriteElements(ai, choiceSource, elements, text, choice, true, true, hasSeparator && lastWasText, separatorStr);
}
}
}
}

private void WriteElements(object? o, object? choiceSource, ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, bool writeAccessors, bool isNullable)
/// <returns>
/// true if this item was written as text, false if an element was written,
/// or <paramref name="emitSeparator"/> if nothing was written (preserving status quo).
/// Since <paramref name="emitSeparator"/> is true only when the previous item was
/// text in a separator-tracking context, returning it propagates that state forward.
/// </returns>
private bool WriteElements(object? o, object? choiceSource, ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, bool writeAccessors, bool isNullable, bool emitSeparator, string? separatorStr)
{
if (elements.Length == 0 && text == null)
return;
return emitSeparator;

if (elements.Length == 1 && text == null)
{
WriteElement(o, elements[0], writeAccessors);
return false;
}
else
{
if (isNullable && choice == null && o == null)
{
return;
return emitSeparator;
}

int anyCount = 0;
Expand Down Expand Up @@ -241,7 +251,7 @@ private void WriteElements(object? o, object? choiceSource, ElementAccessor[] el
}

WriteElement(o, element, writeAccessors);
return;
return false;
}
}
}
Expand All @@ -251,7 +261,7 @@ private void WriteElements(object? o, object? choiceSource, ElementAccessor[] el
if (td.Type!.IsAssignableFrom(o!.GetType()))
{
WriteElement(o, element, writeAccessors);
return;
return false;
}
}
}
Expand All @@ -265,7 +275,7 @@ private void WriteElements(object? o, object? choiceSource, ElementAccessor[] el
if (element.Name == elem.Name && element.Namespace == elem.NamespaceURI)
{
WriteElement(elem, element, writeAccessors);
return;
return false;
}
}

Expand All @@ -277,24 +287,30 @@ private void WriteElements(object? o, object? choiceSource, ElementAccessor[] el
if (unnamedAny != null)
{
WriteElement(elem, unnamedAny, writeAccessors);
return;
return false;
}

throw CreateUnknownAnyElementException(elem.Name, elem.NamespaceURI);
}
}

if (text != null)
if (text != null && o is not null)
{
WriteText(o!, text);
return;
if (emitSeparator)
{
WriteValue(separatorStr);
}
WriteText(o, text);
return true;
}

if (elements.Length > 0 && o != null)
{
throw CreateUnknownTypeException(o);
}
}

return emitSeparator;
}

private static string FindChoiceEnumValue(ElementAccessor element, EnumMapping choiceMapping, bool useReflection)
Expand Down Expand Up @@ -854,7 +870,9 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD
{
var a = (IEnumerable)memberValue;
IEnumerator e = a.GetEnumerator();
bool shouldAppendWhitespace = false;
bool shouldAppendSeparator = false;
char separatorChar = attribute.Separator ?? ' ';
string separatorString = separatorChar.ToString();
if (e != null)
{
while (e.MoveNext())
Expand All @@ -879,9 +897,9 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD
// check to see if we can write values of the attribute sequentially
if (canOptimizeWriteListSequence)
{
if (shouldAppendWhitespace)
if (shouldAppendSeparator)
{
Writer.WriteString(" ");
Writer.WriteString(separatorString);
}

if (ai is byte[])
Expand All @@ -895,9 +913,9 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD
}
else
{
if (shouldAppendWhitespace)
if (shouldAppendSeparator)
{
sb.Append(' ');
sb.Append(separatorChar);
}

sb.Append(stringValue);
Expand All @@ -908,7 +926,7 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD
WriteAttribute(ai, attribute, container);
}

shouldAppendWhitespace = true;
shouldAppendSeparator = true;
}

if (attribute.IsList)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class XmlAttributeAttribute : System.Attribute
private string? _ns;
private string? _dataType;
private XmlSchemaForm _form = XmlSchemaForm.None;
private char _separator;

/// <devdoc>
/// <para>[To be supplied.]</para>
Expand Down Expand Up @@ -97,5 +98,17 @@ public XmlSchemaForm Form
get { return _form; }
set { _form = value; }
}

/// <summary>Gets or sets the separator character used when serializing an array as an XML attribute value list.</summary>
/// <remarks>
/// When set to the default value of <c>'\0'</c> (null character), the space character is used as separator,
/// preserving the existing behavior for <see cref="XmlAttributeAttribute"/> on array-typed members.
/// Set to a non-default value to override the separator.
/// </remarks>
public char Separator
{
get { return _separator; }
set { _separator = value; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,11 @@ private void ImportAccessorMapping(MemberMapping accessor, FieldModel model, Xml
CheckForm(attribute.Form, ns != attribute.Namespace);
attribute.Mapping = ImportTypeMapping(_modelScope.GetTypeModel(targetType), ns, ImportContext.Attribute, a.XmlAttribute.DataType, null, isList, false, limiter);
attribute.IsList = isList;
if (a.XmlAttribute.Separator != '\0')
{
ValidateAttributeSeparatorChar(a.XmlAttribute.Separator, accessorName);
attribute.Separator = a.XmlAttribute.Separator;
}
attribute.Default = GetDefaultValue(model.FieldTypeDesc, model.FieldType, a);
attribute.Any = (a.XmlAnyAttribute != null);
if (attribute.Form == XmlSchemaForm.Qualified && attribute.Namespace != ns)
Expand All @@ -1637,6 +1642,15 @@ private void ImportAccessorMapping(MemberMapping accessor, FieldModel model, Xml
if (!(text.Mapping is SpecialMapping) && targetTypeDesc != _typeScope.GetTypeDesc(typeof(string)))
throw new InvalidOperationException(SR.Format(SR.XmlIllegalArrayTextAttribute, accessorName));

if (a.XmlText.Separator != '\0')
{
Comment thread
StephenMolloy marked this conversation as resolved.
Comment thread
StephenMolloy marked this conversation as resolved.
ValidateTextSeparatorChar(a.XmlText.Separator, accessorName);
if (text.Mapping is SpecialMapping)
throw new InvalidOperationException(SR.Format(SR.XmlIllegalArrayTextSeparator, accessorName));

text.Separator = a.XmlText.Separator;
}

accessor.Text = text;
}
if (a.XmlText == null && a.XmlElements.Count == 0 && a.XmlAnyElements.Count == 0)
Expand Down Expand Up @@ -2261,6 +2275,20 @@ private static void CheckForm(XmlSchemaForm form, bool isQualified)
if (isQualified && form == XmlSchemaForm.Unqualified) throw new InvalidOperationException(SR.XmlInvalidFormUnqualified);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateTextSeparatorChar(char separator, string memberName)
{
if (!XmlCharType.IsTextChar(separator))
throw new InvalidOperationException(SR.Format(SR.XmlInvalidTextSeparatorChar, memberName));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateAttributeSeparatorChar(char separator, string memberName)
{
if (!XmlCharType.IsAttributeValueChar(separator))
throw new InvalidOperationException(SR.Format(SR.XmlInvalidAttributeSeparatorChar, memberName));
}
Comment thread
StephenMolloy marked this conversation as resolved.

private static void CheckNullable(bool isNullable, TypeDesc typeDesc, TypeMapping? mapping)
{
if (mapping is NullableMapping) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ internal void WriteQuotedCSharpString(string? value)
_raCodeGen.WriteQuotedCSharpString(value);
}

internal void WriteQuotedCSharpChar(char value)
{
_raCodeGen.WriteQuotedCSharpChar(value);
}

internal void GenerateHashtableGetBegin(string privateName, string publicName)
{
_writer.Write(typeof(Hashtable).FullName);
Expand Down
Loading