Форматирование десятичных значений для XML

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

Моя система на основе Java в настоящее время преобразует объекты в XML с помощью XStream. У нас есть XSD, который сопровождает XML и определяет различные элементы как string, double, dateTime и т. д.

У меня есть три двойных поля, которые содержат такие значения, как 12,5, 100,123, 5,23445 и т. д. Прямо сейчас они преобразуются в XML практически как есть. Мне нужно, чтобы эти значения были отформатированы в XML до одного десятичного знака; 12,5, 100,1, 5,2 и т. д.

Я кратко придумал варианты, как это сделать:

  • Каким-то образом Java отформатирует эти значения с этой точностью, прежде чем они перейдут в XML. Возможно, NumberFormat может это сделать, хотя я думал, что это в основном для использования с выводом String.
  • Надеюсь, что XSD сможет сделать это за меня; Я знаю, что вы можете установить ограничения на точность в XSD, но я не уверен, действительно ли он обрабатывает само округление или просто скажет: «Это значение 123,123 недопустимо для этой схемы»?
  • Используйте XSLT, чтобы как-то сделать это для меня.

Я бы посоветовал вам коллективно подумать о том, что было бы «принятым» способом / лучшей практикой для использования в такой ситуации.

Спасибо, Дэйв.


person f1dave    schedule 28.06.2011    source источник
comment
Покажите немного кода, и, возможно, у нас будет контекст, который поможет немного лучше.   -  person Don Roby    schedule 28.06.2011
comment
Подходит ли вам XSLT 2.0? В вашем распоряжении будут format-number() и xsl:decimal-format.   -  person Emiliano Poggi    schedule 28.06.2011
comment
@Don - на данный момент для него нет подходящего кода. Я обдумываю 3 варианта, упомянутых выше. @empo - мне нужно посмотреть, поддерживает ли XStream XSLT 2.0; если это так, то они могут помочь.   -  person f1dave    schedule 28.06.2011
comment
Хороший вопрос, +1. См. мой ответ для полного, короткого и простого однострочного решения XPath :)   -  person Dimitre Novatchev    schedule 28.06.2011
comment
Я также добавил обобщенное выражение, которое выполняет округление с любой желаемой точностью. :)   -  person Dimitre Novatchev    schedule 29.06.2011
comment
Всем спасибо. В конце концов я решил использовать серверное решение, сочетающее преобразователи с моим собственным типом данных Wrapper. Таким образом, преобразователи влияют только на двойную оболочку, а не на остальные двойные. Однако все ответы приветствуются.   -  person f1dave    schedule 29.06.2011


Ответы (2)


XStream имеет конвертеры (руководство). Вам нужно будет зарегистрировать свой собственный двойной конвертер, который справится с этим. В конвертере используйте DecimalFormat, чтобы ограничить число десятичных разрядов.

person Bozho    schedule 28.06.2011
comment
Хорошее предложение - могут ли преобразователи быть привязаны к определенному набору элементов? У меня есть несколько двойников в XML, но если я хочу, чтобы те, что касаются цен, были, например, .1... - person f1dave; 28.06.2011
comment
@Bozho: вам может быть интересно увидеть чистое решение XPath в моем ответе :) - person Dimitre Novatchev; 29.06.2011
comment
@Божо: Почему? Можете ли вы предложить то, что вы считаете более простым выражением? - person Dimitre Novatchev; 29.06.2011
comment
Нет, именно поэтому я не предложил выражение :) - person Bozho; 29.06.2011

Это можно сделать в одном выражении XPath.

Использование:

floor(.) + round(10*(. -floor(.))) div 10

Проверка с использованием XSLT в качестве хоста XPath:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="text()[contains(.,'.')]">
    <xsl:value-of select=
     "floor(.) + round(10*(. -floor(.))) div 10"/>
 </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к следующему XML-документу:

<t>
 <n>12.5</n>
 <n>100.123</n>
 <n>5.26445</n>
</t>

получен желаемый правильный результат:

<t>
   <n>12.5</n>
   <n>100.1</n>
   <n>5.3</n>
</t>

Объяснение: использование стандартных функций XPath floor()< /strong>, round() и оператор XPath div и ваша логика.

Обобщенное выражение:

floor(.) + round($vFactor*(. -floor(.))) div $vFactor

где $vFactor — это 10^N, где N — это количество цифр после десятичной точки, которую мы хотим.

Используя это выражение, модифицированное преобразование XSLT выглядит следующим образом:

<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>

     <xsl:param name="pPrecision" select="4"/>

     <xsl:variable name="vFactor" select=
     "substring('10000000000000000000000',
                 1, $pPrecision+1
                )
     "/>

     <xsl:template match="node()|@*">
         <xsl:copy>
           <xsl:apply-templates select="node()|@*"/>
         </xsl:copy>
     </xsl:template>

     <xsl:template match="text()[contains(.,'.')]">
        <xsl:value-of select=
         "floor(.) + round($vFactor*(. -floor(.))) div $vFactor"/>
     </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к тому же XML-документу (см. выше), мы получаем требуемый результат для любого значимого значения $pPrecision. В приведенном выше примере для него установлено значение 4, а результат содержит все числа, округленные в большую сторону. до четырех знаков после запятой:

<t>
   <n>12.5</n>
   <n>100.123</n>
   <n>5.2645</n>
</t>
person Dimitre Novatchev    schedule 28.06.2011