XML 요소를 제자리에 정렬하는 방법은 무엇입니까?

XML 요소를 제자리에 정렬하는 방법은 무엇입니까?

IntelliJ IDEA 구성 파일의 버전을 제어하려고 합니다. 다음은 작은 샘플입니다.

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ChangeListManager">
    <ignored path="tilde.iws" />
    <ignored path=".idea/workspace.xml" />
    <ignored path=".idea/dataSources.local.xml" />
    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
    <option name="TRACKING_ENABLED" value="true" />
    <option name="SHOW_DIALOG" value="false" />
    <option name="HIGHLIGHT_CONFLICTS" value="true" />
    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
    <option name="LAST_RESOLUTION" value="IGNORE" />
  </component>
  <component name="ToolWindowManager">
    <frame x="1201" y="380" width="958" height="1179" extended-state="0" />
    <editor active="false" />
    <layout>
      <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
      <window_info id="Palette&#9;" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
    </layout>
  </component>
</project>

/project/component[@name='ToolWindowManager']/layout/window_infoIDE가 구성을 저장할 때마다 일부 요소가 임의의 순서로 저장되는 것처럼 보입니다. 동일한 유형의 모든 요소는 항상 동일한 순서로 동일한 속성을 갖는 것처럼 보입니다. 요소의 순서는 IDE의 기능과 아무 관련이 없다는 점을 고려하면,요소는 요소 이름을 기준으로 정렬된 다음 속성 값을 기준으로 정렬됩니다.그리고 속성과 공백은 그대로 유지됩니다.

기반으로또 다른 대답난 이미 도착했어요이것:

<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
    <output method="xml" indent="yes" encoding="UTF-8"/>
    <strip-space elements="*"/>

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

    <template match="*">
        <copy>
            <apply-templates select="@*"/>
            <apply-templates>
                <sort select="name()"/>
                <sort select="@*[1]"/>
                <sort select="@*[2]"/>
                <sort select="@*[3]"/>
                <sort select="@*[4]"/>
                <sort select="@*[5]"/>
                <sort select="@*[6]"/>
            </apply-templates>
        </copy>
    </template>
</stylesheet>

거의 다 왔지만 몇 가지 문제가 있습니다.

  • 정렬되지 않습니다모든속성 값( @*효과가 없음)
  • 빈 요소의 끝 부분( ) 앞의 공백을 제거 <foo />합니다 <foo/>.
  • EOF에 개행 문자를 추가합니다(제 생각에는 버그는 아니지만 결과 파일을 원본과 덜 유사하게 만듭니다).

답변1

xmllint정식 XML 정렬의 세부 사항과 그것이 설명하는 내용과 일치하는지 확실하지 않지만 파일을 소스 제어에 저장하기 전에 정식 XML 정렬을 사용하는 것이 좋습니다 . 이에 대해 일관성을 유지한다면 버전 관리는 매우 깨끗하고 유용할 것입니다. 다음을 스크립트로 수정하거나 git을 사용하는 경우 githook스크립트를 시작하도록 설정할 수 있습니다.

$ xmllint --c14n originalConfig.xml > sortedConfig.xml
$ mv sortedConfig.xml originalConfig.xml

Linux 또는 Mac을 사용하는 경우 위의 내용이 적합합니다. Windows를 사용하는 경우 cygwin과 같은 것을 설치해야 할 수도 있습니다.

답변2

나는 perl그것을 사용하여 해결 하겠습니다 XML::Twig.

Perl에는 sort값 범위를 비교하기 위해 임의의 기준을 지정할 수 있는 기능이 있습니다. 함수가 상대 순서에 따라 양수, 음수 또는 0 값을 반환하는 한.

여기서 마법이 발생합니다. 정렬 기준을 지정합니다.

  • 노드 이름(레이블)을 기준으로 비교
  • 그런 다음 속성이 존재하는지 여부를 기준으로 비교하십시오.
  • 그런 다음 속성 값을 비교하십시오.

또한 하위 노드를 정렬하려면 구조 전체에서 이 작업을 재귀적으로 수행해야 합니다.

그래서:

#!/usr/bin/env perl
use strict;
use warnings;

use XML::Twig;

my $xml = XML::Twig -> new -> parsefile ('sample.xml');

sub compare_elements {
   ## perl sort uses $a and $b to compare. 
   ## in this case, it's nodes we expect;

   #tag is the node name. 
   my $compare_by_tag = $a -> tag cmp $b -> tag;
   #conditional return - this works because cmp returns zero
   #if the values are the same.
   return $compare_by_tag if $compare_by_tag; 

   #bit more complicated - extract all the attributes of both a and b, and then compare them sequentially:
   #This is to handle case where you've got mismatched attributes.
   #this may be irrelevant based on your input. 
   my %all_atts;
   foreach my $key ( keys %{$a->atts}, keys %{$b->atts}) { 
      $all_atts{$key}++;
   }
   #iterate all the attributes we've seen - in either element. 
   foreach my $key_to_compare ( sort keys %all_atts ) {

      #test if this attribute exists. If it doesn't in one, but does in the other, then that gets sorted to the top. 
      my $exists = ($a -> att($key_to_compare) ? 1 : 0) <=> ($b -> att($key_to_compare) ? 1 : 0);
      return $exists if $exists;

      #attribute exists in both - extract value, and compare them alphanumerically. 
      my $comparison =  $a -> att($key_to_compare) cmp $b -> att($key_to_compare);
      return $comparison if $comparison;
   }
   #we have fallen through all our comparisons, we therefore assume the nodes are the same and return zero. 
   return 0;
}

#recursive sort - traverses to the lowest node in the tree first, and then sorts that, before
#working back up. 
sub sort_children {
   my ( $node ) = @_;
   foreach my $child ( $node -> children ) { 
      #sort this child if is has child nodes. 
      if ( $child -> children ) { 
         sort_children ( $child )
      }     
   }  

   #iterate each of the child nodes of this one, sorting based on above criteria
      foreach my $element ( sort { compare_elements } $node -> children ) {

         #cut everything, then append to the end.
         #because we've ordered these, then this will work as a reorder operation. 
         $element -> cut;
         $element -> paste ( last_child => $node );
      }
}

#set off recursive sort. 
sort_children ( $xml -> root );

#set output formatting. indented_a implicitly sorts attributes. 
$xml -> set_pretty_print ( 'indented_a');
$xml -> print;

입력이 주어지면 출력은 다음과 같습니다.

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ChangeListManager">
    <ignored path=".idea/dataSources.local.xml" />
    <ignored path=".idea/workspace.xml" />
    <ignored path="tilde.iws" />
    <option
        name="EXCLUDED_CONVERTED_TO_IGNORED"
        value="true"
    />
    <option
        name="HIGHLIGHT_CONFLICTS"
        value="true"
    />
    <option
        name="HIGHLIGHT_NON_ACTIVE_CHANGELIST"
        value="false"
    />
    <option
        name="LAST_RESOLUTION"
        value="IGNORE"
    />
    <option
        name="SHOW_DIALOG"
        value="false"
    />
    <option
        name="TRACKING_ENABLED"
        value="true"
    />
  </component>
  <component name="ToolWindowManager">
    <editor active="false" />
    <frame
        extended-state="0"
        height="1179"
        width="958"
        x="1201"
        y="380"
    />
    <layout>
      <window_info
          active="false"
          anchor="bottom"
          auto_hide="false"
          content_ui="tabs"
          id="TODO"
          internal_type="DOCKED"
          order="6"
          show_stripe_button="true"
          sideWeight="0.5"
          side_tool="false"
          type="DOCKED"
          visible="false"
          weight="0.33"
      />
      <window_info
          active="false"
          anchor="left"
          auto_hide="false"
          content_ui="tabs"
          id="Palette&#x09;"
          internal_type="DOCKED"
          order="2"
          show_stripe_button="true"
          sideWeight="0.5"
          side_tool="false"
          type="DOCKED"
          visible="false"
          weight="0.33"
      />
    </layout>
  </component>
</project>

개별 하위 노드의 순서에 관계없이.

indented_a속성을 새로운 줄로 감싸서 더 깔끔하다고 생각하기 때문에 개인적으로 마음에 듭니다 . 그러나 indented출력 형식은 동일한 목적으로 사용될 수 있습니다.

관련 정보