четверг, 7 октября 2010 г.

Проблемы валидации XML c XInclude.

Eсть вот такое расширение для XML . Первое знакомство можно начать с этого ресурса.
Вкратце: есть возможность с помощью директивы <xi:include href="..." /> в произвольное место XML вставлять содержимое из другого файла.

Штуковина вроде бы прикольная, но при работе с ней возникают некоторые трудности:
  • XML редакторы не обрабатывают эту конструкцию должным образом.
  • Java SAX парсеры без специальной настройки тоже не обрабатывают эту директиву.


Скорее всего происходит это потому, что XML вот такого вида в принципе не валидный:
<test:root>
  <xi:include href="..." />
</test:root>
Для его правильной обработки необходимо произвести нечто вроде предпроцессинга, т.е. заменить <xi:include href="..." /> на содержимое соответствующего файла.

Итак, задача:
Есть XML и его надо загрузить. Ну скажем средствами DOM4J.
Отягощающие условия:
  • Необходимо обязательно провалидировать загружаемый XML
  • В атрибуте xsi:schemaLocation указаны не корректные данные. Настоящее место расположение файлов XSD необходимо задать программно
  • Загружаемый элемент содержит директивы XInclude

Есть XSD и набор тестовых XML файлов (приведены в конце). Особенности XML:
  • Некорректно указан xsi:schemaLocation (например "http://www.xml.test.org/test platform:/resource/dom4j_validation/src/test/resources/xsd/test.xsd" понимает только eclipse)
  • 3 тестовых XML: невалидный, валидный и валидный с Xinclude директивами.
И есть тест:
public abstract class BaseLoaderTest {
 protected abstract TestLoader getLoader();
 
 @Test
 public void testInvalidXml() throws Exception {
  try {
   getLoader().tryToLoad("invalid.xml");
   fail("Invalid XML was loaded without errors");
  } catch (DocumentException e) {
   assertTrue(e.getMessage(), e.getMessage().contains("The content of element 'test:el1' is not complete"));
  }
 }
 
 @Test
 public void testValidXmlWithoutXInclude() throws Exception {
  Document result = getLoader().tryToLoad("valid.xml");
  assertNotNull(result);
 }
 
 @Test
 public void testValidXmlWithXInclude() throws Exception {
  Document result = getLoader().tryToLoad("valid_with_xinclude.xml");
  assertNotNull(result);
 }
}

Начинаем со стандартного кода:
public Document tryToLoad(String xmlLocation) throws Exception {
  SAXReader reader = new SAXReader(true);
  reader.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
  reader.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", getSchemaFiles());
  
  return reader.read(getResource(xmlLocation));
}
В таком варианте мы включаем валидацию и указываем истинное месторасположение XSD файлов. Но testValidXmlWithXInclude() упорно валится :(

Потребовалось довольно много времени, для того, чтобы найти 2 заветные строчки кода, которые сделали счастье. Информация была найдена на сайте apache xerces. В итоге получилось нечто вот такое:
public Document tryToLoad(String xmlLocation) throws Exception {
  SAXReader reader = new SAXReader(true);
  reader.setProperty(JAXP_SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI);
  reader.setProperty(JAXP_SCHEMA_SOURCE, getSchemaFiles());

  // enable XInclude directives processing
  reader.setFeature("http://apache.org/xml/features/xinclude", true);
  reader.setFeature("http://apache.org/xml/features/xinclude/fixup-base-uris", false);

  return reader.read(getResource(xmlLocation));
}

Содерживое тестовых XML /XSD файлов.
<?xml version="1.0" encoding="UTF-8"?>
<schema
 xmlns="http://www.w3.org/2001/XMLSchema"
 targetNamespace="http://www.xml.test.org/test"
 xmlns:tns="http://www.xml.test.org/test"
 elementFormDefault="qualified">

 <element
  name="root"
  type="tns:root-type" />
 <complexType
  name="root-type">
  <sequence>
   <element
    ref="tns:el1"
    minOccurs="1"
    maxOccurs="unbounded" />
  </sequence>
 </complexType>

 <complexType
  name="level1-type">
  <sequence>
   <element
    name="el2"
    type="tns:level2-type"
    minOccurs="1"
    maxOccurs="unbounded"
    nillable="false" />
  </sequence>
 </complexType>

 <complexType
  name="level2-type">
  <sequence>
   <element
    name="value"
    type="string"
    minOccurs="1"
    maxOccurs="unbounded"
    nillable="false" />
  </sequence>
 </complexType>


    <element name="el1" type="tns:level1-type"  nillable="false"/>
</schema>

<?xml version="1.0" encoding="UTF-8"?>
<test:root
 xmlns:test="http://www.xml.test.org/test"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.xml.test.org/test platform:/resource/dom4j_validation/src/test/resources/xsd/test.xsd">
 <test:el1>
  <test:el2>
   <test:value>value1</test:value>
  </test:el2>
 </test:el1>
 <test:el1>
  <!— Missing a required element. -->
 </test:el1>
</test:root>

<?xml version="1.0" encoding="UTF-8"?>
<test:root
 xmlns:test="http://www.xml.test.org/test"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.xml.test.org/test platform:/resource/dom4j_validation/src/test/resources/xsd/test.xsd">
 <test:el1>
  <test:el2>
   <test:value>value1</test:value>
  </test:el2>
 </test:el1>
 <test:el1>
  <test:el2>
   <test:value>value2_1</test:value>
   <test:value>value2_2</test:value>
  </test:el2>
 </test:el1>
</test:root>

<?xml version="1.0" encoding="UTF-8"?>
<test:root
 xmlns:test="http://www.xml.test.org/test"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xi="http://www.w3.org/2001/XInclude"
 xsi:schemaLocation="http://www.xml.test.org/test platform:/resource/dom4j_validation/src/test/resources/xsd/test.xsd
 http://www.w3.org/2001/XInclude http://www.w3.org/2001/XInclude.xsd">
 <xi:include
  href="valid_with_xinclude_part1.xml" parse="xml" />
 <xi:include
  href="valid_with_xinclude_part2.xml" parse="xml" />
</test:root>


(ну и валидные valid_with_xinclude_part1.xml и valid_with_xinclude_part2.xml )
<?xml version="1.0" encoding="UTF-8"?>
<test:el1
 xmlns:test="http://www.xml.test.org/test"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.xml.test.org/test ./xsd/test.xsd">
 <test:el2>
  <test:value>value1</test:value>
 </test:el2>
</test:el1>

<?xml version="1.0" encoding="UTF-8"?>
<test:el1
 xmlns:test="http://www.xml.test.org/test"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.xml.test.org/test ./xsd/test.xsd">
 <test:el2>
  <test:value>value2_1</test:value>
  <test:value>value2_2</test:value>
 </test:el2>
</test:el1>

Комментариев нет:

Отправить комментарий