Какой самый быстрый способ программной проверки правильности XML-файлов в C#?

У меня есть большие партии файлов XHTML, которые обновляются вручную. Во время фазы обзора обновлений я хотел бы программно проверить правильность файлов. В настоящее время я использую XmlReader, но время, необходимое для среднего процессора, намного больше, чем я ожидал.

Размер файлов XHTML варьируется от 4 КБ до 40 КБ, и проверка занимает несколько секунд на файл. Проверка важна, но я бы хотел, чтобы время было как можно короче, поскольку проверка выполняется во время чтения файлов на следующем шаге процесса.

Есть ли более быстрый способ сделать простую проверку правильности XML? Может быть, с использованием внешних библиотек XML?


Я могу подтвердить, что проверка "обычного" содержимого на основе XML молниеносно выполняется с помощью XmlReader, и, как предполагается, проблема, по-видимому, связана с тем, что DTD XHTML читается при каждой проверке файла.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Обратите внимание, что в дополнение к DTD также загружаются соответствующие файлы.ent (xhtml-lat1.ent, xhtml-symbol.ent, xhtml-special.ent).

Поскольку полное игнорирование DTD на самом деле не вариант для XHTML, поскольку правильная формация тесно связана с допустимыми объектами HTML (например, & nbsp; будет быстро вводить ошибки проверки, когда мы игнорируем DTD).


Проблема была решена с помощью пользовательского XmlResolver, как предложено, в сочетании с локальными (встроенными) копиями файлов DTD и сущностей.

Я опубликую решение здесь, как только я очистил код

6 ответов

Решение

Я ожидаю, что XmlReader с while(reader.Read)() {} будет самый быстрый управляемый подход. Конечно, не нужно считывать секунды, чтобы прочитать 40 КБ... какой метод ввода вы используете?

Возможно, у вас есть какие-то внешние (схема и т. Д.) Сущности для разрешения? Если это так, вы можете написать XmlResolver (устанавливается через XmlReaderSettings) который использует локально кэшированные схемы, а не удаленную выборку...

Следующее делает ~300KB практически мгновенно:

    using(MemoryStream ms = new MemoryStream()) {
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.CloseOutput = false;
        using (XmlWriter writer = XmlWriter.Create(ms, settings))
        {
            writer.WriteStartElement("xml");
            for (int i = 0; i < 15000; i++)
            {
                writer.WriteElementString("value", i.ToString());
            }
            writer.WriteEndElement();
        }
        Console.WriteLine(ms.Length + " bytes");
        ms.Position = 0;
        int nodes = 0;
        Stopwatch watch = Stopwatch.StartNew();
        using (XmlReader reader = XmlReader.Create(ms))
        {
            while (reader.Read()) { nodes++; }
        }
        watch.Stop();
        Console.WriteLine("{0} nodes in {1}ms", nodes,
            watch.ElapsedMilliseconds);
    }

Создать XmlReader объект, передавая в XmlReaderSettings объект, который имеет ConformanceLevel.Document,

Это подтвердит правильность формирования.

Эта статья MSDN должна объяснить детали.

На моем довольно обычном ноутбуке чтение документа XML объемом 250 Кб от начала до конца с XmlReader занимает 6 миллисекунд. Кое-что еще, кроме простого разбора XML, является виновником.

Я знаю, что я пишу некро, но я думаю, что это может быть решением

  1. используйте HTML Tidy, чтобы очистить ваш xml. установить опцию, чтобы удалить тип документа
  2. затем прочитайте сгенерированный xhtml/xml из tidy.

вот тот же код

public void GetDocumentStructure(int documentID)
    {
        string scmRepoPath = ConfigurationManager.AppSettings["SCMRepositoryFolder"];
        string docFilePath = scmRepoPath + "\\" + documentID.ToString() + ".xml";

        string docFilePath2 = scmRepoPath + "\\" + documentID.ToString() + "_clean.xml";

        Tidy tidy = new Tidy();
        tidy.Options.MakeClean = true;
        tidy.Options.NumEntities = true;
        tidy.Options.Xhtml = true;
        // this option removes the DTD on the generated output of Tidy
        tidy.Options.DocType = DocType.Omit;

        FileStream input = new FileStream(docFilePath, FileMode.Open);            
        MemoryStream output = new MemoryStream();
        TidyMessageCollection msgs = new TidyMessageCollection();
        tidy.Parse(input, output, msgs);            
        output.Seek(0, SeekOrigin.Begin);

        XmlReader rd = XmlReader.Create(output);            
        int node = 0;

        System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
        while (rd.Read())
        {                
            ++node;                
        }
        watch.Stop();

        Console.WriteLine("Duration was : " + watch.Elapsed.ToString());
    }

Лично я довольно ленивый... поэтому я ищу библиотеки.NET, которые уже решают проблему. Попробуйте использовать DataSet.ReadXML() функционировать и ловить исключения. Это делает довольно удивительную работу по объяснению ошибок формата XML.

Как уже упоминалось, узким местом, скорее всего, является не XmlReader.

Проверьте, не случится ли вам много конкатенации строк без строителя строк.

Это действительно может нанести удар по вашей производительности.

Я использую эту функцию для проверки строк / фрагментов

<Runtime.CompilerServices.Extension()>
Public Function IsValidXMLFragment(ByVal xmlFragment As String, Optional Strict As Boolean = False) As Boolean
    IsValidXMLFragment = True

    Dim NameTable As New Xml.NameTable

    Dim XmlNamespaceManager As New Xml.XmlNamespaceManager(NameTable)
    XmlNamespaceManager.AddNamespace("xsd", "http://www.w3.org/2001/XMLSchema")
    XmlNamespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")

    Dim XmlParserContext As New Xml.XmlParserContext(Nothing, XmlNamespaceManager, Nothing, Xml.XmlSpace.None)

    Dim XmlReaderSettings As New Xml.XmlReaderSettings
    XmlReaderSettings.ConformanceLevel = Xml.ConformanceLevel.Fragment
    XmlReaderSettings.ValidationType = Xml.ValidationType.Schema
    If Strict Then
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ProcessInlineSchema)
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ReportValidationWarnings)
    Else
        XmlReaderSettings.ValidationFlags = XmlSchemaValidationFlags.None
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.AllowXmlAttributes)
    End If

    AddHandler XmlReaderSettings.ValidationEventHandler, Sub() IsValidXMLFragment = False
    AddHandler XmlReaderSettings.ValidationEventHandler, AddressOf XMLValidationCallBack

    Dim XmlReader As Xml.XmlReader = Xml.XmlReader.Create(New IO.StringReader(xmlFragment), XmlReaderSettings, XmlParserContext)
    While XmlReader.Read
        'Read entire XML
    End While
End Function

Я использую эту функцию для проверки файлов:

Public Function IsValidXMLDocument(ByVal Path As String, Optional Strict As Boolean = False) As Boolean
    IsValidXMLDocument = IO.File.Exists(Path)
    If Not IsValidXMLDocument Then Exit Function

    Dim XmlReaderSettings As New Xml.XmlReaderSettings
    XmlReaderSettings.ConformanceLevel = Xml.ConformanceLevel.Document
    XmlReaderSettings.ValidationType = Xml.ValidationType.Schema
    If Strict Then
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ProcessInlineSchema)
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ReportValidationWarnings)
    Else
        XmlReaderSettings.ValidationFlags = XmlSchemaValidationFlags.None
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.AllowXmlAttributes)
    End If
    XmlReaderSettings.CloseInput = True

    AddHandler XmlReaderSettings.ValidationEventHandler, Sub() IsValidXMLDocument = False
    AddHandler XmlReaderSettings.ValidationEventHandler, AddressOf XMLValidationCallBack

    Using FileStream As New IO.FileStream(Path, IO.FileMode.Open)
        Using XmlReader As Xml.XmlReader = Xml.XmlReader.Create(FileStream, XmlReaderSettings)
            While XmlReader.Read
                'Read entire XML
            End While
        End Using
    End Using
End Function
Другие вопросы по тегам