Skip to content

Commit

Permalink
Update project.
Browse files Browse the repository at this point in the history
XMPP# - Core

- Minor improvements.
- Fixed wrong indent chars & side for default formatting options.
- Fixed `Element.Value` returning entire inner text from all descendant nodes.

XMPP# - Expat

- Minor improvements.
- Better namespace handler (using namespace stack to track different XML scopes).
- All tests passing.
  • Loading branch information
nathan130200 committed May 7, 2024
1 parent 298e3d5 commit 33a5410
Show file tree
Hide file tree
Showing 13 changed files with 520 additions and 46 deletions.
20 changes: 18 additions & 2 deletions XmppSharp.Expat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# XMPP# Expat
This package implements the Expat parser in Xmpp Sharp library.

# Common Types
- `ExpatXmppParser`
[![github](https://img.shields.io/badge/XMPP%23_%20Expat-ffe000?style=flat-square&logo=github&label=Github)](https://github.com/nathan130200/XmppSharp)

### Common Types
- `ExpatXmppParser`

### Version History

*1.0.0*

- Initial Release

___

*1.0.1*

- Minor improvements.
- Better namespace handler (using namespace stack to track different XML scopes).
- All tests passing.
127 changes: 112 additions & 15 deletions XmppSharp.Expat/ExpatXmppParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Expat;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Expat;
using XmppSharp.Dom;
using XmppSharp.Exceptions;
using XmppSharp.Factory;
Expand All @@ -9,31 +12,57 @@ namespace XmppSharp.Parsers;
/// <summary>
/// An enhanced XMPP parser built using Expat library.
/// </summary>
public class ExpatXmppParser : BaseXmppParser
public partial class ExpatXmppParser : BaseXmppParser
{
private Parser _parser;
private Element _currentElem;
private XmlNamespaceManager _nsStack;
private NameTable _xmlNames;

void AddNamespacesToScope(IReadOnlyDictionary<string, string> attrs)
{
foreach (var (key, value) in attrs)
{
if (key == "xmlns")
this._nsStack.AddNamespace(string.Empty, value);
else if (key.StartsWith("xmlns:"))
{
var prefix = key[(key.IndexOf(':') + 1)..];
this._nsStack.AddNamespace(prefix, value);
}
}
}

public ExpatXmppParser(ExpatEncodingType encoding = ExpatEncodingType.Utf8)
{
this._nsStack = new(this._xmlNames = new NameTable());

this._parser = new Parser(encoding);

this._parser.OnElementStart += e =>
{
var entity = Xml.ExtractQualifiedName(e.Name);
this._nsStack.PushScope();
string ns;
AddNamespacesToScope(e.Attributes);
if (entity.HasPrefix)
e.Attributes.TryGetValue($"xmlns:{entity.Prefix}", out ns);
else
e.Attributes.TryGetValue("xmlns", out ns);
var qname = Xml.ExtractQualifiedName(e.Name);
var ns = this._nsStack.LookupNamespace(qname.HasPrefix ? qname.Prefix : string.Empty);
if (e.Name is "iq" or "message" or "presence") // work-around
ns ??= Namespace.Client;
var element = ElementFactory.Create(e.Name, ns);
//foreach (var (key, value) in _nsStack.GetNamespacesInScope(XmlNamespaceScope.Local))
//{
// var att = string.IsNullOrWhiteSpace(key) ? "xmlns" : $"xmlns:{key}";
// element.SetAttribute(att, value);
//}
foreach (var (key, value) in e.Attributes)
element.SetAttribute(key, value);
if (e.Name == "stream:stream")
AsyncHelper.RunSync(() => FireStreamStart(element as StreamStream));
else
Expand All @@ -45,6 +74,8 @@ public ExpatXmppParser(ExpatEncodingType encoding = ExpatEncodingType.Utf8)

this._parser.OnElementEnd += e =>
{
this._nsStack.PopScope();
if (e.Value == "stream:stream")
AsyncHelper.RunSync(() => FireStreamEnd());
else
Expand Down Expand Up @@ -72,10 +103,21 @@ public ExpatXmppParser(ExpatEncodingType encoding = ExpatEncodingType.Utf8)
{
if (_currentElem != null)
{
var trimWS = _currentElem.GetAttribute("xml:space") != "preserve";
// skip whitespace if not explicit declared.
if (string.IsNullOrWhiteSpace(e.Value) && trimWS)
return;
var val = e.Value;
if (trimWS) // same for trailing whitespace
val = TrimWhitespace(val);
if (_currentElem.LastNode is Text text)
text.Value += e.Value;
text.Value += val;
else
_currentElem.AddChild(new Text(e.Value));
_currentElem.AddChild(new Text(val));
}
};

Expand All @@ -90,23 +132,78 @@ public ExpatXmppParser(ExpatEncodingType encoding = ExpatEncodingType.Utf8)
};
}

[GeneratedRegex("\n")]
protected static partial Regex NewLineRegex();

[GeneratedRegex(@"\s+")]
protected static partial Regex ContiguousSpaceRegex();

static string TrimWhitespace(string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;

str = NewLineRegex().Replace(str, string.Empty);
str = str.Replace("\t", " ");
str = str.Trim();
str = ContiguousSpaceRegex().Replace(str, " ");

return str;
}

public void Reset()
{
this.EnsureNotDisposed();

while (this._nsStack.PopScope())
;

// reset namespace stack.
this._nsStack = new(this._xmlNames);

// reset the parser
this._parser.Reset();
}

public void Write(byte[] buffer, int offset = 0, int length = -1, bool isFinalBlock = false)
public void Write(byte[] buffer, int offset, int length, bool isFinalBlock = false)
{
this.EnsureNotDisposed();

byte[] temp;

try
{
temp = GC.AllocateUninitializedArray<byte>(length, true);
Buffer.BlockCopy(buffer, offset, temp, 0, length);
this._parser.Feed(temp, length, isFinalBlock);
}
finally
{
temp = null;
}
}

public void Write(byte[] buffer, int length, bool isFinalBlock = false)
{
length = length < 0 ? buffer.Length : length;
Write(buffer[offset..length], isFinalBlock);
this.EnsureNotDisposed();
this._parser.Feed(buffer, length, isFinalBlock);
}

public void Write(byte[] buffer, bool isFinalBlock = false)
=> this._parser.Feed(buffer, buffer.Length, isFinalBlock);
{
this.EnsureNotDisposed();
this._parser.Feed(buffer, buffer.Length, isFinalBlock);
}

protected override void OnDispose()
protected override void Disposing()
{
this._xmlNames = null;

while (this._nsStack.PopScope())
;

this._nsStack = null;

this._parser?.Dispose();
this._parser = null;
}
Expand Down
4 changes: 2 additions & 2 deletions XmppSharp.Expat/XmppSharp.Expat.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net7.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>annotations</Nullable>
</PropertyGroup>
Expand All @@ -26,7 +26,7 @@
</PropertyGroup>

<PropertyGroup>
<Version>1.0.0</Version>
<Version>1.0.1</Version>
<DebugSymbols>true</DebugSymbols>
<Copyright>nathan130200</Copyright>
<RepositoryType>git</RepositoryType>
Expand Down
Loading

0 comments on commit 33a5410

Please sign in to comment.