Redid xml to json conversion

This commit is contained in:
Boris 2024-03-10 22:36:17 +01:00
parent 4eaacaeb94
commit 8d3dfd43d4
7 changed files with 182 additions and 63 deletions

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Serialization;
namespace Jackett.Common.Helpers
{

View File

@ -0,0 +1,71 @@
using System.Linq;
using System.Xml.Linq;
using Jackett.Common.Extensions;
using Newtonsoft.Json.Linq;
namespace Jackett.Common.Helpers
{
public static class XmlToJsonConverter
{
/// <summary>
/// Converts an XML element to a JSON object.
/// Attributes are stored in an @attributes object.
/// </summary>
public static JToken XmlToJson(XElement element)
{
var obj = new JObject();
// Build @attributes object from element attributes
if (element.Attributes().Any())
{
var attributes = element.Attributes()
.ToDictionary(
attribute => GetName(attribute.Name, attribute.Parent?.Document),
attribute => (JToken)attribute.Value
);
obj.Add("@attributes", JObject.FromObject(attributes));
}
foreach (var childElement in element.Elements())
{
var childObj = XmlToJson(childElement);
var childName = GetName(childElement.Name, element.Document);
// If the child name already exists, convert it to an array
if (obj.ContainsKey(childName))
{
if (obj[childName] is JArray existingArray)
{
existingArray.Add(childObj);
}
else
{
obj[childName] = new JArray(obj[childName]!, childObj);
}
}
else
{
obj.Add(childName, childObj);
}
}
if (!element.Elements().Any() && !element.Value.IsNullOrWhiteSpace())
{
return element.Value;
}
return obj;
}
private static string GetName(XName name, XDocument document)
{
if (document == null)
{
return name.LocalName;
}
var prefix = document.Root?.GetPrefixOfNamespace(name.Namespace);
return prefix != null ? $"{prefix}:{name.LocalName}" : name.LocalName;
}
}
}

View File

@ -1,29 +0,0 @@
using System.IO;
using Newtonsoft.Json;
namespace Jackett.Common.Helpers
{
/// <summary>
/// JsonTextWriter to convert XML to JSON.
/// Makes sure that XML attributes do not have the '@' or '#' prefix in the JSON output.
/// https://stackoverflow.com/a/43485727
/// </summary>
public class XmlToJsonWriter : JsonTextWriter
{
public XmlToJsonWriter(TextWriter textWriter) : base(textWriter)
{
}
public override void WritePropertyName(string name)
{
if (name.StartsWith("@") || name.StartsWith("#"))
{
base.WritePropertyName(name.Substring(1));
}
else
{
base.WritePropertyName(name);
}
}
}
}

View File

@ -5,6 +5,8 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using Jackett.Common.Helpers;
using Newtonsoft.Json;
namespace Jackett.Common.Models
{
@ -52,7 +54,7 @@ namespace Jackett.Common.Models
return new XElement(_TorznabNs + "attr", new XAttribute("name", name), new XAttribute("value", value));
}
public string ToXml(Uri selfAtom)
private XDocument GetXDocument(Uri selfAtom)
{
// IMPORTANT: We can't use Uri.ToString(), because it generates URLs without URL encode (links with unicode
// characters are broken). We must use Uri.AbsoluteUri instead that handles encoding correctly
@ -127,7 +129,19 @@ namespace Jackett.Common.Models
)
);
return xdoc;
}
public string ToXml(Uri selfAtom)
{
var xdoc = GetXDocument(selfAtom);
return xdoc.Declaration + Environment.NewLine + xdoc;
}
public string ToJson(Uri selfAtom, JsonSerializerSettings serializerSettings = null)
{
var jsonObject = XmlToJsonConverter.XmlToJson(GetXDocument(selfAtom).Root);
return JsonConvert.SerializeObject(jsonObject, serializerSettings);
}
}
}

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Jackett.Common.Helpers;
using Newtonsoft.Json;
@ -358,15 +356,8 @@ namespace Jackett.Common.Models
public string ToJson(JsonSerializerSettings serializerSettings = null)
{
var stringBuilder = new StringBuilder();
var serializer = JsonSerializer.Create(serializerSettings);
using var stringWriter = new StringWriter(stringBuilder);
using var jsonWriter = new XmlToJsonWriter(stringWriter);
serializer.Serialize(jsonWriter, GetXDocument().Root);
return stringBuilder.ToString();
var jsonObject = XmlToJsonConverter.XmlToJson(GetXDocument().Root);
return JsonConvert.SerializeObject(jsonObject, serializerSettings);
}
public static TorznabCapabilities Concat(TorznabCapabilities lhs, TorznabCapabilities rhs)

View File

@ -359,7 +359,7 @@ namespace Jackett.Server.Controllers
if (string.Equals(CurrentQuery.QueryType, "caps", StringComparison.InvariantCultureIgnoreCase))
{
return CurrentQuery.IsJson ?
(IActionResult)Json(CurrentIndexer.TorznabCaps.ToJson(_JsonSerializerSettings)) :
Content(CurrentIndexer.TorznabCaps.ToJson(_JsonSerializerSettings), "application/json", Encoding.UTF8) :
Content(CurrentIndexer.TorznabCaps.ToXml(), "application/rss+xml", Encoding.UTF8);
}
@ -378,24 +378,6 @@ namespace Jackett.Server.Controllers
else if (string.Equals(request.configured, "false", StringComparison.InvariantCultureIgnoreCase))
indexers = indexers.Where(i => !i.IsConfigured);
if (CurrentQuery.IsJson)
{
return Json(new JObject
{
["indexers"] = JArray.FromObject(indexers.Select(i => new
{
id = i.Id,
configured = i.IsConfigured,
title = i.Name,
description = i.Description,
link = i.SiteLink,
language = i.Language,
type = i.Type,
caps = i.TorznabCaps
}))
});
}
var xdoc = new XDocument(
new XDeclaration("1.0", "UTF-8", null),
new XElement("indexers",
@ -413,6 +395,11 @@ namespace Jackett.Server.Controllers
)
);
if (CurrentQuery.IsJson)
{
return Json(XmlToJsonConverter.XmlToJson(xdoc.Root), _JsonSerializerSettings);
}
return Content(xdoc.Declaration.ToString() + Environment.NewLine + xdoc.ToString(), "application/xml", Encoding.UTF8);
}
@ -502,12 +489,14 @@ namespace Jackett.Server.Controllers
else
logger.Info($"Torznab search in {CurrentIndexer.Name} for {CurrentQuery.GetQueryString()} => Found {result.Releases.Count()} releases{cacheStr} [{stopwatch.ElapsedMilliseconds:0}ms]");
var serverUri = new Uri(serverUrl);
if (CurrentQuery.IsJson)
{
return Json(resultPage);
var json = resultPage.ToJson(serverUri);
return Content(json, "application/json", Encoding.UTF8);
}
var xml = resultPage.ToXml(new Uri(serverUrl));
var xml = resultPage.ToXml(serverUri);
// Force the return as XML
return Content(xml, "application/rss+xml", Encoding.UTF8);

View File

@ -0,0 +1,83 @@
using System.Xml.Linq;
using Jackett.Common.Helpers;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Jackett.Test.Common.Helpers
{
[TestFixture]
public class XmlToJsonConverterTests
{
[Test]
public void XmlToJsonConverter_XmlToJson_Empty()
{
var element = new XElement("test");
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithAttributes()
{
var element = new XElement("test", new XAttribute("attr1", "value1"), new XAttribute("attr2", "value2"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"@attributes\":{\"attr1\":\"value1\",\"attr2\":\"value2\"}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElement()
{
var element = new XElement("test", new XElement("child"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":{}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElement_WithAttributes()
{
var element = new XElement("test", new XElement("child", new XAttribute("attr1", "value1"), new XAttribute("attr2", "value2")));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":{\"@attributes\":{\"attr1\":\"value1\",\"attr2\":\"value2\"}}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElement_WithText()
{
var element = new XElement("test", new XElement("child", new XElement("subchild", "text")));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":{\"subchild\":\"text\"}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElementArray()
{
var element = new XElement("test", new XElement("child"), new XElement("child"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":[{},{}]}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElementArray_WithText()
{
var element = new XElement("test", new XElement("child", "text1"), new XElement("child", "text2"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":[\"text1\",\"text2\"]}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_HandlesNamespaces()
{
var document = new XDocument(
new XElement("root",
new XAttribute(XNamespace.Xmlns + "ns1", "http://example.com/ns1"),
new XAttribute(XNamespace.Xmlns + "ns2", "http://example.com/ns2"),
new XElement("{http://example.com/ns1}child1"),
new XElement("{http://example.com/ns2}child2")
)
);
var json = XmlToJsonConverter.XmlToJson(document.Root);
Assert.AreEqual("{\"@attributes\":{\"xmlns:ns1\":\"http://example.com/ns1\",\"xmlns:ns2\":\"http://example.com/ns2\"},\"ns1:child1\":{},\"ns2:child2\":{}}", json.ToString(Formatting.None));
}
}
}