Статьи

Проверка XML против XSD в Java

Существует множество инструментов для проверки XML-документа на соответствие XSD . К ним относятся скрипты и инструменты операционной системы, такие как xmllint , редакторы XML и IDE, и даже онлайн-валидаторы. Я нашел полезным иметь свой собственный простой в использовании инструмент проверки XML из-за ограничений или проблем ранее упомянутых подходов. Java облегчает написание такого инструмента, и этот пост демонстрирует, как легко разработать простой инструмент проверки XML в Java.

Инструмент Java, разработанный в этом посте, требует JDK 8. Однако простое приложение Java можно довольно легко изменить для работы с JDK 7 или даже с такой старой версией Java, как JDK 5. В большинстве случаев я пытался прокомментировать код, который требует JDK 7 или JDK 8 для определения этих зависимостей и обеспечения альтернативных подходов в более ранних версиях Java. Я сделал это так, чтобы инструмент можно было адаптировать для работы даже в средах со старыми версиями Java.

Полный список кодов для инструмента проверки XML на основе Java, описанный в этом посте, приведен в конце поста. Наиболее важные строки кода из этого приложения при обсуждении проверки XML на один или несколько XSD показаны ниже.

Суть проверки XML против XSD с Java

1
2
3
final Schema schema = schemaFactory.newSchema(xsdSources);
final Validator validator = schema.newValidator();
validator.validate(new StreamSource(new File(xmlFilePathAndName)));

В предыдущем листинге кода показан простой подход, доступный в стандартном JDK для проверки XML на соответствие XSD. Экземпляр javax.xml.validation.Schema создается с помощью вызова javax.xml.validation.SchemaFactory.newSchema (Source []) (где массив объектов javax.xml.transform.Source представляет один или несколько XSD). Экземпляр javax.xml.validation.Validator получается из экземпляра Schema с помощью метода newValidator () Schema . Проверяемый XML-код может быть передан методу validate (Source) этого Validator для проверки XML-данных по XSD или XSD, изначально предоставленным объекту Schema созданному с помощью SchemaFactory.newSchema(Source[]) .

Следующий листинг кода включает только что выделенный код, но представляет весь метод, в котором находится этот код.

validateXmlAgainstXsds (String, String [])

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Validate provided XML against the provided XSD schema files.
 *
 * @param xmlFilePathAndName Path/name of XML file to be validated;
 *    should not be null or empty.
 * @param xsdFilesPathsAndNames XSDs against which to validate the XML;
 *    should not be null or empty.
 */
public static void validateXmlAgainstXsds(
   final String xmlFilePathAndName, final String[] xsdFilesPathsAndNames)
{
   if (xmlFilePathAndName == null || xmlFilePathAndName.isEmpty())
   {
      out.println("ERROR: Path/name of XML to be validated cannot be null.");
      return;
   }
   if (xsdFilesPathsAndNames == null || xsdFilesPathsAndNames.length < 1)
   {
      out.println("ERROR: At least one XSD must be provided to validate XML against.");
      return;
   }
   final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 
   final StreamSource[] xsdSources = generateStreamSourcesFromXsdPathsJdk8(xsdFilesPathsAndNames);
 
   try
   {
      final Schema schema = schemaFactory.newSchema(xsdSources);
      final Validator validator = schema.newValidator();
      out.println(  "Validating " + xmlFilePathAndName + " against XSDs "
                  + Arrays.toString(xsdFilesPathsAndNames) + "...");
      validator.validate(new StreamSource(new File(xmlFilePathAndName)));
   }
   catch (IOException | SAXException exception)  // JDK 7 multi-exception catch
   {
      out.println(
           "ERROR: Unable to validate " + xmlFilePathAndName
         + " against XSDs " + Arrays.toString(xsdFilesPathsAndNames)
         + " - " + exception);
   }
   out.println("Validation process completed.");
}

В листинге кода для метода validateXmlAgainstXsds(String, String[]) показано, как можно получить экземпляр SchemaFactory с помощью указанного типа схемы ( XMLConstants.W3C_XML_SCHEMA_NS_URI ). Этот метод также обрабатывает различные типы исключений, которые могут быть выброшены в процессе проверки. Как отмечается в комментарии к коду, в этом методе используется изменение языка JDK 7, поддерживающее catch нескольких исключений в одном предложении catch но его можно заменить на отдельные предложения catch или catch единственного более общего исключения для баз кода раньше, чем JDK 7.

Только что показанный метод вызывает метод generateStreamSourcesFromXsdPathsJdk8(String[]) и следующий список содержит этот вызванный метод.

generateStreamSourcesFromXsdPathsJdk8 (String [])

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
/**
 * Generates array of StreamSource instances representing XSDs
 * associated with the file paths/names provided and use JDK 8
 * Stream API.
 *
 * This method can be commented out if using a version of
 * Java prior to JDK 8.
 *
 * @param xsdFilesPaths String representations of paths/names
 *    of XSD files.
 * @return StreamSource instances representing XSDs.
 */
private static StreamSource[] generateStreamSourcesFromXsdPathsJdk8(
   final String[] xsdFilesPaths)
{
   return Arrays.stream(xsdFilesPaths)
                .map(StreamSource::new)
                .collect(Collectors.toList())
                .toArray(new StreamSource[xsdFilesPaths.length]);
}

Только что показанный метод использует поддержку потока JDK 8 для преобразования массива Strings, представляющего пути / имена файлов XSD, в экземпляры StreamSource на основе содержимого XSD, на которые указывает строка пути / имени. В полном листинге кода класса также есть устаревший метод generateStreamSourcesFromXsdPathsJdk7(final String[]) который можно использовать вместо этого метода для оснований кода в версии Java ранее JDK 8.

Это одноклассное Java-приложение наиболее полезно, когда оно выполняется из командной строки. Для этого определяется main функция, как показано в следующем листинге кода.

Исполняемая основная (String []) функция

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Validates provided XML against provided XSD.
 *
 * @param arguments XML file to be validated (first argument) and
 *    XSD against which it should be validated (second and later
 *    arguments).
 */
public static void main(final String[] arguments)
{
   if (arguments.length < 2)
   {
      out.println("\nUSAGE: java XmlValidator <xmlFile> <xsdFile1> ... <xsdFileN>\n");
      out.println("\tOrder of XSDs can be significant (place XSDs that are");
      out.println("\tdependent on other XSDs after those they depend on)");
      System.exit(-1);
   }
   // Arrays.copyOfRange requires JDK 6; see
   // for additional details for versions of Java prior to JDK 6.
   final String[] schemas = Arrays.copyOfRange(arguments, 1, arguments.length);
   validateXmlAgainstXsds(arguments[0], schemas);
}

Исполняемая main(String[]) функция main(String[]) печатает оператор использования, если ему передано менее двух аргументов командной строки, поскольку она ожидает проверки по крайней мере имени / пути файла XML и проверки имени / пути XSD XML против.

javaXsdValidationToolUsage

Функция main принимает первый аргумент командной строки и обрабатывает его как путь / имя XML-файла, а затем обрабатывает все оставшиеся аргументы командной строки как пути / имена одного или нескольких XSD.

Простой Java-инструмент для проверки XML по одному или нескольким XSD-файлам теперь показан (полный список кода приведен внизу статьи). С его помощью мы можем запустить его на примере XML-файла и связанных XSD. Для этой демонстрации я использую очень простое проявление web.xml развертывания Servlet 2.5 web.xml .

Пример валидного сервлета 2.5 web.xml

1
2
3
4
5
6
7
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="2.5">
 
    <display-name>Sample Java Servlet 2.5 Web Application</display-name>
</web-app>

Простой web.xml который только что показан, действителен для XSD Servlet 2.5, и результаты работы этого простого средства проверки XSD на основе Java подтверждают это, не сообщая об ошибках проверки.

validWebXml25ShowsNoValidationErrors

XML-файл, действительный для XSD, не дает очень интересных результатов с этим инструментом. В следующем листинге кода показан намеренно недействительный web.xml котором есть элемент «title», не указанный в соответствующем Servlet 2.5 XSD. Вывод с выделенными наиболее значительными частями сообщения об ошибке показан после перечисления кода.

Пример неверного сервлета 2.5 web.xml (web-invalid.xml)

1
2
3
4
5
6
7
8
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="2.5">
 
    <display-name>Java Servlet 2.5 Web Application</display-name>
    <title>A handy example</title>
</web-app>

invalidWebXml25ShowsValidationErrors

Как показывает последний вывод, вещи более интересны с точки зрения вывода, когда предоставленный XML не является допустимым XSD.

Здесь я хочу подчеркнуть одну важную оговорку. XSD, предоставляемые этому Java-инструменту, иногда необходимо указывать в определенном порядке. В частности, XSD с зависимостями « include » от других XSD должны быть перечислены в командной строке ПОСЛЕ XSD, который они включают. Другими словами, XSD без «включаемых» зависимостей будут обычно предоставляться в командной строке перед теми XSD, которые их включают.

Следующий листинг кода для полного класса XmlValidator .

XmlValidator.java (полный список классов)

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package dustin.examples.xmlvalidation;
 
import org.xml.sax.SAXException;
 
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
 
import static java.lang.System.out;
 
/**
 * Validate provided XML against the provided XSDs.
 */
public class XmlValidator
{
   /**
    * Validate provided XML against the provided XSD schema files.
    *
    * @param xmlFilePathAndName Path/name of XML file to be validated;
    *    should not be null or empty.
    * @param xsdFilesPathsAndNames XSDs against which to validate the XML;
    *    should not be null or empty.
    */
   public static void validateXmlAgainstXsds(
      final String xmlFilePathAndName, final String[] xsdFilesPathsAndNames)
   {
      if (xmlFilePathAndName == null || xmlFilePathAndName.isEmpty())
      {
         out.println("ERROR: Path/name of XML to be validated cannot be null.");
         return;
      }
      if (xsdFilesPathsAndNames == null || xsdFilesPathsAndNames.length < 1)
      {
         out.println("ERROR: At least one XSD must be provided to validate XML against.");
         return;
      }
      final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 
      final StreamSource[] xsdSources = generateStreamSourcesFromXsdPathsJdk8(xsdFilesPathsAndNames);
 
      try
      {
         final Schema schema = schemaFactory.newSchema(xsdSources);
         final Validator validator = schema.newValidator();
         out.println("Validating " + xmlFilePathAndName + " against XSDs "
            + Arrays.toString(xsdFilesPathsAndNames) + "...");
         validator.validate(new StreamSource(new File(xmlFilePathAndName)));
      }
      catch (IOException | SAXException exception)  // JDK 7 multi-exception catch
      {
         out.println(
            "ERROR: Unable to validate " + xmlFilePathAndName
            + " against XSDs " + Arrays.toString(xsdFilesPathsAndNames)
            + " - " + exception);
      }
      out.println("Validation process completed.");
   }
 
   /**
    * Generates array of StreamSource instances representing XSDs
    * associated with the file paths/names provided and use JDK 8
    * Stream API.
    *
    * This method can be commented out if using a version of
    * Java prior to JDK 8.
    *
    * @param xsdFilesPaths String representations of paths/names
    *    of XSD files.
    * @return StreamSource instances representing XSDs.
    */
   private static StreamSource[] generateStreamSourcesFromXsdPathsJdk8(
      final String[] xsdFilesPaths)
   {
      return Arrays.stream(xsdFilesPaths)
                   .map(StreamSource::new)
                   .collect(Collectors.toList())
                   .toArray(new StreamSource[xsdFilesPaths.length]);
   }
 
   /**
    * Generates array of StreamSource instances representing XSDs
    * associated with the file paths/names provided and uses
    * pre-JDK 8 Java APIs.
    *
    * This method can be commented out (or better yet, removed
    * altogether) if using JDK 8 or later.
    *
    * @param xsdFilesPaths String representations of paths/names
    *    of XSD files.
    * @return StreamSource instances representing XSDs.
    * @deprecated Use generateStreamSourcesFromXsdPathsJdk8 instead
    *    when JDK 8 or later is available.
    */
   @Deprecated
   private static StreamSource[] generateStreamSourcesFromXsdPathsJdk7(
      final String[] xsdFilesPaths)
   {
      // Diamond operator used here requires JDK 7; add type of
      // StreamSource to generic specification of ArrayList for
      // JDK 5 or JDK 6
      final List<StreamSource> streamSources = new ArrayList<>();
      for (final String xsdPath : xsdFilesPaths)
      {
         streamSources.add(new StreamSource(xsdPath));
      }
      return streamSources.toArray(new StreamSource[xsdFilesPaths.length]);
   }
 
   /**
    * Validates provided XML against provided XSD.
    *
    * @param arguments XML file to be validated (first argument) and
    *    XSD against which it should be validated (second and later
    *    arguments).
    */
   public static void main(final String[] arguments)
   {
      if (arguments.length < 2)
      {
         out.println("\nUSAGE: java XmlValidator <xmlFile> <xsdFile1> ... <xsdFileN>\n");
         out.println("\tOrder of XSDs can be significant (place XSDs that are");
         out.println("\tdependent on other XSDs after those they depend on)");
         System.exit(-1);
      }
      // Arrays.copyOfRange requires JDK 6; see
      // for additional details for versions of Java prior to JDK 6.
      final String[] schemas = Arrays.copyOfRange(arguments, 1, arguments.length);
      validateXmlAgainstXsds(arguments[0], schemas);
   }
}

Несмотря на то, что изначально может быть предложено в этой статье, использование Java для проверки XML на XSD довольно просто. Пример приложения, показанный и объясненный здесь, пытается продемонстрировать это и является полезным инструментом для простой проверки командной строки документов XML на соответствие указанным XSD. Можно легко перенести это на Groovy, чтобы быть более дружественным к сценариям. Как упоминалось ранее, этот простой инструмент требует JDK 8 в том виде, в котором он написан в настоящее время, но его можно легко адаптировать для работы с JDK 5, JDK 6 или JDK 7.

Ссылка: Проверка XML на XSD в Java от нашего партнера по JCG Дастина Маркса в блоге Inspired by Actual Events .