Порядок атрибутов XML после обработки DOM - PullRequest
40 голосов
/ 07 апреля 2009

При обработке XML с помощью стандартной DOM порядок атрибутов не гарантируется после обратной сериализации. Наконец, это то, что я только что понял, когда использовал стандартный Java Transform API для сериализации вывода.

Однако мне нужно сохранить порядок. Я хотел бы знать, есть ли на Java возможность сохранить исходный порядок атрибутов XML-файла, обрабатываемого с помощью DOM API, или каким-либо способом принудительно установить порядок (возможно, с помощью альтернативного API-интерфейса сериализации, который позволяет вам установить вид имущества). В моем случае обработка уменьшает значение некоторых атрибутов (не всех) последовательности одинаковых элементов с кучей атрибутов и, возможно, вставляет еще несколько элементов.

Есть ли какой-нибудь "простой" способ или мне нужно определить собственную таблицу стилей преобразования XSLT, чтобы указать выходные данные и изменить весь входной XML-файл?

Обновление Я должен поблагодарить все ваши ответы. Ответ кажется теперь более очевидным, чем я ожидал. Я никогда не обращал внимания на порядок следования атрибутов, так как раньше никогда не нуждался в этом.

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

Я только что разработал небольшое приложение для объединения исходного файла (общего для всех проектов) с определенными частями каждого проекта (изменения значения некоторых атрибутов), поэтому файл для конкретного проекта получает обновления базового (новые определения сигналов тревоги). или исправлены некоторые значения атрибутов). Моя основная мотивация требовать упорядоченных атрибутов - иметь возможность проверять выходные данные приложения снова и снова в исходном файле с помощью инструмента сравнения текста (такого как Winmerge). Если формат (в основном порядок атрибутов) остается прежним, различия легко заметить.

Я действительно думал, что это возможно, поскольку программы обработки XML, такие как XML Spy, позволяют редактировать файлы XML и применять некоторые порядок (режим сетки). Может быть, я выбрал одну из этих программ, чтобы вручную изменить файл вывода.

Ответы [ 11 ]

24 голосов
/ 16 сентября 2010

Извините, но ответ более тонкий, чем "Нет, ты не можешь" или "Зачем тебе это нужно делать в первую очередь?".

Краткий ответ: «DOM не позволит вам сделать это, но SAX сделает».

Это потому, что DOM не заботится о порядке атрибутов, поскольку он не имеет смысла для стандарта, и к тому времени, когда XSL овладевает входным потоком, информация уже потеряна. Большинство XSL-движков фактически изящно сохранят порядок атрибутов входного потока (например, Xalan-C (кроме одного случая) или Xalan-J (всегда)). Особенно если вы используете <xsl:copy*>.

Насколько мне известно, случаи, когда порядок атрибутов не сохраняется. - Если входной поток является DOM - Xalan-C: если вы вставите теги дерева результатов буквально (например, <elem att1={@att1} .../>

Вот один пример с SAX для записи (также запрещающий ворчание DTD).

SAXParserFactory spf = SAXParserFactoryImpl.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(false);
spf.setFeature("http://xml.org/sax/features/validation", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParser sp = spf.newSAXParser() ;
Source src = new SAXSource ( sp.getXMLReader(), new InputSource( input.getAbsolutePath() ) ) ;
String resultFileName = input.getAbsolutePath().replaceAll(".xml$", ".cooked.xml" ) ;
Result result = new StreamResult( new File (resultFileName) ) ;
TransformerFactory tf = TransformerFactory.newInstance();
Source xsltSource = new StreamSource( new File ( COOKER_XSL ) );
xsl = tf.newTransformer( xsltSource ) ;
xsl.setParameter( "srcDocumentName", input.getName() ) ;
xsl.setParameter( "srcDocumentPath", input.getAbsolutePath() ) ;

xsl.transform(src, result );

Я также хотел бы отметить, что, по мнению многих скептиков, в есть случаи, когда порядок атрибутов имеет значение .

Регрессионное тестирование является очевидным случаем. Кто бы ни был вызван для оптимизации не очень хорошо написанного XSL, он знает, что вы обычно хотите убедиться, что "новые" деревья результатов похожи или "идентичны" старым. И когда дерево результатов составляет около миллиона строк, инструменты сравнения XML оказываются слишком громоздкими ... В этих случаях сохранение порядка атрибутов очень помогает.

Надеюсь, это поможет; -)

24 голосов
/ 07 апреля 2009

Посмотрите раздел 3.1 рекомендации XML. В нем говорится: «Обратите внимание, что порядок спецификаций атрибутов в начальном теге или теге пустого элемента не имеет значения».

Если часть программного обеспечения требует, чтобы атрибуты элемента XML отображались в определенном порядке, это программное обеспечение не обрабатывает XML, а обрабатывает текст, который внешне выглядит как XML. Это должно быть исправлено.

Если это невозможно исправить, и вам нужно создавать файлы, соответствующие его требованиям, вы не можете надежно использовать стандартные инструменты XML для создания этих файлов. Например, вы можете попытаться (как вы предлагаете) использовать XSLT для создания атрибутов в определенном порядке, например ::

<test>
   <xsl:attribute name="foo"/>
   <xsl:attribute name="bar"/>
   <xsl:attribute name="baz"/>
</test>

только чтобы обнаружить, что процессор XSLT излучает это:

<test bar="" baz="" foo=""/>

потому что DOM, который процессор использует, упорядочивает атрибуты в алфавитном порядке по имени тега. (Это распространенное, но не универсальное поведение среди XML DOM.)

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

9 голосов
/ 16 сентября 2010

XML Канонизация приводит к последовательному упорядочению атрибутов, в первую очередь позволяющему проверять подпись для некоторых или всех XML, хотя существуют и другие потенциальные применения. Это может соответствовать вашим целям.

8 голосов
/ 07 апреля 2009

Невозможно переоценить то, что только что сказал Роберт Росни, но я попробую. ; -)

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

XML должен быть одним из самых важных стандартов, которые у нас есть. Это основа «старых веб» вещей, таких как SOAP, и еще «веб 2.0», таких как RSS и Atom. Благодаря четким стандартам XML может взаимодействовать между различными платформами.

Если мы откажемся от XML, постепенно мы попадем в ситуацию, когда производитель XML не сможет предположить, что потребитель XML сможет потреблять их контент. Это может иметь катастрофические последствия для промышленности.

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

Так что, не «включайте» компании, которые не понимают XML. Отправьте им стандарт, выделив соответствующие строки. Им нужно перестать думать, что XML - это просто текст с угловыми скобками. Он просто не ведет себя как текст с угловыми скобками.

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

2 голосов
/ 07 апреля 2009

Вам действительно не нужно соблюдать порядок. Насколько я знаю, ни одна схема не учитывает порядок атрибутов при проверке XML-документа. Похоже, что все, что обрабатывает XML на другом конце, не использует надлежащий DOM для анализа результатов.

Полагаю, одним из вариантов было бы создать документ вручную, используя построение строк, но я настоятельно рекомендую против этого.

1 голос
/ 01 февраля 2013

У меня была точно такая же проблема. Я хотел изменить атрибуты XML, но хотел сохранить порядок из-за различий. Я использовал StAX для достижения этой цели. Вы должны использовать XMLStreamReader и XMLStreamWriter (решение на основе курсора). Когда вы получаете тип события START_ELEMENT, курсор сохраняет индекс атрибутов. Следовательно, вы можете внести соответствующие изменения и записать их в выходной файл «по порядку».

Посмотрите на эту статью / обсуждение . Вы можете увидеть, как читать атрибуты начальных элементов по порядку.

1 голос
/ 07 апреля 2009

Роберт Россни хорошо сказал: если вы полагаетесь на упорядочение атрибутов, вы на самом деле не обрабатываете XML, а скорее напоминает XML.

Я могу вспомнить, по крайней мере, две причины, по которым вы можете заботиться о порядке следования атрибутов. Могут быть и другие, но по крайней мере для этих двух я могу предложить альтернативы:

  1. Вы используете несколько экземпляров атрибутов с одинаковыми именами:

    <foo myAttribute="a" myAttribute="b" myAttribute="c"/>
    

    Это просто неверный XML; процессор DOM, вероятно, отбросит все, кроме одного из этих значений - если он вообще обрабатывает документ. Вместо этого вы хотите использовать дочерние элементы:

    <foo>
        <myChild="a"/>
        <myChild="b"/>
        <myChild="c"/>
    </foo>
    
  2. Вы предполагаете, что какое-то различие применяется к атрибуту (-ам), которые на первом месте. Сделайте это явным либо через другие атрибуты, либо через дочерние элементы. Например:

    <foo attr1="a" attr2="b" attr3="c" theMostImportantAttribute="attr1" />
    
0 голосов
/ 15 сентября 2015

Вид работ ...

package mynewpackage;

// for the method
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

// for the test example
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import org.w3c.dom.Document;
import java.math.BigDecimal;

public class NodeTools {
    /**
     * Method sorts any NodeList by provided attribute.
     * @param nl NodeList to sort
     * @param attributeName attribute name to use
     * @param asc true - ascending, false - descending
     * @param B class must implement Comparable and have Constructor(String) - e.g. Integer.class , BigDecimal.class etc
     * @return 
     */
    public static Node[] sortNodes(NodeList nl, String attributeName, boolean asc, Class<? extends Comparable> B)
    {        
        class NodeComparator<T> implements Comparator<T>
        {
            @Override
            public int compare(T a, T b)
            {
                int ret;
                Comparable bda = null, bdb = null;
                try{
                    Constructor bc = B.getDeclaredConstructor(String.class);
                    bda = (Comparable)bc.newInstance(((Element)a).getAttribute(attributeName));
                    bdb = (Comparable)bc.newInstance(((Element)b).getAttribute(attributeName));
                }
                catch(Exception e)
                {
                    return 0; // yes, ugly, i know :)
                }
                ret = bda.compareTo(bdb);
                return asc ? ret : -ret; 
            }
        }

        List<Node> x = new ArrayList<>();
        for(int i = 0; i < nl.getLength(); i++)
        {
            x.add(nl.item(i));
        }
        Node[] ret = new Node[x.size()];
        ret = x.toArray(ret);
        Arrays.sort(ret, new NodeComparator<Node>());
        return ret;
    }    

    public static void main(String... args)
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
        DocumentBuilder builder;
        String s = "<xml><item id=\"1\" price=\"100.00\" /><item id=\"3\" price=\"29.99\" /><item id=\"2\" price=\"5.10\" /></xml>";
        Document doc = null;
        try 
        {  
            builder = factory.newDocumentBuilder();  
            doc = builder.parse(new InputSource(new StringReader(s)));
        }
        catch(Exception e) { System.out.println("Alarm "+e); return; }

        System.out.println("*** Sort by id ***");
        Node[] ret = NodeTools.sortNodes(doc.getElementsByTagName("item"), "id", true, Integer.class);

        for(Node n: ret)
        {
            System.out.println(((Element)n).getAttribute("id")+" : "+((Element)n).getAttribute("price"));
        }

        System.out.println("*** Sort by price ***");
        ret = NodeTools.sortNodes(doc.getElementsByTagName("item"), "price", true, BigDecimal.class);
        for(Node n: ret)
        {
            System.out.println(((Element)n).getAttribute("id")+" : "+((Element)n).getAttribute("price"));
        }
    }
}

В моем простом тесте он печатает:

*** Sort by id ***
1 : 100.00
2 : 5.10
3 : 29.99
*** Sort by price ***
2 : 5.10
3 : 29.99
1 : 100.00
0 голосов
/ 09 июня 2015

Вы все еще можете сделать это, используя стандартный DOM и Transformation API, используя быстрое и грязное решение, подобное описанному мной:

Мы знаем, что решение API преобразования упорядочивает атрибуты в алфавитном порядке. Вы можете добавить к именам атрибутов некоторые строки, которые можно будет легко вырезать позже, чтобы они выводились в нужном вам порядке. Простые префиксы, такие как «a_», «b_» и т. Д., Должны быть достаточными в большинстве ситуаций и могут быть легко удалены из выходного xml с помощью регулярного выражения с одним слоем.

Если вы загружаете xml и сохраняете и хотите сохранить порядок атрибутов, вы можете использовать тот же принцип, сначала изменив имена атрибутов во входном тексте xml, а затем проанализировав его в объекте Document. Опять же, сделайте эту модификацию на основе текстовой обработки xml. Это может быть сложно, но может быть сделано путем обнаружения элементов и их атрибутов, опять же, с помощью регулярных выражений. Обратите внимание, что это грязное решение. Существует много подводных камней при самостоятельном анализе XML, даже для чего-то более простого, поэтому будьте осторожны, если решите реализовать это.

0 голосов
/ 03 октября 2014

У меня довольно похожая проблема. Мне нужно иметь всегда один и тот же атрибут для первого. Пример:

<h50row a="1" xidx="1" c="1"></h50row>
<h50row a="2" b="2" xidx="2"></h50row>

должно стать

<h50row xidx="1" a="1" c="1"></h50row>
<h50row xidx="2" a="2" b="2"></h50row>

Я нашел решение с регулярным выражением:

test = "<h50row a=\"1\" xidx=\"1\" c=\"1\"></h50row>";
test = test.replaceAll("(<h5.*row)(.*)(.xidx=\"\\w*\")([^>]*)(>)", "$1$3$2$4$5");

Надеюсь, вы найдете это полезным

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...