Почему мой Transformer выводит свою таблицу стилей, а не преобразование, которое он представляет?

1

Мой вопрос вкратце: когда я строю Transformer из допустимой таблицы стилей и вызываю его метод transform(input, output), я, кажется, сам получаю содержимое самой таблицы стилей в output, а не преобразование input.

(Обновления внизу. На этот вопрос теперь дан ответ.)

Я выполняю самые основные из всех возможных базовых вызовов базовых API javax.xml.transform использующих Java 8.

Вот подробности.

У меня есть базовый XML-документ, который выглядит так (как мы увидим, это не имеет большого значения):

<?xml version="1.0" encoding="UTF-8"?>
<doc>
<target-store name="foobar">
 <target-key name="abc"/>
  <target-key name="def"/>
 </target-store>  
 <testing>
  <target-store>
   <target-key name="ghi">
    <bogus/>
   </target-key>
  </target-store>
 </testing>
</doc>

У меня есть XSLT файл, который выглядит так, хотя, как мы вскоре увидим, это не имеет особого значения:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

  <!-- First the identity transformation. -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy> 
  </xsl:template>

  <xsl:template match="target-store">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()">
        <xsl:with-param name="u" select="'bill'"/>
        <xsl:with-param name="p" select="'gates'"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="target-key">
    <xsl:param name="u" select="'scott'"/>
    <xsl:param name="p" select="'tiger'"/>
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
        <username><xsl:value-of select="$u"/></username> 
        <password><xsl:value-of select="$p"/></password>
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

(Включение этих необработанных значений в несколько разных онлайн-тестеров XSLT создает преобразованный вывод типа, который я хочу. Помещение фиктивных символов в файл XSLT вызывает ошибку, поэтому он успешно считывается и анализируется, поэтому он является допустимым файлом таблицы стилей.)

Если я запустил следующий (элементарный) код преобразования Java, выводится таблица стилей, а не преобразование, которое оно представляет. Виски Танго Фокстрот.

Я не могу понять, почему это так. Вот код, а затем я вставлю вывод:

public void testRawAPIs() throws Exception {
  final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  assertNotNull(ccl);

  final URL foobarXml = ccl.getResource("foobar.xml");
  assertNotNull(foobarXml);

  final URL foobarXslt = ccl.getResource("foobar.xslt");
  assertNotNull(foobarXslt);

  try (final InputStream foobarStream = new BufferedInputStream(foobarXml.openStream());
       final InputStream foobarXsltStream = new BufferedInputStream(foobarXslt.openStream())) {

    System.out.println("*****");

    // db is set up elsewhere as DocumentFactoryBuilder.newInstance().newDocumentBuilder().
    final Document foobarDocument = db.parse(foobarStream);
    assertNotNull(foobarDocument);
    print(foobarDocument);

    System.out.println("*****");

    final Document foobarXsltDocument = db.parse(foobarXsltStream);
    assertNotNull(foobarXsltDocument);
    print(foobarXsltDocument);

    System.out.println("*****");

    // tf is set up by JUnit elsewhere as TransformerFactory.newInstance().
    final Transformer t = tf.newTemplates(new DOMSource(foobarXsltDocument)).newTransformer();
    assertNotNull(t);

    final DOMResult result = new DOMResult();

    t.transform(new DOMSource(foobarDocument), result);

    // TODO FIXME: for some reason, this prints out the STYLESHEET.  WTF.
    print((Document)result.getNode());

    System.out.println("*****");

}

Метод print() прост:

private static final void print(final Document document) throws Exception {
  print(document, new BufferedWriter(new OutputStreamWriter(System.out, "UTF-8")), 2);
}

private static final void print(final Document document, final Writer writer, final int indent) throws Exception {
  final Transformer transformer = tf.newTransformer();
  assertNotNull(transformer);
  transformer.setOutputProperty(OutputKeys.METHOD, "xml");
  transformer.setOutputProperty(OutputKeys.INDENT, "yes");
  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent));

  transformer.transform(new DOMSource(document), new StreamResult(writer));
}

Наконец, выход. Как вы можете видеть, исходный документ печатается первым, как я ожидал, после чего следует явно напечатанная таблица стилей, как я ожидал, а затем... нормализованная версия таблицы стилей (WTF):

*****
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<doc>
<target-store name="foobar">
  <target-key name="abc"/>
  <target-key name="def"/>
</target-store>
<testing>
  <target-store>
    <target-key name="ghi">
      <bogus/>
    </target-key>
  </target-store>
</testing>
</doc>
*****
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

  <!-- First the identity transformation. -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="target-store">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()">
        <xsl:with-param name="u" select="'bill'"/>
        <xsl:with-param name="p" select="'gates'"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="target-key">
    <xsl:param name="u" select="'scott'"/>
    <xsl:param name="p" select="'tiger'"/>
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <username>
        <xsl:value-of select="$u"/>
      </username> 
      <password>
        <xsl:value-of select="$p"/>
      </password>
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>
*****
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="target-store">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()">
        <xsl:with-param name="u" select="'bill'"/>
        <xsl:with-param name="p" select="'gates'"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="target-key">
    <xsl:param name="u" select="'scott'"/>
    <xsl:param name="p" select="'tiger'"/>
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <username>
        <xsl:value-of select="$u"/>
      </username>
      <password>
        <xsl:value-of select="$p"/>
      </password>
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>
*****

Что я сделал неправильно в этом самом базовом примере учебного класса?

Обновление # 1: В моем примере здесь я DOMSource Source для моей таблицы стилей с помощью DOMSource. Если я переключусь на использование StreamSource для "размещения" моей таблицы стилей, мой вывод будет правильным. Почему это? Есть ли что-то по своей сути неправильно с использованием DOMSource для DOMSource Document который является результатом разбора таблицы стилей XSLT?

Обновление № 2: Спасибо Господу; есть связанный с этим вопрос.

Обновление №3: TransformerFactory позволяет использовать любую реализацию Source в своем newTransformer(Source). Но если эта реализация Source является DOMSource, то вам лучше надеяться, что ваш вызывающий абонент произведет ее с помощью пространства имен DocumentBuilderFactory, или результатом преобразования документа будет сама таблица стилей. Это чрезвычайно странно и неприятно из-за недостатка в дизайне этих API.

  • 1
    Дает ли использование StreamSource (вместо DOMSource ) другой результат? (Я имею в виду, попробуйте использовать: final Transformer t = tf.newTemplates(new DOMSource(foobarXsltStream)).newTransformer() и t.transform(new DOMSource(foobarStream), result); ). Метод отличается, и результаты могут дать вам лучшее представление о проблеме (также эффективнее сделать это таким образом).
  • 1
    Да, это именно проблема. Если DocumentBuilderFactory которая (косвенно) создала Document в DOMSource не поддерживала пространство имен, то вы получите таблицу стилей в качестве выходных данных. Это даже при том, что контракт в Transformer принимает любой Source . Почему это так, до сих пор немного загадка для меня.
Теги:
xslt

1 ответ

1

Оказывается, хотя TransformerFactory позволяет использовать реализацию Source для вашей таблицы стилей, если вы случайно (неосознанно, конечно) используете DOMSource который был сконструирован вызывающим, который не объявлял свой связанный DocumentBuilderFactory как пространство имен, тогда вы будете получите результат таблицы стилей как преобразование (!).

Мораль истории: скажите своим абонентам, что делать (?!) Или - если у вас есть какой-либо контроль над процессом преобразования - убедитесь, что вы никогда не принимаете Source который на самом деле является DOMSource.

  • 0
    Хотя ответ может быть эмпирически правильным (предположительно для версии Xalan, поставляемой с JDK), я не понимаю, ПОЧЕМУ это происходит. Я ожидаю сообщение о том, что таблица стилей недействительна. Но другая мораль этой истории - избегать DOM, как чумы. Всегда есть лучшая альтернатива.
  • 0
    И я не! Когда у меня будет немного времени, я собираюсь покопаться в реализации Xalan и отследить эту присоску. Ненавижу чувствовать себя тупым.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню