Как использовать XDocument.Save для сохранения файла с использованием настраиваемого отступа для атрибутов

Моя цель — вывести измененный файл XML и сохранить специальный отступ, который присутствовал в исходном файле. Цель состоит в том, чтобы полученный файл по-прежнему выглядел как оригинал, что упрощает их сравнение и объединение с помощью системы управления версиями.

Моя программа будет читать файл XML и добавлять или изменять один конкретный атрибут.

Вот форматирование, которое я пытаюсь достичь/сохранить:

<Base Import="..\commom\style.xml">
  <Item Width="480"
        Height="500"
        VAlign="Center"
        Style="level1header">
(...)

В этом случае я просто хочу выровнять все атрибуты после первого с первым.

XmlWriterSettings предоставляет параметры форматирования, но они не приведут к желаемому результату.

settings.Indent = true;
settings.NewLineOnAttributes = true;

Эти настройки поместят первый атрибут на новую строку вместо того, чтобы оставить его на той же строке, что и узел, и выровняют атрибуты с узлом.

Вот вызов Load, который запрашивает сохранение пробелов:

MyXml = XDocument.Load(filepath, LoadOptions.PreserveWhitespace);

Но, похоже, это не будет делать то, что я ожидал.

Я попытался предоставить собственный класс, производный от XmlWriter для вызова XDocument.Save, но мне не удалось правильно вставить пробел, не столкнувшись с InvalidOperationException. Кроме того, это решение кажется излишним для небольшого дополнения, которое я ищу.

Для справки, это мой вызов сохранения, не использующий мой собственный модуль записи xml (который все равно не работает)

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = true;
settings.OmitXmlDeclaration = true;
using (XmlWriter writer = XmlWriter.Create(filepath + "_auto", settings))
{
    MyXml.Save(writer);
}

person A.Boulianne    schedule 23.06.2017    source источник
comment
Я не думаю, что System.Xml был разработан для поддержки полностью произвольных спецификаций форматирования. Вы можете написать свою собственную реализацию XmlWriter, которая поддерживает ваш формат, или просто начать использовать готовый формат, который он поддерживает.   -  person Matti Virkkunen    schedule 23.06.2017


Ответы (1)


В итоге я вообще не использовал XDocument.Save, а вместо этого создал класс, который принимает XDocument, XmlWriter, а также TextWriter. Класс анализирует все узлы в XDocument, TextWriter привязан к файлу на диске, который XmlWriter использует в качестве выходного канала.

Затем мой класс использует XmlWriter для вывода xml. Чтобы добиться дополнительного интервала, я использовал решение, описанное здесь, https://stackoverflow.com/a/24010544/5920497. , поэтому я также использую базовый TextWriter.

Вот пример решения.

Вызов класса для сохранения документа:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = false; // Behavior changed in PrettyXmlWriter
settings.OmitXmlDeclaration = true;

using(TextWriter rawwriter = File.CreateText(filepath))
using (XmlWriter writer = XmlWriter.Create(rawwriter, settings))
{
    // rawwriter is used both by XmlWriter and PrettyXmlWriter
    PrettyXmlWriter outputter = new PrettyXmlWriter(writer, rawwriter);
    outputter.Write(MyXml);

    writer.Flush();
    writer.Close();
}

Внутри PrettyXmlWriter:

private XmlWriter Writer { get; set; }
private TextWriter InnerTextWriter { get; set; }

public void Write(XDocument doc)
{
    XElement root = doc.Root;
    WriteNode(root, 0);
}

private void WriteNode(XNode node, int currentNodeDepth)
{
    if(node.NodeType == XmlNodeType.Element)
    {
        WriteElement((XElement)node, currentNodeDepth);
    }
    else if(node.NodeType == XmlNodeType.Text)
    {
        WriteTextNode((XText)node, currentNodeDepth, doIndentAttributes);
    }
}

private void WriteElement(XElement node, int currentNodeDepth)
{
    Writer.WriteStartElement(node.Name.LocalName);

    // Write attributes with indentation
    XAttribute[] attributes = node.Attributes().ToArray();

    if(attributes.Length > 0)
    {
        // First attribute, unindented.
        Writer.WriteAttributeString(attributes[0].Name.LocalName, attributes[0].Value);

        for(int i=1; i<attributes.Length; ++i)
        {
            // Write indentation
            Writer.Flush();
            string indentation = Writer.Settings.NewLineChars + string.Concat(Enumerable.Repeat(Writer.Settings.IndentChars, currentNodeDepth));
            indentation += string.Concat(Enumerable.Repeat(" ", node.Name.LocalName.Length + 1));
            // Using Underlying TextWriter trick to output whitespace
            InnerTextWriter.Write(indentation);

            Writer.WriteAttributeString(attributes[i].Name.LocalName, attributes[i].Value);
        }
    }

    // output children
    foreach(XNode child in node.Nodes())
    {
        WriteNode(child, currentNodeDepth + 1);
    }

    Writer.WriteEndElement();
}
person A.Boulianne    schedule 28.06.2017