2013-05-20 4 views
2

Я пишу файл XML, и табуляция выходит немного неправильно:Странная XML отступы

<BusinessEvents> 

<MailEvent> 
      <to>Wellington</to> 
      <weight>10.0</weight> 
      <priority>air priority</priority> 
      <volume>10.0</volume> 
      <from>Christchurch</from> 
      <day>Mon May 20 14:30:08 NZST 2013</day> 
      <PPW>8.0</PPW> 
      <PPV>2.5</PPV> 
    </MailEvent> 
<DiscontinueEvent> 
      <to>Wellington</to> 
      <priority>air priority</priority> 
      <company>Kiwi Co</company> 
      <from>Sydney</from> 
    </DiscontinueEvent> 
<RoutePriceUpdateEvent> 
      <weightcost>3.0</weightcost> 
      <to>Wellington</to> 
      <duration>15.0</duration> 
      <maxweight>40.0</maxweight> 
      <maxvolume>20.0</maxvolume> 
      <priority>air priority</priority> 
      <company>Kiwi Co</company> 
      <day>Mon May 20 14:30:08 NZST 2013</day> 
      <frequency>3.0</frequency> 
      <from>Wellington</from> 
      <volumecost>2.0</volumecost> 
    </RoutePriceUpdateEvent> 
<CustomerPriceUpdateEvent> 
      <weightcost>3.0</weightcost> 
      <to>Wellington</to> 
      <priority>air priority</priority> 
      <from>Sydney</from> 
      <volumecost>2.0</volumecost> 
    </CustomerPriceUpdateEvent> 
</BusinessEvents> 

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

Я подозреваю, что это, возможно, придется делать с добавлением корня не к документу через doc.appendChild(root), но когда я делаю, что я получаю сообщение об ошибке

«была предпринята попытка вставить узел, где не разрешено "

Вот мой парсер:.

DocumentBuilderFactory icFactory = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder icBuilder; 
     try { 
      icBuilder = icFactory.newDocumentBuilder(); 
      String businessEventsFile = System.getProperty("user.dir") + "/testdata/businessevents/businessevents.xml"; 
      Document doc = icBuilder.parse (businessEventsFile); 

      Element root = doc.getDocumentElement(); 

      Element element; 

      if(event instanceof CustomerPriceUpdateEvent){ 
       element = doc.createElement("CustomerPriceUpdateEvent"); 
      } 
      else if(event instanceof DiscontinueEvent){ 
       element = doc.createElement("DiscontinueEvent"); 
      } 
      else if(event instanceof MailEvent){ 
       element = doc.createElement("MailEvent"); 
      } 
      else if(event instanceof RoutePriceUpdateEvent){ 
       element = doc.createElement("RoutePriceUpdateEvent"); 
      } 
      else{ 
       throw new Exception("business event isnt valid"); 
      } 

      for(Map.Entry<String, String> field : event.getFields().entrySet()){ 
       Element newElement = doc.createElement(field.getKey()); 
       newElement.appendChild(doc.createTextNode(field.getValue())); 
       element.appendChild(newElement); 
      } 

      root.appendChild(element); 


      // output DOM XML to console 
      Transformer transformer = TransformerFactory.newInstance().newTransformer(); 
//   transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 
      transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 
      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "5"); 
      DOMSource source = new DOMSource(doc); 
      StreamResult console = new StreamResult(businessEventsFile); 
      transformer.transform(source, console); 

Любое понимание будет оценено.

ответ

7

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

Например, после разбора документа у вас, вероятно, есть пустой текстовый узел прямо перед узлом <MailEvent> под узлом <BusinessEvents>. Трансформатор сохраняет пустые текстовые узлы (что я предполагаю, это правильное поведение).

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

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

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

Метод cleanEmptyTextNodes(Node parentNode) ниже рекурсивно удаляет все пустые текстовые узлы из поддерева.

import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.StringWriter; 

import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.parsers.ParserConfigurationException; 
import javax.xml.transform.OutputKeys; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerException; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.dom.DOMSource; 
import javax.xml.transform.stream.StreamResult; 

import org.w3c.dom.Document; 
import org.w3c.dom.Node; 
import org.xml.sax.SAXException; 

public class FormatXml { 

    public static void main(String[] args) throws ParserConfigurationException, 
      FileNotFoundException, SAXException, IOException, 
      TransformerException { 
     DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory 
       .newInstance(); 
     DocumentBuilder documentBuilder = docBuilderFactory 
       .newDocumentBuilder(); 
     Document node = documentBuilder.parse(new FileInputStream("data.xml")); 
     System.out.println(format(node, 4)); 
    } 

    public static String format(Node node, int indent) 
      throws TransformerException { 
     cleanEmptyTextNodes(node); 
     StreamResult result = new StreamResult(new StringWriter()); 
     getTransformer(indent).transform(new DOMSource(node), result); 
     return result.getWriter().toString(); 
    } 

    private static Transformer getTransformer(int indent) { 
     Transformer transformer; 
     try { 
      transformer = TransformerFactory.newInstance().newTransformer(); 
     } catch (Exception e) { 
      throw new RuntimeException("Failed to create the Transformer", e); 
     } 
     transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 
     transformer.setOutputProperty(
       "{http://xml.apache.org/xslt}indent-amount", 
       Integer.toString(indent)); 
     return transformer; 
    } 

    /** 
    * Removes text nodes that only contains whitespace. The conditions for 
    * removing text nodes, besides only containing whitespace, are: If the 
    * parent node has at least one child of any of the following types, all 
    * whitespace-only text-node children will be removed: - ELEMENT child - 
    * CDATA child - COMMENT child 
    * 
    * The purpose of this is to make the format() method (that use a 
    * Transformer for formatting) more consistent regarding indenting and line 
    * breaks. 
    */ 
    private static void cleanEmptyTextNodes(Node parentNode) { 
     boolean removeEmptyTextNodes = false; 
     Node childNode = parentNode.getFirstChild(); 
     while (childNode != null) { 
      removeEmptyTextNodes |= checkNodeTypes(childNode); 
      childNode = childNode.getNextSibling(); 
     } 

     if (removeEmptyTextNodes) { 
      removeEmptyTextNodes(parentNode); 
     } 
    } 

    private static void removeEmptyTextNodes(Node parentNode) { 
     Node childNode = parentNode.getFirstChild(); 
     while (childNode != null) { 
      // grab the "nextSibling" before the child node is removed 
      Node nextChild = childNode.getNextSibling(); 

      short nodeType = childNode.getNodeType(); 
      if (nodeType == Node.TEXT_NODE) { 
       boolean containsOnlyWhitespace = childNode.getNodeValue() 
         .trim().isEmpty(); 
       if (containsOnlyWhitespace) { 
        parentNode.removeChild(childNode); 
       } 
      } 
      childNode = nextChild; 
     } 
    } 

    private static boolean checkNodeTypes(Node childNode) { 
     short nodeType = childNode.getNodeType(); 

     if (nodeType == Node.ELEMENT_NODE) { 
      cleanEmptyTextNodes(childNode); // recurse into subtree 
     } 

     if (nodeType == Node.ELEMENT_NODE 
       || nodeType == Node.CDATA_SECTION_NODE 
       || nodeType == Node.COMMENT_NODE) { 
      return true; 
     } else { 
      return false; 
     } 
    } 

} 

Полученный форматированный вывод с входом:

<?xml version="1.0" encoding="UTF-8" standalone="no"?> 
<BusinessEvents> 
    <MailEvent> 
     <to>Wellington</to> 
     <weight>10.0</weight> 
     <priority>air priority</priority> 
     <volume>10.0</volume> 
     <from>Christchurch</from> 
     <day>Mon May 20 14:30:08 NZST 2013</day> 
     <PPW>8.0</PPW> 
     <PPV>2.5</PPV> 
    </MailEvent> 
    <DiscontinueEvent> 
     <to>Wellington</to> 
     <priority>air priority</priority> 
     <company>Kiwi Co</company> 
     <from>Sydney</from> 
    </DiscontinueEvent> 
    <RoutePriceUpdateEvent> 
     <weightcost>3.0</weightcost> 
     <to>Wellington</to> 
     <duration>15.0</duration> 
     <maxweight>40.0</maxweight> 
     <maxvolume>20.0</maxvolume> 
     <priority>air priority</priority> 
     <company>Kiwi Co</company> 
     <day>Mon May 20 14:30:08 NZST 2013</day> 
     <frequency>3.0</frequency> 
     <from>Wellington</from> 
     <volumecost>2.0</volumecost> 
    </RoutePriceUpdateEvent> 
    <CustomerPriceUpdateEvent> 
     <weightcost>3.0</weightcost> 
     <to>Wellington</to> 
     <priority>air priority</priority> 
     <from>Sydney</from> 
     <volumecost>2.0</volumecost> 
    </CustomerPriceUpdateEvent> 
</BusinessEvents>