Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File format revision #641

Merged
merged 10 commits into from Jun 20, 2022
210 changes: 193 additions & 17 deletions backend/Origam.Common/Extensions/XmlExtensions.cs
Expand Up @@ -31,6 +31,178 @@ namespace Origam.Extensions
{
public static class XmlExtensions
{
private class XmlnsIndentedWriter : XmlWriter
{
// WriteStartDocument is skipped, so we start on root
private bool isRootElement = true;
private int indentLevel = -1;
private readonly Stream stream;
private readonly XmlWriter baseWriter;
private XmlnsIndentedWriter(
Stream output, XmlWriter baseWriter)
{
stream = output;
this.baseWriter = baseWriter;
}
public new static XmlWriter Create(
Stream stream, XmlWriterSettings settings)
{
var writer = XmlWriter.Create(stream, settings);
return new XmlnsIndentedWriter(stream, writer);
}

private void WriteRawText(string text)
{
baseWriter.Flush();
var buffer = baseWriter.Settings.Encoding.GetBytes(text);
stream.Write(buffer, 0, buffer.Length);
}
#region XmlWriter implementation
public override WriteState WriteState => baseWriter.WriteState;

public override void Flush()
{
baseWriter.Flush();
}

public override string LookupPrefix(string ns)
{
return baseWriter.LookupPrefix(ns);
}

public override void WriteBase64(
byte[] buffer, int index, int count)
{
baseWriter.WriteBase64(buffer, index, count);
}

public override void WriteCData(string text)
{
baseWriter.WriteCData(text);
}

public override void WriteCharEntity(char ch)
{
baseWriter.WriteCharEntity(ch);
}

public override void WriteChars(char[] buffer, int index, int count)
{
baseWriter.WriteChars(buffer, index, count);
}

public override void WriteComment(string text)
{
baseWriter.WriteComment(text);
}

public override void WriteDocType(
string name, string pubid, string sysid, string subset)
{
baseWriter.WriteDocType(name, pubid, sysid, subset);
}

public override void WriteEndAttribute()
{
if (indentLevel >= 0)
{
WriteRawText(
baseWriter.Settings.NewLineChars
+ new string(' ', indentLevel));
}
baseWriter.WriteEndAttribute();
}

public override void WriteEndDocument()
{
baseWriter.WriteEndDocument();
}

public override void WriteEndElement()
{
baseWriter.WriteEndElement();
}

public override void WriteEntityRef(string name)
{
baseWriter.WriteEntityRef(name);
}

public override void WriteFullEndElement()
{
baseWriter.WriteFullEndElement();
}

public override void WriteProcessingInstruction(
string name, string text)
{
baseWriter.WriteProcessingInstruction(name, text);
}

public override void WriteRaw(char[] buffer, int index, int count)
{
baseWriter.WriteRaw(buffer, index, count);
}

public override void WriteRaw(string data)
{
baseWriter.WriteRaw(data);
}

public override void WriteStartAttribute(
string prefix, string localName, string ns)
{
baseWriter.WriteStartAttribute(prefix, localName, ns);
}

public override void WriteStartDocument()
{
baseWriter.WriteStartDocument();
}

public override void WriteStartDocument(bool standalone)
{
baseWriter.WriteStartDocument(standalone);
}

public override void WriteStartElement(
string prefix, string localName, string ns)
{
if (isRootElement)
{
if (indentLevel < 0)
{
// initialize the indent level;
indentLevel = 1;
}
else
{
// do not track indent for the whole document;
// when second element starts, we are done
isRootElement = false;
indentLevel = -1;
}
}
baseWriter.WriteStartElement(prefix, localName, ns);
}

public override void WriteString(string text)
{
baseWriter.WriteString(text);
}

public override void WriteSurrogateCharEntity(
char lowChar, char highChar)
{
baseWriter.WriteSurrogateCharEntity(lowChar, highChar);
}

public override void WriteWhitespace(string ws)
{
baseWriter.WriteWhitespace(ws);
}
#endregion
}
public static IEnumerable<XmlNode> GetAllNodes(this XmlNode topNode)
{
foreach (var node in topNode.ChildNodes)
Expand Down Expand Up @@ -60,20 +232,21 @@ public static int GetDepth(this XmlNode node)
public static string ToBeautifulString(this XmlDocument document,
XmlWriterSettings xmlWriterSettings)
{
MemoryStream mStream = new MemoryStream();
XmlWriter writer = XmlWriter.Create(mStream,xmlWriterSettings);
var memoryStream = new MemoryStream();
var writer = XmlnsIndentedWriter.Create(
memoryStream, xmlWriterSettings);
try
{
document.WriteContentTo(writer);
writer.Flush();
mStream.Flush();
mStream.Position = 0;
StreamReader sReader = new StreamReader(mStream);
return sReader.ReadToEnd();
memoryStream.Flush();
memoryStream.Position = 0;
var streamReader = new StreamReader(memoryStream);
return streamReader.ReadToEnd();
}
finally
{
mStream.Close();
memoryStream.Close();
}
}

Expand Down Expand Up @@ -101,14 +274,15 @@ public static string ToBeautifulString(this XmlDocument document)
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true
};
NewLineOnAttributes = true,
};
xmlWriterSettings.DoNotEscapeUriAttributes = false;
return ToBeautifulString(document, xmlWriterSettings);
}

public static string ToBeautifulString(this XDocument document)
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings
var xmlWriterSettings = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true
Expand All @@ -125,11 +299,13 @@ public static string ToBeautifulString(this XDocument document)
{
att.OwnerElement.RemoveAttributeNode(att);
}
if (!string.IsNullOrEmpty(doc.OuterXml))
if (string.IsNullOrEmpty(doc.OuterXml))
{
var elements = XDocument.Parse(doc.OuterXml);
elements.Descendants().Where(e => e.IsEmpty || string.IsNullOrWhiteSpace(e.Value)).Remove();
return doc;
}
var elements = XDocument.Parse(doc.OuterXml);
elements.Descendants().Where(
e => e.IsEmpty || string.IsNullOrWhiteSpace(e.Value)).Remove();
#endif
return doc;
}
Expand All @@ -143,11 +319,11 @@ public static XDocument ToXDocument(this XmlDocument xmlDocument)
}
}

public static bool AttributeIsFalseOrMissing(this XmlElement element, string attributeName)
public static bool AttributeIsFalseOrMissing(
this XmlElement element, string attributeName)
{
string value = element.GetAttribute(attributeName);
return value == "false" ||
string.IsNullOrEmpty(value);
var value = element.GetAttribute(attributeName);
return value == "false" || string.IsNullOrEmpty(value);
}
}
}
2 changes: 1 addition & 1 deletion backend/Origam.Common/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/Origam.Common/Strings.resx
Expand Up @@ -163,7 +163,7 @@
<value>Cannot read OrigamSettings.config... </value>
</data>
<data name="ShortGnu" xml:space="preserve">
<value>{0} Copyright (C) 2021 Advantage Solutions, s.r.o
<value>{0} Copyright (C) 2022 Advantage Solutions, s.r.o
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions.</value>
Expand Down
56 changes: 42 additions & 14 deletions backend/Origam.DA.Service/OrigamDocumentSorter.cs
Expand Up @@ -19,6 +19,7 @@
*/
#endregion

using System.Collections.Generic;
using System.Linq;
using System.Xml;
using MoreLinq;
Expand All @@ -27,40 +28,67 @@ namespace Origam.DA.Service
{
public static class OrigamDocumentSorter
{
private class XmlnsComparer : IComparer<string>
{
private const string XmlnsX = "xmlns:x";
public int Compare(string x, string y)
{
if ((x == XmlnsX) && (y == XmlnsX))
{
return 0;
}
if (x == XmlnsX)
{
return 1;
}
if (y == XmlnsX)
{
return -1;
}
return string.Compare(x, y);
}
}
public static XmlDocument CopyAndSort(OrigamXmlDocument doc)
{
var nameSpaceInfo = NameSpaceInfo.Create(doc);
var nameSpaceInfo = NamespaceInfo.Create(doc);
var newDoc = new OrigamXmlDocument();
doc.FileElement.Attributes
.Cast<XmlAttribute>()
.OrderBy(attribute => attribute.Value)
.OrderBy(
attribute => attribute.Name,
new XmlnsComparer(),
OrderByDirection.Ascending)
.ForEach(attribute =>
newDoc.FileElement.SetAttribute(attribute.Name, attribute.Value));

doc.ChildNodes
.Cast<XmlNode>()
.ForEach(node => CopyNodes(node, newDoc.FileElement, newDoc, nameSpaceInfo));
return newDoc;
}

private static void CopyNodes(XmlNode node, XmlElement targetNode, OrigamXmlDocument newDoc, NameSpaceInfo nameSpaceInfo)
private static void CopyNodes(
XmlNode node,
XmlElement targetNode,
OrigamXmlDocument newDoc,
NamespaceInfo namespaceInfo)
{
string fullName = nameSpaceInfo.AbstractSchemaPrefix + ":name";
string fullId = nameSpaceInfo.PersistencePrefix + ":id";
var fullId = namespaceInfo.PersistencePrefix + ":id";
CopyAttributes(node, targetNode);
node.ChildNodes
.Cast<XmlNode>()
.OrderBy(childNode => childNode.LocalName)
.ThenBy(childNode => childNode.Attributes?[fullName]?.Value ?? "zzzzzzzz")
.ThenBy(childNode => childNode.Attributes?[fullId]?.Value ?? "zzzzzzzz")
.OrderBy(childNode => childNode.Prefix + childNode.LocalName)
.ThenBy(childNode
=> childNode.Attributes?[fullId]?.Value
?? childNode.Attributes?["id"]?.Value
?? "zzzzzzzz")
.ForEach(childNode =>
{
var xmlns = string.IsNullOrEmpty(childNode.NamespaceURI)
? newDoc.FileElement.Attributes["xmlns"].Value
: childNode.NamespaceURI;
XmlElement childCopy = newDoc.CreateElement(childNode.Name, xmlns);
var childCopy = newDoc.CreateElement(childNode.Name, xmlns);
targetNode.AppendChild(childCopy);
CopyNodes(childNode, childCopy, newDoc, nameSpaceInfo);
CopyNodes(childNode, childCopy, newDoc, namespaceInfo);
});
}

Expand All @@ -77,13 +105,13 @@ private static void CopyAttributes(XmlNode childNode, XmlElement childCopy)
}
}

class NameSpaceInfo
class NamespaceInfo
{
public static NameSpaceInfo Create(OrigamXmlDocument doc)
public static NamespaceInfo Create(OrigamXmlDocument doc)
{
var xmlAttributes = doc.FileElement?.Attributes?
.Cast<XmlAttribute>();
return new NameSpaceInfo
return new NamespaceInfo
{
PersistencePrefix = xmlAttributes
?.FirstOrDefault(attr => attr.Value.StartsWith(
Expand Down