From 1804b9286f02a9aa9e4ac3f9af70c8a35fcc6fc5 Mon Sep 17 00:00:00 2001 From: Nathan Ferreira <14365254+nathan130200@users.noreply.github.com> Date: Sun, 28 Apr 2024 11:44:30 -0300 Subject: [PATCH] Bump version. - Fixed wrong sub classing around some elements. - Added full control about XML formatting. - Fixed overload around SetAttributeValue. net6.0 use TryParseHelper. .ne7+ use IParsable interface instead. - All tests passing --- XmppSharp.Test/ParserTests.cs | 2 +- XmppSharp.Test/XmlTests.cs | 2 +- XmppSharp/Dom/Cdata.cs | 7 +- XmppSharp/Dom/Comment.cs | 7 +- XmppSharp/Dom/Element.cs | 154 ++++++++++++++------ XmppSharp/Dom/Node.cs | 2 +- XmppSharp/Dom/Text.cs | 7 +- XmppSharp/Jid.cs | 8 +- XmppSharp/Protocol/Base/Stanza.cs | 2 +- XmppSharp/Protocol/Base/StanzaError.cs | 16 +- XmppSharp/Protocol/Base/StreamError.cs | 6 +- XmppSharp/Protocol/Base/StreamFeatures.cs | 12 +- XmppSharp/Protocol/Base/StreamStream.cs | 2 +- XmppSharp/Protocol/Client/Bind.cs | 8 +- XmppSharp/Protocol/DataForms/Field.cs | 4 +- XmppSharp/Protocol/DataForms/Form.cs | 4 +- XmppSharp/Protocol/DataForms/Option.cs | 2 +- XmppSharp/Protocol/Message.cs | 6 +- XmppSharp/Protocol/Presence.cs | 6 +- XmppSharp/Protocol/Sasl/Failure.cs | 18 ++- XmppSharp/Protocol/Sasl/FailureCondition.cs | 2 + XmppSharp/StringBuilderPool.cs | 35 ++--- XmppSharp/Utilities.cs | 22 ++- XmppSharp/Xml.cs | 79 +++++++--- XmppSharp/XmlFormatting.cs | 82 +++++++++++ XmppSharp/XmppSharp.csproj | 2 +- 26 files changed, 344 insertions(+), 153 deletions(-) create mode 100644 XmppSharp/XmlFormatting.cs diff --git a/XmppSharp.Test/ParserTests.cs b/XmppSharp.Test/ParserTests.cs index ba03177..e6a4fdf 100644 --- a/XmppSharp.Test/ParserTests.cs +++ b/XmppSharp.Test/ParserTests.cs @@ -71,7 +71,7 @@ internal static async Task ParseFromBuffer([StringSyntax("Xml")] string internal static void PrintResult(Element e) { - Debug.WriteLine("\nRESULT:\n" + e.ToString(true) + "\n"); + Debug.WriteLine("\nRESULT:\n" + e.ToString(XmlFormatting.Indented) + "\n"); } [TestMethod] diff --git a/XmppSharp.Test/XmlTests.cs b/XmppSharp.Test/XmlTests.cs index d365075..2bd052d 100644 --- a/XmppSharp.Test/XmlTests.cs +++ b/XmppSharp.Test/XmlTests.cs @@ -135,7 +135,7 @@ public async Task CloneNodes() var cloned = elem.Clone(); Assert.AreNotSame(elem, cloned); - var outXml = cloned.ToString(false); + var outXml = cloned.ToString(XmlFormatting.Default); Assert.AreEqual(inXml, outXml); } diff --git a/XmppSharp/Dom/Cdata.cs b/XmppSharp/Dom/Cdata.cs index 752a991..268bec6 100644 --- a/XmppSharp/Dom/Cdata.cs +++ b/XmppSharp/Dom/Cdata.cs @@ -12,6 +12,9 @@ public class Cdata : Node public override Node Clone() => new Cdata(this); - public override void WriteTo(XmlWriter writer) - => writer.WriteCData(this.Value); + public override void WriteTo(XmlWriter writer, in XmlFormatting formatting) + { + if (formatting.IncludeCdataNodes) + writer.WriteCData(this.Value); + } } \ No newline at end of file diff --git a/XmppSharp/Dom/Comment.cs b/XmppSharp/Dom/Comment.cs index f1e04de..ae3b733 100644 --- a/XmppSharp/Dom/Comment.cs +++ b/XmppSharp/Dom/Comment.cs @@ -12,6 +12,9 @@ public class Comment : Node public override Node Clone() => new Comment(this); - public override void WriteTo(XmlWriter writer) - => writer.WriteComment(this.Value); + public override void WriteTo(XmlWriter writer, in XmlFormatting formatting) + { + if (formatting.IncludeCommentNodes) + writer.WriteComment(this.Value); + } } diff --git a/XmppSharp/Dom/Element.cs b/XmppSharp/Dom/Element.cs index 0bcdfaa..71686b6 100644 --- a/XmppSharp/Dom/Element.cs +++ b/XmppSharp/Dom/Element.cs @@ -34,6 +34,9 @@ public Element(string qualifiedName, string namespaceURI) : this(qualifiedName) } } + public bool IsRootElement + => Parent is null; + public IEnumerable Nodes() { lock (this._childNodes) @@ -101,18 +104,40 @@ public override string Value } } + /// + /// Gets the XML string representation of the current element and its child nodes. + /// + /// Well-formed XML serialized with the entire XML tree. public override string ToString() - => this.ToString(false); + => this.ToString(XmlFormatting.Default); - public string ToString(bool indented, char indentChar = ' ', int indentSize = 2) + /// + /// Gets the XML string representation of the current element and its child nodes. + /// + /// Determines which formatting will be used. + /// Well-formed XML serialized with the entire XML tree. + public string ToString(XmlFormatting formatting) { - using (StringBuilderPool.Rent(out var sb)) + StringBuilderPool.Rent(out var sb); + + try { - using (var writer = Xml.CreateWriter(indented, sb, indentChar, indentSize)) + using (var writer = Xml.CreateWriter(sb, formatting)) + +/* Unmerged change from project 'XmppSharp (net8.0)' +Before: this.WriteTo(writer); +After: + this.WriteTo(writer, formatting); +*/ + this.WriteTo(writer, formatting); return sb.ToString(); } + finally + { + StringBuilderPool.Return(sb); + } } public string? DefaultNamespace @@ -146,7 +171,7 @@ public override Element Clone() return elem; } - public override void WriteTo(XmlWriter writer) + public override void WriteTo(XmlWriter writer, in XmlFormatting formatting) { var ns = this.GetNamespace(this._prefix); @@ -172,19 +197,19 @@ public override void WriteTo(XmlWriter writer) if (!info.HasPrefix) writer.WriteAttributeString(name, value); else - writer.WriteAttributeString(info.Prefix, info.LocalName, info.Prefix switch + writer.WriteAttributeString(info.LocalName, info.Prefix switch { "xml" => Namespace.Xml, "xmlns" => Namespace.Xmlns, _ => this.GetNamespace(info.Prefix) ?? string.Empty - }); + }, value); } } lock (this._childNodes) { foreach (var node in this._childNodes) - node.WriteTo(writer); + node.WriteTo(writer, formatting); } writer.WriteEndElement(); @@ -281,18 +306,20 @@ public virtual void RemoveChild(Node n) return; lock (this._childNodes) - this._childNodes.Remove(n); + { + n._parent = null; - n._parent = null; + this._childNodes.Remove(n); - if (n is Element elem) - { - var prefix = elem.Prefix; + if (n is Element elem) + { + var prefix = elem.Prefix; - if (prefix != null) - elem.SetNamespace(prefix, this.GetNamespace(prefix)); - else - elem.SetNamespace(this.GetNamespace()); + if (prefix != null) + elem.SetNamespace(prefix, this.GetNamespace(prefix)); + else + elem.SetNamespace(this.GetNamespace()); + } } } @@ -332,6 +359,16 @@ public void SetNamespace(string prefix, string uri) return value; } + public IReadOnlyDictionary Attributes() + { + KeyValuePair[] result; + + lock (_attributes) + result = _attributes.ToArray(); + + return result.ToDictionary(x => x.Key, x => x.Value); + } + public Element SetAttribute(string name, object? value) { Require.NotNullOrWhiteSpace(name); @@ -347,6 +384,29 @@ public Element SetAttribute(string name, object? value) return this; } + public void RemoveAllChildNodes() + { + lock (_childNodes) + { + foreach (var item in _childNodes) + item._parent = null; + + _childNodes.Clear(); + } + } + + public void RemoveAllAttributes() + { + lock (_attributes) + _attributes.Clear(); + } + + public void Clear() + { + RemoveAllChildNodes(); + RemoveAllAttributes(); + } + public Element RemoveAttribute(string name) { Require.NotNullOrWhiteSpace(name); @@ -367,49 +427,45 @@ public bool HasAttribute(string name) public IEnumerable Children() { + var result = new List(); + lock (this._childNodes) { - foreach (var node in this._childNodes) + foreach (var node in _childNodes) { if (node is Element e) - yield return e; + result.Add(e); } } + + return result.AsEnumerable(); } public IEnumerable Children() => this.Children().OfType(); - public IEnumerable Children(string? tagName, string namespaceURI) + public IEnumerable Children(string? tagName, string? namespaceURI = default) { Require.NotNull(tagName); - return this.Children(x => x.TagName == tagName && x.Prefix == null + return this.Children(x => x.TagName == tagName && namespaceURI == null || (x.Prefix == null ? x.GetNamespace() == namespaceURI - : x.GetNamespace(x.Prefix) == namespaceURI); + : x.GetNamespace(x.Prefix) == namespaceURI)); } public IEnumerable Children(Func predicate) { Require.NotNull(predicate); - - lock (this._childNodes) - { - foreach (var node in this._childNodes) - { - if (node is Element child && predicate(child)) - yield return child; - } - } + return Children().Where(predicate); } - public Element Child(string tagName, string? namespaceURI) + public Element Child(string tagName, string? namespaceURI = default) { Require.NotNull(tagName); - return this.Children(x => x.TagName == tagName && x.Prefix == null + return this.Children(x => x.TagName == tagName && namespaceURI == null || (x.Prefix == null ? x.GetNamespace() == namespaceURI - : x.GetNamespace(x.Prefix) == namespaceURI) + : x.GetNamespace(x.Prefix) == namespaceURI)) .FirstOrDefault(); } @@ -441,19 +497,7 @@ public void SetTag(string tagName) this.AddChild(new Element(tagName)); } - public void SetTag(string tagName, object? value) - { - Require.NotNullOrWhiteSpace(tagName); - - var elem = new Element(tagName); - - if (value != null) - elem.Value = Convert.ToString(value, CultureInfo.InvariantCulture); - - this.AddChild(elem); - } - - public void SetTag(string tagName, string? namespaceURI, object? value) + public void SetTag(string tagName, string? namespaceURI = default, object? value = default) { Require.NotNullOrWhiteSpace(tagName); @@ -476,4 +520,20 @@ public bool HasTag(string tagName, string? namespaceURI = default) Require.NotNullOrWhiteSpace(tagName); return this.Child(tagName, namespaceURI) is not null; } + + public void ReplaceWith(Element other) + { + Require.NotNull(other); + + var parent = this.Parent; + this.Remove(); + parent?.AddChild(other); + } + + public void ReplaceFrom(ref Element other) + { + Require.NotNull(other); + other.Remove(); + other = this; + } } diff --git a/XmppSharp/Dom/Node.cs b/XmppSharp/Dom/Node.cs index c7f52ca..d0994ce 100644 --- a/XmppSharp/Dom/Node.cs +++ b/XmppSharp/Dom/Node.cs @@ -28,7 +28,7 @@ public virtual string Value set; } - public abstract void WriteTo(XmlWriter writer); + public abstract void WriteTo(XmlWriter writer, in XmlFormatting formatting); public abstract Node Clone(); object ICloneable.Clone() => this.Clone(); diff --git a/XmppSharp/Dom/Text.cs b/XmppSharp/Dom/Text.cs index caf37d6..fcfc9dd 100644 --- a/XmppSharp/Dom/Text.cs +++ b/XmppSharp/Dom/Text.cs @@ -12,6 +12,9 @@ public class Text : Node public override Node Clone() => new Text(this); - public override void WriteTo(XmlWriter writer) - => writer.WriteString(this.Value); + public override void WriteTo(XmlWriter writer, in XmlFormatting formatting) + { + if (formatting.IncludeTextNodes) + writer.WriteString(this.Value); + } } diff --git a/XmppSharp/Jid.cs b/XmppSharp/Jid.cs index ab60b61..eaff5ec 100644 --- a/XmppSharp/Jid.cs +++ b/XmppSharp/Jid.cs @@ -199,7 +199,9 @@ public static bool TryParse(string input, out Jid result) /// public override string ToString() { - using (StringBuilderPool.Rent(out var sb)) + StringBuilderPool.Rent(out var sb); + + try { if (this._local != null) sb.Append(this._local).Append('@'); @@ -212,6 +214,10 @@ public override string ToString() return sb.ToString(); } + finally + { + StringBuilderPool.Return(sb); + } } /// diff --git a/XmppSharp/Protocol/Base/Stanza.cs b/XmppSharp/Protocol/Base/Stanza.cs index 6c90b12..7863591 100644 --- a/XmppSharp/Protocol/Base/Stanza.cs +++ b/XmppSharp/Protocol/Base/Stanza.cs @@ -1,6 +1,6 @@ namespace XmppSharp.Protocol.Base; -public abstract class Stanza : Element +public abstract class Stanza : DirectionalElement { protected Stanza(string qualifiedName) : base(qualifiedName) { diff --git a/XmppSharp/Protocol/Base/StanzaError.cs b/XmppSharp/Protocol/Base/StanzaError.cs index 45c13b9..ed82be1 100644 --- a/XmppSharp/Protocol/Base/StanzaError.cs +++ b/XmppSharp/Protocol/Base/StanzaError.cs @@ -2,10 +2,10 @@ namespace XmppSharp.Protocol.Base; -[XmppTag("error", "jabber:client")] -[XmppTag("error", "jabber:server")] -[XmppTag("error", "jabber:component:accept")] -[XmppTag("error", "jabber:component:connect")] +[XmppTag("error", Namespace.Client)] +[XmppTag("error", Namespace.Server)] +[XmppTag("error", Namespace.Accept)] +[XmppTag("error", Namespace.Connect)] public class StanzaError : Element { public StanzaError() : base("error", Namespace.Client) @@ -29,9 +29,9 @@ public StanzaErrorCondition? Condition { get { - foreach (var (tag, value) in XmppEnum.GetValues()) + foreach (var (name, value) in XmppEnum.GetValues()) { - if (this.HasTag(Namespace.Stanzas + tag)) + if (this.HasTag(name, Namespace.Stanzas)) return value; } @@ -40,10 +40,10 @@ public StanzaErrorCondition? Condition set { XmppEnum.GetNames() - .ForEach(tag => this.RemoveTag(Namespace.Stanzas + tag)); + .ForEach(name => this.RemoveTag(name, Namespace.Stanzas)); if (value.TryGetValue(out var result)) - this.SetTag(Namespace.Stanzas + result.ToXmppName()); + this.SetTag(result.ToXmppName(), Namespace.Stanzas); } } diff --git a/XmppSharp/Protocol/Base/StreamError.cs b/XmppSharp/Protocol/Base/StreamError.cs index 368783a..4b979cc 100644 --- a/XmppSharp/Protocol/Base/StreamError.cs +++ b/XmppSharp/Protocol/Base/StreamError.cs @@ -24,7 +24,7 @@ public StreamErrorCondition Condition { foreach (var (name, value) in XmppEnum.GetValues()) { - if (this.HasTag(Namespace.Streams + name)) + if (this.HasTag(name, Namespace.Streams)) return value; } @@ -32,10 +32,10 @@ public StreamErrorCondition Condition } set { - this.RemoveTag(Namespace.Streams + this.Condition.ToXmppName()); + this.RemoveTag(this.Condition.ToXmppName(), Namespace.Streams); if (XmppEnum.IsDefined(value)) - this.SetTag(Namespace.Streams + value.ToXmppName()); + this.SetTag(value.ToXmppName(), Namespace.Streams); } } diff --git a/XmppSharp/Protocol/Base/StreamFeatures.cs b/XmppSharp/Protocol/Base/StreamFeatures.cs index 2634a26..2abe0e4 100644 --- a/XmppSharp/Protocol/Base/StreamFeatures.cs +++ b/XmppSharp/Protocol/Base/StreamFeatures.cs @@ -47,8 +47,10 @@ public bool SupportsBind get => this.HasTag("bind", Namespace.Bind); set { - Action fn = !value ? this.RemoveTag : this.SetTag; - fn("bind", Namespace.Bind); + if (!value) + this.RemoveTag("bind", Namespace.Bind); + else + this.SetTag("bind", Namespace.Bind); } } @@ -57,8 +59,10 @@ public bool SupportsSession get => this.HasTag("session", Namespace.Session); set { - Action fn = !value ? this.RemoveTag : this.SetTag; - fn("session", Namespace.Session); + if (!value) + this.RemoveTag("session", Namespace.Session); + else + this.SetTag("session", Namespace.Session); } } } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StreamStream.cs b/XmppSharp/Protocol/Base/StreamStream.cs index d60ed11..fe0d8af 100644 --- a/XmppSharp/Protocol/Base/StreamStream.cs +++ b/XmppSharp/Protocol/Base/StreamStream.cs @@ -3,7 +3,7 @@ namespace XmppSharp.Protocol.Base; [XmppTag("stream:stream", "http://etherx.jabber.org/streams")] -public class StreamStream : Element +public class StreamStream : DirectionalElement { public StreamStream() : base("stream:stream", Namespace.Stream) { diff --git a/XmppSharp/Protocol/Client/Bind.cs b/XmppSharp/Protocol/Client/Bind.cs index bd6d739..5e4ecf8 100644 --- a/XmppSharp/Protocol/Client/Bind.cs +++ b/XmppSharp/Protocol/Client/Bind.cs @@ -16,7 +16,7 @@ public Bind(string resource) : this() public Bind(Jid jid) : this() => this.Jid = jid; - public string Resource + public string? Resource { get => this.GetTag("resource"); set @@ -24,11 +24,11 @@ public string Resource if (value == null) this.RemoveTag("resource"); else - this.SetTag("resource", value); + this.SetTag("resource", value: value); } } - public Jid Jid + public Jid? Jid { get { @@ -44,7 +44,7 @@ public Jid Jid if (value == null) this.RemoveTag("jid"); else - this.SetTag("jid", value.ToString()); + this.SetTag("jid", value: value.ToString()); } } } diff --git a/XmppSharp/Protocol/DataForms/Field.cs b/XmppSharp/Protocol/DataForms/Field.cs index b7d0dc0..af45887 100644 --- a/XmppSharp/Protocol/DataForms/Field.cs +++ b/XmppSharp/Protocol/DataForms/Field.cs @@ -31,7 +31,7 @@ public string? Label public string? Description { get => this.GetTag("desc"); - set => this.SetTag("desc", value); + set => this.SetTag("desc", value: value); } public bool IsRequired @@ -49,7 +49,7 @@ public bool IsRequired public new string Value { get => this.GetTag("value"); - set => this.SetTag("value", value); + set => this.SetTag("value", value: value); } public IEnumerable